mirror of
https://github.com/chartdb/chartdb.git
synced 2025-10-31 20:14:00 +00:00
create diagram dialog, data layer
This commit is contained in:
467
package-lock.json
generated
467
package-lock.json
generated
@@ -22,15 +22,18 @@
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@xyflow/react": "^12.0.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"dexie": "^4.0.8",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
"lucide-react": "^0.424.0",
|
||||
"nanoid": "^5.0.7",
|
||||
"react": "^18.3.1",
|
||||
"react-code-blocks": "^0.1.6",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-resizable-panels": "^2.0.22",
|
||||
"react-router-dom": "^6.26.0",
|
||||
@@ -422,6 +425,27 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/is-prop-valid": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
|
||||
"integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "^0.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/memoize": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
|
||||
"integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/unitless": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
|
||||
"integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||
@@ -1913,6 +1937,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toggle-group": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.0.tgz",
|
||||
"integrity": "sha512-PpTJV68dZU2oqqgq75Uzto5o/XfOVgkrJ9rulVmfTKxWp3HfUjHE6CP/WLRR4AzPX9HWxw7vFow2me85Yu+Naw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.0",
|
||||
"@radix-ui/react-context": "1.1.0",
|
||||
"@radix-ui/react-direction": "1.1.0",
|
||||
"@radix-ui/react-primitive": "2.0.0",
|
||||
"@radix-ui/react-roving-focus": "1.1.0",
|
||||
"@radix-ui/react-toggle": "1.1.0",
|
||||
"@radix-ui/react-use-controllable-state": "1.1.0"
|
||||
},
|
||||
"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": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.2.tgz",
|
||||
@@ -2427,6 +2480,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/hast": {
|
||||
"version": "2.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz",
|
||||
"integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/js-cookie": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.7.tgz",
|
||||
@@ -2477,6 +2539,18 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/stylis": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz",
|
||||
"integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
"version": "2.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
|
||||
"integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "7.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz",
|
||||
@@ -3186,6 +3260,15 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/camelize": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
|
||||
"integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001649",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001649.tgz",
|
||||
@@ -3222,6 +3305,36 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/character-entities": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
|
||||
"integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/character-entities-legacy": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
|
||||
"integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/character-reference-invalid": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
|
||||
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
@@ -3689,6 +3802,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/comma-separated-tokens": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz",
|
||||
"integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||
@@ -3740,6 +3863,15 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/css-color-keywords": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
||||
"integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/css-in-js-utils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz",
|
||||
@@ -3749,6 +3881,17 @@
|
||||
"hyphenate-style-name": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/css-to-react-native": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
|
||||
"integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"camelize": "^1.0.0",
|
||||
"css-color-keywords": "^1.0.0",
|
||||
"postcss-value-parser": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
|
||||
@@ -4039,6 +4182,12 @@
|
||||
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dexie": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.8.tgz",
|
||||
"integrity": "sha512-1G6cJevS17KMDK847V3OHvK2zei899GwpDiqfEXHP1ASvme6eWJmAp9AU4s1son2TeGkWmC0g3y8ezOBPnalgQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
@@ -5089,6 +5238,19 @@
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fault": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz",
|
||||
"integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"format": "^0.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/file-entry-cache": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
|
||||
@@ -5174,6 +5336,14 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/format": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
|
||||
"integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
|
||||
"engines": {
|
||||
"node": ">=0.4.x"
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
||||
@@ -5500,6 +5670,42 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-parse-selector": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
|
||||
"integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hastscript": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz",
|
||||
"integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^2.0.0",
|
||||
"comma-separated-tokens": "^1.0.0",
|
||||
"hast-util-parse-selector": "^2.0.0",
|
||||
"property-information": "^5.0.0",
|
||||
"space-separated-tokens": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/highlight.js": {
|
||||
"version": "10.7.3",
|
||||
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
|
||||
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/hyphenate-style-name": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz",
|
||||
@@ -5589,6 +5795,30 @@
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-alphabetical": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
|
||||
"integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/is-alphanumerical": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
|
||||
"integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-alphabetical": "^1.0.0",
|
||||
"is-decimal": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arguments": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
|
||||
@@ -5735,6 +5965,16 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-decimal": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
|
||||
"integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
@@ -5794,6 +6034,16 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-hexadecimal": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
|
||||
"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/is-map": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
|
||||
@@ -6207,6 +6457,20 @@
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/lowlight": {
|
||||
"version": "1.20.0",
|
||||
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz",
|
||||
"integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fault": "^1.0.0",
|
||||
"highlight.js": "~10.7.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
@@ -6585,6 +6849,24 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-entities": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz",
|
||||
"integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"character-entities": "^1.0.0",
|
||||
"character-entities-legacy": "^1.0.0",
|
||||
"character-reference-invalid": "^1.0.0",
|
||||
"is-alphanumerical": "^1.0.0",
|
||||
"is-decimal": "^1.0.0",
|
||||
"is-hexadecimal": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
@@ -6905,6 +7187,15 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prismjs": {
|
||||
"version": "1.29.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
|
||||
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
@@ -6917,6 +7208,19 @@
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/property-information": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz",
|
||||
"integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xtend": "^4.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
@@ -6958,6 +7262,24 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-code-blocks": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/react-code-blocks/-/react-code-blocks-0.1.6.tgz",
|
||||
"integrity": "sha512-ENNuxG07yO+OuX1ChRje3ieefPRz6yrIpHmebQlaFQgzcAHbUfVeTINpOpoI9bSRSObeYo/OdHsporeToZ7fcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.10.4",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"styled-components": "^6.1.0",
|
||||
"tslib": "^2.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
@@ -7100,6 +7422,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-syntax-highlighter": {
|
||||
"version": "15.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz",
|
||||
"integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"highlight.js": "^10.4.1",
|
||||
"lowlight": "^1.17.0",
|
||||
"prismjs": "^1.27.0",
|
||||
"refractor": "^3.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 0.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-universal-interface": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz",
|
||||
@@ -7178,6 +7516,30 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/refractor": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz",
|
||||
"integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hastscript": "^6.0.0",
|
||||
"parse-entities": "^2.0.0",
|
||||
"prismjs": "~1.27.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/refractor/node_modules/prismjs": {
|
||||
"version": "1.27.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
|
||||
"integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
@@ -7437,6 +7799,12 @@
|
||||
"node": ">=6.9"
|
||||
}
|
||||
},
|
||||
"node_modules/shallowequal": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@@ -7515,6 +7883,16 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/space-separated-tokens": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz",
|
||||
"integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/stack-generator": {
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz",
|
||||
@@ -7782,6 +8160,86 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-components": {
|
||||
"version": "6.1.12",
|
||||
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.12.tgz",
|
||||
"integrity": "sha512-n/O4PzRPhbYI0k1vKKayfti3C/IGcPf+DqcrOB7O/ab9x4u/zjqraneT5N45+sIe87cxrCApXM8Bna7NYxwoTA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/is-prop-valid": "1.2.2",
|
||||
"@emotion/unitless": "0.8.1",
|
||||
"@types/stylis": "4.2.5",
|
||||
"css-to-react-native": "3.2.0",
|
||||
"csstype": "3.1.3",
|
||||
"postcss": "8.4.38",
|
||||
"shallowequal": "1.1.0",
|
||||
"stylis": "4.3.2",
|
||||
"tslib": "2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/styled-components"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.8.0",
|
||||
"react-dom": ">= 16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-components/node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-components/node_modules/postcss": {
|
||||
"version": "8.4.38",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
||||
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-components/node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/stylis": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
|
||||
@@ -8570,6 +9028,15 @@
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
|
||||
@@ -25,15 +25,18 @@
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@xyflow/react": "^12.0.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"dexie": "^4.0.8",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
"lucide-react": "^0.424.0",
|
||||
"nanoid": "^5.0.7",
|
||||
"react": "^18.3.1",
|
||||
"react-code-blocks": "^0.1.6",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-resizable-panels": "^2.0.22",
|
||||
"react-router-dom": "^6.26.0",
|
||||
|
||||
BIN
src/assets/mariadb_logo.png
Normal file
BIN
src/assets/mariadb_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
BIN
src/assets/mysql_logo.png
Normal file
BIN
src/assets/mysql_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
src/assets/postgresql_logo.png
Normal file
BIN
src/assets/postgresql_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
src/assets/sql_server_logo.png
Normal file
BIN
src/assets/sql_server_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
BIN
src/assets/sqlite_logo.png
Normal file
BIN
src/assets/sqlite_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
55
src/components/code-snippet/code-snippet.tsx
Normal file
55
src/components/code-snippet/code-snippet.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import React from 'react';
|
||||
import { CopyBlock, atomOneDark } from 'react-code-blocks';
|
||||
import { CodeBlockProps } from 'react-code-blocks/dist/components/CodeBlock';
|
||||
|
||||
export interface CodeSnippetProps {
|
||||
className?: string;
|
||||
codeProps?: CodeBlockProps;
|
||||
}
|
||||
|
||||
export const CodeSnippet: React.FC<CodeSnippetProps> = ({
|
||||
className,
|
||||
codeProps,
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn('flex flex-1', className)}>
|
||||
<CopyBlock
|
||||
language="sql"
|
||||
text={
|
||||
"WITH fk_info as ( \n\
|
||||
(SELECT (@fk_info:=NULL),\n\
|
||||
(SELECT (0)\n\
|
||||
FROM (SELECT kcu.table_schema,\n\
|
||||
kcu.table_name,\n\
|
||||
kcu.column_name as fk_column,\n\
|
||||
kcu.constraint_name as foreign_key_name,\n\
|
||||
kcu.referenced_table_name as reference_table,\n\
|
||||
kcu.referenced_column_name as reference_column,\n\
|
||||
CONCAT('FOREIGN KEY (', kcu.column_name, ') REFERENCES ', \n\
|
||||
kcu.referenced_table_name, '(', kcu.refssssssssssssssssssssssssssssssssserenced_column_name, ') ',\n\
|
||||
'ON UPDATE ', rc.update_rule, \n\
|
||||
' ON DELETE ', rc.delete_rule) AS fk_def\n\
|
||||
FROM\n\
|
||||
information_schema.key_column_usage kcu\n\
|
||||
JOIN\n\
|
||||
information_schema.referential_constraints rc\n\
|
||||
ON kcu.constraint_name = rc.constraint_name\n\
|
||||
AND kcu.table_name = rc.table_name\n\
|
||||
WHERE\n\
|
||||
kcu.referenced_table_name IS NOT NULL) as fk\n\
|
||||
WHERE table_schema LIKE IFNULL(NULL, '%')\n\
|
||||
AND table_schema = DATABASE()"
|
||||
}
|
||||
theme={atomOneDark}
|
||||
customStyle={{
|
||||
display: 'flex',
|
||||
flex: '1',
|
||||
fontSize: '14px',
|
||||
width: '100%',
|
||||
}}
|
||||
{...codeProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -29,8 +29,10 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
|
||||
showClose?: boolean;
|
||||
}
|
||||
>(({ className, children, showClose, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
@@ -42,10 +44,12 @@ const DialogContent = React.forwardRef<
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<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="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
{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="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
)}
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
));
|
||||
|
||||
24
src/components/textarea/textarea.tsx
Normal file
24
src/components/textarea/textarea.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export interface TextareaProps
|
||||
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
'flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
Textarea.displayName = 'Textarea';
|
||||
|
||||
export { Textarea };
|
||||
59
src/components/toggle/toggle-group.tsx
Normal file
59
src/components/toggle/toggle-group.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
|
||||
import { type VariantProps } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { toggleVariants } from './toggle-variants';
|
||||
|
||||
const ToggleGroupContext = React.createContext<
|
||||
VariantProps<typeof toggleVariants>
|
||||
>({
|
||||
size: 'default',
|
||||
variant: 'default',
|
||||
});
|
||||
|
||||
const ToggleGroup = React.forwardRef<
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
|
||||
VariantProps<typeof toggleVariants>
|
||||
>(({ className, variant, size, children, ...props }, ref) => (
|
||||
<ToggleGroupPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn('flex items-center justify-center gap-1', className)}
|
||||
{...props}
|
||||
>
|
||||
<ToggleGroupContext.Provider value={{ variant, size }}>
|
||||
{children}
|
||||
</ToggleGroupContext.Provider>
|
||||
</ToggleGroupPrimitive.Root>
|
||||
));
|
||||
|
||||
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
|
||||
|
||||
const ToggleGroupItem = React.forwardRef<
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
|
||||
VariantProps<typeof toggleVariants>
|
||||
>(({ className, children, variant, size, ...props }, ref) => {
|
||||
const context = React.useContext(ToggleGroupContext);
|
||||
|
||||
return (
|
||||
<ToggleGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
toggleVariants({
|
||||
variant: context.variant || variant,
|
||||
size: context.size || size,
|
||||
}),
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</ToggleGroupPrimitive.Item>
|
||||
);
|
||||
});
|
||||
|
||||
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
|
||||
|
||||
export { ToggleGroup, ToggleGroupItem };
|
||||
@@ -7,12 +7,18 @@ import { DBIndex } from '@/lib/domain/db-index';
|
||||
import { DBRelationship } from '@/lib/domain/db-relationship';
|
||||
|
||||
export interface ChartDBContext {
|
||||
diagramId: string;
|
||||
diagramName: string;
|
||||
databaseType: DatabaseType;
|
||||
tables: DBTable[];
|
||||
relationships: DBRelationship[];
|
||||
|
||||
// General operations
|
||||
updateDiagramId: (id: string) => void;
|
||||
updateDiagramName: (name: string) => void;
|
||||
|
||||
// Database type operations
|
||||
setDatabaseType: (databaseType: DatabaseType) => void;
|
||||
updateDatabaseType: (databaseType: DatabaseType) => void;
|
||||
|
||||
// Table operations
|
||||
createTable: () => DBTable;
|
||||
@@ -66,11 +72,17 @@ export interface ChartDBContext {
|
||||
|
||||
export const chartDBContext = createContext<ChartDBContext>({
|
||||
databaseType: DatabaseType.GENERIC,
|
||||
diagramName: 'New Diagram',
|
||||
diagramId: '',
|
||||
tables: [],
|
||||
relationships: [],
|
||||
|
||||
// General operations
|
||||
updateDiagramId: emptyFn,
|
||||
updateDiagramName: emptyFn,
|
||||
|
||||
// Database type operations
|
||||
setDatabaseType: emptyFn,
|
||||
updateDatabaseType: emptyFn,
|
||||
|
||||
// Table operations
|
||||
updateTables: emptyFn,
|
||||
|
||||
@@ -10,6 +10,8 @@ import { DBRelationship } from '@/lib/domain/db-relationship';
|
||||
export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [diagramId, setDiagramId] = React.useState('');
|
||||
const [diagramName, setDiagramName] = React.useState('New Diagram');
|
||||
const [databaseType, setDatabaseType] = React.useState<DatabaseType>(
|
||||
DatabaseType.GENERIC
|
||||
);
|
||||
@@ -18,6 +20,10 @@ export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
|
||||
[]
|
||||
);
|
||||
|
||||
const updateDatabaseType = setDatabaseType;
|
||||
const updateDiagramId = setDiagramId;
|
||||
const updateDiagramName = setDiagramName;
|
||||
|
||||
const addTable = (table: DBTable) => {
|
||||
setTables((tables) => [...tables, table]);
|
||||
};
|
||||
@@ -282,10 +288,14 @@ export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
|
||||
return (
|
||||
<chartDBContext.Provider
|
||||
value={{
|
||||
diagramId,
|
||||
diagramName,
|
||||
databaseType,
|
||||
tables,
|
||||
relationships,
|
||||
setDatabaseType,
|
||||
updateDiagramId,
|
||||
updateDiagramName,
|
||||
updateDatabaseType,
|
||||
createTable,
|
||||
addTable,
|
||||
getTable,
|
||||
|
||||
69
src/context/data-context/data-context.tsx
Normal file
69
src/context/data-context/data-context.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import { createContext } from 'react';
|
||||
import { Diagram } from '@/lib/domain/diagram';
|
||||
import { emptyFn } from '@/lib/utils';
|
||||
import { DBRelationship } from '@/lib/domain/db-relationship';
|
||||
import { DBTable } from '@/lib/domain/db-table';
|
||||
|
||||
export interface DataContext {
|
||||
// Diagram operations
|
||||
addDiagram: (params: { diagram: Diagram }) => Promise<void>;
|
||||
listDiagrams: () => Promise<Diagram[]>;
|
||||
getDiagram: (id: string) => Promise<Diagram | undefined>;
|
||||
updateDiagram: (params: {
|
||||
id: string;
|
||||
diagram: Partial<Diagram>;
|
||||
}) => Promise<void>;
|
||||
deleteDiagram: (id: string) => Promise<void>;
|
||||
|
||||
// Table operations
|
||||
addTable: (params: { diagramId: string; table: DBTable }) => Promise<void>;
|
||||
getTable: (params: {
|
||||
diagramId: string;
|
||||
id: string;
|
||||
}) => Promise<DBTable | undefined>;
|
||||
updateTable: (params: {
|
||||
id: string;
|
||||
table: Partial<DBTable>;
|
||||
}) => Promise<void>;
|
||||
deleteTable: (params: { diagramId: string; id: string }) => Promise<void>;
|
||||
listTables: (diagramId: string) => Promise<DBTable[]>;
|
||||
|
||||
// Relationships operations
|
||||
addRelationship: (params: {
|
||||
diagramId: string;
|
||||
relationship: DBRelationship;
|
||||
}) => Promise<void>;
|
||||
getRelationship: (params: {
|
||||
diagramId: string;
|
||||
id: string;
|
||||
}) => Promise<DBRelationship | undefined>;
|
||||
updateRelationship: (params: {
|
||||
id: string;
|
||||
relationship: Partial<DBRelationship>;
|
||||
}) => Promise<void>;
|
||||
deleteRelationship: (params: {
|
||||
diagramId: string;
|
||||
id: string;
|
||||
}) => Promise<void>;
|
||||
listRelationships: (diagramId: string) => Promise<DBRelationship[]>;
|
||||
}
|
||||
|
||||
export const dataContext = createContext<DataContext>({
|
||||
addDiagram: emptyFn,
|
||||
listDiagrams: emptyFn,
|
||||
getDiagram: emptyFn,
|
||||
updateDiagram: emptyFn,
|
||||
deleteDiagram: emptyFn,
|
||||
|
||||
addTable: emptyFn,
|
||||
getTable: emptyFn,
|
||||
updateTable: emptyFn,
|
||||
deleteTable: emptyFn,
|
||||
listTables: emptyFn,
|
||||
|
||||
addRelationship: emptyFn,
|
||||
getRelationship: emptyFn,
|
||||
updateRelationship: emptyFn,
|
||||
deleteRelationship: emptyFn,
|
||||
listRelationships: emptyFn,
|
||||
});
|
||||
190
src/context/data-context/data-provider.tsx
Normal file
190
src/context/data-context/data-provider.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
import React from 'react';
|
||||
import { dataContext } from './data-context';
|
||||
import Dexie, { type EntityTable } from 'dexie';
|
||||
import { Diagram } from '@/lib/domain/diagram';
|
||||
import { DBTable } from '@/lib/domain/db-table';
|
||||
import { DBRelationship } from '@/lib/domain/db-relationship';
|
||||
|
||||
export const DataProvider: React.FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const db = new Dexie('ChartDB') as Dexie & {
|
||||
diagrams: EntityTable<
|
||||
Diagram,
|
||||
'id' // primary key "id" (for the typings only)
|
||||
>;
|
||||
db_tables: EntityTable<
|
||||
DBTable & { diagramId: string },
|
||||
'id' // primary key "id" (for the typings only)
|
||||
>;
|
||||
db_relationships: EntityTable<
|
||||
DBRelationship & { diagramId: string },
|
||||
'id' // primary key "id" (for the typings only)
|
||||
>;
|
||||
};
|
||||
|
||||
// Schema declaration:
|
||||
db.version(1).stores({
|
||||
diagrams: '++id, name, databaseType, tables, relationships',
|
||||
db_tables:
|
||||
'++id, diagramId, name, x, y, fields, indexes, color, createdAt',
|
||||
db_relationships:
|
||||
'++id, diagramId, name, sourceTableId, targetTableId, sourceFieldId, targetFieldId, type, createdAt',
|
||||
});
|
||||
|
||||
const addDiagram = async ({ diagram }: { diagram: Diagram }) => {
|
||||
await db.diagrams.add(diagram);
|
||||
};
|
||||
|
||||
const listDiagrams = async (): Promise<Diagram[]> => {
|
||||
return await db.diagrams.toArray();
|
||||
};
|
||||
|
||||
const getDiagram = async (id: string): Promise<Diagram | undefined> => {
|
||||
return await db.diagrams.get(id);
|
||||
};
|
||||
|
||||
const updateDiagram = async ({
|
||||
diagram,
|
||||
id,
|
||||
}: {
|
||||
id: string;
|
||||
diagram: Partial<Diagram>;
|
||||
}) => {
|
||||
await db.diagrams.update(id, diagram);
|
||||
};
|
||||
|
||||
const deleteDiagram = async (id: string) => {
|
||||
await Promise.all([
|
||||
db.diagrams.delete(id),
|
||||
db.db_tables.where('diagramId').equals(id).delete(),
|
||||
db.db_relationships.where('diagramId').equals(id).delete(),
|
||||
]);
|
||||
};
|
||||
|
||||
const addTable = async ({
|
||||
diagramId,
|
||||
table,
|
||||
}: {
|
||||
diagramId: string;
|
||||
table: DBTable;
|
||||
}) => {
|
||||
await db.db_tables.add({
|
||||
...table,
|
||||
diagramId,
|
||||
});
|
||||
};
|
||||
|
||||
const getTable = async ({
|
||||
id,
|
||||
diagramId,
|
||||
}: {
|
||||
diagramId: string;
|
||||
id: string;
|
||||
}): Promise<DBTable | undefined> => {
|
||||
return await db.db_tables.get({ id, diagramId });
|
||||
};
|
||||
|
||||
const updateTable = async ({
|
||||
id,
|
||||
table,
|
||||
}: {
|
||||
id: string;
|
||||
table: Partial<DBTable>;
|
||||
}) => {
|
||||
await db.db_tables.update(id, table);
|
||||
};
|
||||
|
||||
const deleteTable = async ({
|
||||
id,
|
||||
diagramId,
|
||||
}: {
|
||||
id: string;
|
||||
diagramId: string;
|
||||
}) => {
|
||||
await db.db_tables.where({ id, diagramId }).delete();
|
||||
};
|
||||
|
||||
const listTables = async (diagramId: string): Promise<DBTable[]> => {
|
||||
return await db.db_tables
|
||||
.where('diagramId')
|
||||
.equals(diagramId)
|
||||
.toArray();
|
||||
};
|
||||
|
||||
const addRelationship = async ({
|
||||
diagramId,
|
||||
relationship,
|
||||
}: {
|
||||
diagramId: string;
|
||||
relationship: DBRelationship;
|
||||
}) => {
|
||||
await db.db_relationships.add({
|
||||
...relationship,
|
||||
diagramId,
|
||||
});
|
||||
};
|
||||
|
||||
const getRelationship = async ({
|
||||
id,
|
||||
diagramId,
|
||||
}: {
|
||||
diagramId: string;
|
||||
id: string;
|
||||
}): Promise<DBRelationship | undefined> => {
|
||||
return await db.db_relationships.get({ id, diagramId });
|
||||
};
|
||||
|
||||
const updateRelationship = async ({
|
||||
id,
|
||||
relationship,
|
||||
}: {
|
||||
id: string;
|
||||
relationship: Partial<DBRelationship>;
|
||||
}) => {
|
||||
await db.db_relationships.update(id, relationship);
|
||||
};
|
||||
|
||||
const deleteRelationship = async ({
|
||||
id,
|
||||
diagramId,
|
||||
}: {
|
||||
id: string;
|
||||
diagramId: string;
|
||||
}) => {
|
||||
await db.db_relationships.where({ id, diagramId }).delete();
|
||||
};
|
||||
|
||||
const listRelationships = async (
|
||||
diagramId: string
|
||||
): Promise<DBRelationship[]> => {
|
||||
return await db.db_relationships
|
||||
.where('diagramId')
|
||||
.equals(diagramId)
|
||||
.toArray();
|
||||
};
|
||||
|
||||
return (
|
||||
<dataContext.Provider
|
||||
value={{
|
||||
addDiagram,
|
||||
listDiagrams,
|
||||
getDiagram,
|
||||
updateDiagram,
|
||||
deleteDiagram,
|
||||
addTable,
|
||||
getTable,
|
||||
updateTable,
|
||||
deleteTable,
|
||||
listTables,
|
||||
addRelationship,
|
||||
getRelationship,
|
||||
updateRelationship,
|
||||
deleteRelationship,
|
||||
listRelationships,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</dataContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import { createContext } from 'react';
|
||||
import { emptyFn } from '@/lib/utils';
|
||||
|
||||
export interface CreateDiagramDialogContext {
|
||||
openCreateDiagramDialog: () => void;
|
||||
closeCreateDiagramDialog: () => void;
|
||||
}
|
||||
|
||||
export const createDiagramDialogContext =
|
||||
createContext<CreateDiagramDialogContext>({
|
||||
openCreateDiagramDialog: emptyFn,
|
||||
closeCreateDiagramDialog: emptyFn,
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { createDiagramDialogContext } from './create-diagram-dialog-context';
|
||||
import { CreateDiagramDialog } from './create-diagram-dialog';
|
||||
|
||||
export const CreateDiagramDialogProvider: React.FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
return (
|
||||
<createDiagramDialogContext.Provider
|
||||
value={{
|
||||
openCreateDiagramDialog: () => setOpen(true),
|
||||
closeCreateDiagramDialog: () => setOpen(false),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<CreateDiagramDialog dialog={{ open }} />
|
||||
</createDiagramDialogContext.Provider>
|
||||
);
|
||||
};
|
||||
257
src/dialogs/create-diagram-dialog/create-diagram-dialog.tsx
Normal file
257
src/dialogs/create-diagram-dialog/create-diagram-dialog.tsx
Normal file
@@ -0,0 +1,257 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { Button } from '@/components/button/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/dialog/dialog';
|
||||
import { DialogProps } from '@radix-ui/react-dialog';
|
||||
import { ToggleGroup, ToggleGroupItem } from '@/components/toggle/toggle-group';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import { getDatabaseLogo } from '@/lib/databases';
|
||||
import { CodeSnippet } from '@/components/code-snippet/code-snippet';
|
||||
import { Textarea } from '@/components/textarea/textarea';
|
||||
import { useData } from '@/hooks/use-data';
|
||||
import { Diagram } from '@/lib/domain/diagram';
|
||||
import { generateId } from '@/lib/utils';
|
||||
import { useCreateDiagramDialog } from '@/hooks/use-create-diagram-dialog';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
enum CreateDiagramDialogStep {
|
||||
SELECT_DATABASE = 'SELECT_DATABASE',
|
||||
IMPORT_DATABASE = 'IMPORT_DATABASE',
|
||||
}
|
||||
|
||||
export interface CreateDiagramDialogProps {
|
||||
dialog: DialogProps;
|
||||
}
|
||||
|
||||
export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
||||
dialog,
|
||||
}) => {
|
||||
const [databaseType, setDatabaseType] = React.useState<DatabaseType>(
|
||||
DatabaseType.GENERIC
|
||||
);
|
||||
const { closeCreateDiagramDialog } = useCreateDiagramDialog();
|
||||
const [scriptResult, setScriptResult] = React.useState('');
|
||||
const [step, setStep] = React.useState<CreateDiagramDialogStep>(
|
||||
CreateDiagramDialogStep.SELECT_DATABASE
|
||||
);
|
||||
const { listDiagrams, addDiagram } = useData();
|
||||
const [diagramNumber, setDiagramNumber] = React.useState<number>(1);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchDiagrams = async () => {
|
||||
const diagrams = await listDiagrams();
|
||||
console.log({ diagrams });
|
||||
setDiagramNumber(diagrams.length + 1);
|
||||
};
|
||||
fetchDiagrams();
|
||||
}, [listDiagrams, setDiagramNumber]);
|
||||
|
||||
const createNewDiagram = useCallback(async () => {
|
||||
const diagram: Diagram = {
|
||||
id: generateId(),
|
||||
name: `Diagram ${diagramNumber}`,
|
||||
databaseType,
|
||||
tables: [],
|
||||
relationships: [],
|
||||
};
|
||||
await addDiagram({ diagram });
|
||||
closeCreateDiagramDialog();
|
||||
navigate(`/diagrams/${diagram.id}`);
|
||||
}, [
|
||||
databaseType,
|
||||
diagramNumber,
|
||||
addDiagram,
|
||||
closeCreateDiagramDialog,
|
||||
navigate,
|
||||
]);
|
||||
|
||||
const renderDatabaseOption = useCallback((type: DatabaseType) => {
|
||||
const logo = getDatabaseLogo(type);
|
||||
return (
|
||||
<ToggleGroupItem
|
||||
value={type}
|
||||
aria-label="Toggle bold"
|
||||
className="flex w-32 h-32"
|
||||
>
|
||||
<img src={logo} alt="PostgreSQL" />
|
||||
</ToggleGroupItem>
|
||||
);
|
||||
}, []);
|
||||
|
||||
const renderHeader = useCallback(() => {
|
||||
switch (step) {
|
||||
case CreateDiagramDialogStep.SELECT_DATABASE:
|
||||
return (
|
||||
<DialogHeader>
|
||||
<DialogTitle>What is your Database?</DialogTitle>
|
||||
<DialogDescription>
|
||||
Each database has its own unique features and
|
||||
capabilities.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
);
|
||||
case CreateDiagramDialogStep.IMPORT_DATABASE:
|
||||
return (
|
||||
<DialogHeader>
|
||||
<DialogTitle>Import your Database</DialogTitle>
|
||||
</DialogHeader>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}, [step]);
|
||||
|
||||
const renderContent = useCallback(() => {
|
||||
switch (step) {
|
||||
case CreateDiagramDialogStep.SELECT_DATABASE:
|
||||
return (
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
<ToggleGroup
|
||||
value={databaseType}
|
||||
onValueChange={(value: DatabaseType) => {
|
||||
if (!value) {
|
||||
setDatabaseType(DatabaseType.GENERIC);
|
||||
} else {
|
||||
setDatabaseType(value);
|
||||
setStep(
|
||||
CreateDiagramDialogStep.IMPORT_DATABASE
|
||||
);
|
||||
}
|
||||
}}
|
||||
type="single"
|
||||
className="grid grid-cols-3 grid-flow-row gap-6 xl:grid-cols-5"
|
||||
>
|
||||
{renderDatabaseOption(DatabaseType.MYSQL)}
|
||||
{renderDatabaseOption(DatabaseType.POSTGRESQL)}
|
||||
{renderDatabaseOption(DatabaseType.MARIADB)}
|
||||
{renderDatabaseOption(DatabaseType.SQLITE)}
|
||||
{renderDatabaseOption(DatabaseType.SQL_SERVER)}
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
);
|
||||
case CreateDiagramDialogStep.IMPORT_DATABASE:
|
||||
return (
|
||||
<div className="flex flex-1 flex-col w-full gap-6">
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
1. Run this script in your database:
|
||||
</p>
|
||||
<CodeSnippet className="max-h-40 w-full" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 h-48">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
2. Paste the script result here:
|
||||
</p>
|
||||
<Textarea
|
||||
className="flex-1 w-full p-2 text-sm bg-muted-1 rounded-md"
|
||||
placeholder="Script result here..."
|
||||
value={scriptResult}
|
||||
onChange={(e) =>
|
||||
setScriptResult(e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}, [
|
||||
step,
|
||||
databaseType,
|
||||
scriptResult,
|
||||
setScriptResult,
|
||||
renderDatabaseOption,
|
||||
setDatabaseType,
|
||||
]);
|
||||
|
||||
const renderFooter = useCallback(() => {
|
||||
switch (step) {
|
||||
case CreateDiagramDialogStep.SELECT_DATABASE:
|
||||
return (
|
||||
<DialogFooter className="flex !justify-between gap-2">
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={createNewDiagram}
|
||||
>
|
||||
Skip
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="default"
|
||||
disabled={databaseType === DatabaseType.GENERIC}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
);
|
||||
case CreateDiagramDialogStep.IMPORT_DATABASE:
|
||||
return (
|
||||
<DialogFooter className="flex !justify-between gap-2">
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
setStep(
|
||||
CreateDiagramDialogStep.SELECT_DATABASE
|
||||
)
|
||||
}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<div className="flex gap-2">
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={createNewDiagram}
|
||||
>
|
||||
Skip this step
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="default"
|
||||
disabled={scriptResult.trim().length === 0}
|
||||
onClick={createNewDiagram}
|
||||
>
|
||||
Finish
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}, [step, databaseType, scriptResult, createNewDiagram]);
|
||||
|
||||
return (
|
||||
<Dialog {...dialog}>
|
||||
<DialogContent
|
||||
className="flex flex-col min-w-[500px] xl:min-w-[75vw] max-h-[80vh] overflow-y-auto"
|
||||
showClose={false}
|
||||
>
|
||||
{renderHeader()}
|
||||
{renderContent()}
|
||||
{renderFooter()}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
5
src/hooks/use-create-diagram-dialog.ts
Normal file
5
src/hooks/use-create-diagram-dialog.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { useContext } from 'react';
|
||||
import { createDiagramDialogContext } from '@/dialogs/create-diagram-dialog/create-diagram-dialog-context';
|
||||
|
||||
export const useCreateDiagramDialog = () =>
|
||||
useContext(createDiagramDialogContext);
|
||||
4
src/hooks/use-data.ts
Normal file
4
src/hooks/use-data.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { useContext } from 'react';
|
||||
import { dataContext } from '@/context/data-context/data-context';
|
||||
|
||||
export const useData = () => useContext(dataContext);
|
||||
@@ -13,6 +13,9 @@ export const dataTypeMap: Record<
|
||||
readonly (typeof dataTypes)[number][]
|
||||
> = {
|
||||
[DatabaseType.GENERIC]: genericDataTypes,
|
||||
[DatabaseType.POSTGRES]: postgresDataTypes,
|
||||
[DatabaseType.POSTGRESQL]: postgresDataTypes,
|
||||
[DatabaseType.MYSQL]: mysqlDataTypes,
|
||||
[DatabaseType.SQL_SERVER]: [],
|
||||
[DatabaseType.MARIADB]: [],
|
||||
[DatabaseType.SQLITE]: [],
|
||||
} as const;
|
||||
|
||||
@@ -1,117 +1,118 @@
|
||||
import { minimizeQuery } from './minimize-query';
|
||||
|
||||
const rawPostgresQuery = `
|
||||
WITH fk_info AS (
|
||||
select array_to_string(array_agg(CONCAT('{"schema":"', schema_name, '"',
|
||||
',"table":"', table_name, '"',
|
||||
',"column":"', fk_column, '"',
|
||||
',"foreign_key_name":"', foreign_key_name, '"',
|
||||
',"reference_table":"', reference_table, '"',
|
||||
',"reference_column":"', reference_column, '"',
|
||||
',"fk_def":"', fk_def,
|
||||
'"}')), ',') as fk_metadata
|
||||
from (
|
||||
SELECT
|
||||
'public' as schema_name,
|
||||
conname AS foreign_key_name,
|
||||
conrelid::regclass AS table_name,
|
||||
(regexp_matches(pg_get_constraintdef(oid), 'FOREIGN KEY \((\w+)\) REFERENCES (\w+)\((\w+)\)', 'g'))[1] AS fk_column,
|
||||
(regexp_matches(pg_get_constraintdef(oid), 'FOREIGN KEY \((\w+)\) REFERENCES (\w+)\((\w+)\)', 'g'))[2] AS reference_table,
|
||||
(regexp_matches(pg_get_constraintdef(oid), 'FOREIGN KEY \((\w+)\) REFERENCES (\w+)\((\w+)\)', 'g'))[3] AS reference_column,
|
||||
pg_get_constraintdef(oid) as fk_def
|
||||
FROM
|
||||
pg_constraint
|
||||
WHERE
|
||||
contype = 'f'
|
||||
AND connamespace = 'public'::regnamespace) as x
|
||||
), pk_info AS (
|
||||
SELECT 'public' as schema_name,
|
||||
conrelid::regclass::text AS pk_table,
|
||||
array_length(string_to_array(substring(pg_get_constraintdef(oid) FROM '\((.*?)\)'), ','), 1) AS field_count,
|
||||
regexp_split_to_table(substring(pg_get_constraintdef(oid) FROM '\((.*)\)'),',') AS pk_column,
|
||||
pg_get_constraintdef(oid) as pk_def
|
||||
FROM pg_constraint c
|
||||
where connamespace = 'public'::regnamespace
|
||||
AND c.contype = 'p'
|
||||
),
|
||||
indexes_cols as (
|
||||
select tnsp.nspname as schema_name,
|
||||
trel.relname as table_name,
|
||||
pg_relation_size(tnsp.nspname || '.' || '"' || irel.relname || '"') as index_size,
|
||||
irel.relname as index_name,
|
||||
am.amname as index_type,
|
||||
a.attname as col_name,
|
||||
(case when i.indisunique = true then 'true' else 'false' end) as is_unique,
|
||||
irel.reltuples as cardinality,
|
||||
1 + Array_position(i.indkey, a.attnum) as column_position,
|
||||
case o.OPTION & 1 when 1 then 'DESC' else 'ASC' end as direction,
|
||||
CASE WHEN indpred IS NOT NULL THEN 'true' ELSE 'false' END as is_partial_index
|
||||
from pg_index as i
|
||||
join pg_class as trel on trel.oid = i.indrelid
|
||||
join pg_namespace as tnsp on trel.relnamespace = tnsp.oid
|
||||
join pg_class as irel on irel.oid = i.indexrelid
|
||||
join pg_am as am on irel.relam = am.oid
|
||||
cross join lateral unnest (i.indkey)
|
||||
with ordinality as c (colnum, ordinality) left join lateral unnest (i.indoption)
|
||||
with ordinality as o (option, ordinality)
|
||||
on c.ordinality = o.ordinality join pg_attribute as a on trel.oid = a.attrelid and a.attnum = c.colnum
|
||||
where tnsp.nspname not like 'pg_%'
|
||||
group by tnsp.nspname, trel.relname, irel.relname, am.amname, i.indisunique, i.indexrelid, irel.reltuples, a.attname, array_position(i.indkey, a.attnum), o.OPTION, i.indpred
|
||||
),
|
||||
cols as (
|
||||
select array_to_string(array_agg(CONCAT('{"schema":"', cols.table_schema,
|
||||
'","table":"', cols.table_name,
|
||||
'","name":"', cols.column_name,
|
||||
'","type":"', replace(cols.data_type, '"', ''),
|
||||
'","ordinal_position":"', cols.ordinal_position,
|
||||
'","nullable":', case when (cols.IS_NULLABLE = 'YES') then 'true' else 'false' end,
|
||||
',"is_pk":', case when (pk_column is not null) then 'true' else 'false' end,
|
||||
',"collation":"', coalesce(cols.COLLATION_NAME, ''), '"}')), ',') as cols_metadata
|
||||
from information_schema.columns cols
|
||||
left join pk_info as pk
|
||||
on pk.schema_name = cols.table_schema and pk_table = cols.table_name and pk_column = cols.column_name
|
||||
where cols.table_schema not in ('information_schema', 'pg_catalog')
|
||||
), indexes_metadata as (
|
||||
select array_to_string(array_agg(CONCAT('{"schema":"', schema_name,
|
||||
'","table":"', table_name,
|
||||
'","name":"', index_name,
|
||||
'","column":"', replace(col_name :: text, '"', E'"'),
|
||||
'","index_type":"', index_type,
|
||||
'","cardinality":', cardinality,
|
||||
',"size":', index_size,
|
||||
',"unique":', is_unique,
|
||||
',"is_partial_index":', is_partial_index,
|
||||
',"direction":"', lower(direction),
|
||||
'"}')), ',') as indexes_metadata
|
||||
from indexes_cols x
|
||||
), tbls as (
|
||||
select array_to_string(array_agg(CONCAT('{', '"schema":"', TABLE_SCHEMA, '",', '"table":"', TABLE_NAME, '",', '"rows":',
|
||||
coalesce((select s.n_live_tup
|
||||
from pg_stat_user_tables s
|
||||
where tbls.TABLE_SCHEMA = s.schemaname and tbls.TABLE_NAME = s.relname),
|
||||
0), ', "type":"', TABLE_TYPE, '",', '"engine":"",', '"collation":""}')),
|
||||
',') as tbls_metadata
|
||||
from information_schema.tables tbls
|
||||
where tbls.TABLE_SCHEMA not in ('information_schema', 'pg_catalog')
|
||||
), config as (
|
||||
select array_to_string(
|
||||
array_agg(CONCAT('{"name":"', conf.name, '","value":"', replace(conf.setting, '"', E'"'), '"}')),
|
||||
',') as config_metadata
|
||||
from pg_settings conf
|
||||
), views as
|
||||
(
|
||||
select array_to_string(array_agg(CONCAT('{"schema":"', views.schemaname, '","view_name":"', viewname, '"}')),
|
||||
',') as views_metadata
|
||||
from pg_views views
|
||||
where views.schemaname not in ('information_schema', 'pg_catalog')
|
||||
)
|
||||
select CONCAT('{ "fk_info": [', coalesce(fk_metadata, ''),
|
||||
'], "columns": [', coalesce(cols_metadata, ''),
|
||||
'], "indexes": [', coalesce(indexes_metadata, ''),
|
||||
'], "tables":[', coalesce(tbls_metadata, ''),
|
||||
'], "views":[', coalesce(views_metadata, ''),
|
||||
'], "server_name": "', '', '", "version": "', '',
|
||||
'"}') as " "
|
||||
from cols,indexes_metadata, tbls, config, views, fk_info;
|
||||
`;
|
||||
const rawPostgresQuery = '';
|
||||
// const rawPostgresQuery = `
|
||||
// WITH fk_info AS (
|
||||
// select array_to_string(array_agg(CONCAT('{"schema":"', schema_name, '"',
|
||||
// ',"table":"', table_name, '"',
|
||||
// ',"column":"', fk_column, '"',
|
||||
// ',"foreign_key_name":"', foreign_key_name, '"',
|
||||
// ',"reference_table":"', reference_table, '"',
|
||||
// ',"reference_column":"', reference_column, '"',
|
||||
// ',"fk_def":"', fk_def,
|
||||
// '"}')), ',') as fk_metadata
|
||||
// from (
|
||||
// SELECT
|
||||
// 'public' as schema_name,
|
||||
// conname AS foreign_key_name,
|
||||
// conrelid::regclass AS table_name,
|
||||
// (regexp_matches(pg_get_constraintdef(oid), 'FOREIGN KEY \((\w+)\) REFERENCES (\w+)\((\w+)\)', 'g'))[1] AS fk_column,
|
||||
// (regexp_matches(pg_get_constraintdef(oid), 'FOREIGN KEY \((\w+)\) REFERENCES (\w+)\((\w+)\)', 'g'))[2] AS reference_table,
|
||||
// (regexp_matches(pg_get_constraintdef(oid), 'FOREIGN KEY \((\w+)\) REFERENCES (\w+)\((\w+)\)', 'g'))[3] AS reference_column,
|
||||
// pg_get_constraintdef(oid) as fk_def
|
||||
// FROM
|
||||
// pg_constraint
|
||||
// WHERE
|
||||
// contype = 'f'
|
||||
// AND connamespace = 'public'::regnamespace) as x
|
||||
// ), pk_info AS (
|
||||
// SELECT 'public' as schema_name,
|
||||
// conrelid::regclass::text AS pk_table,
|
||||
// array_length(string_to_array(substring(pg_get_constraintdef(oid) FROM '\((.*?)\)'), ','), 1) AS field_count,
|
||||
// regexp_split_to_table(substring(pg_get_constraintdef(oid) FROM '\((.*)\)'),',') AS pk_column,
|
||||
// pg_get_constraintdef(oid) as pk_def
|
||||
// FROM pg_constraint c
|
||||
// where connamespace = 'public'::regnamespace
|
||||
// AND c.contype = 'p'
|
||||
// ),
|
||||
// indexes_cols as (
|
||||
// select tnsp.nspname as schema_name,
|
||||
// trel.relname as table_name,
|
||||
// pg_relation_size(tnsp.nspname || '.' || '"' || irel.relname || '"') as index_size,
|
||||
// irel.relname as index_name,
|
||||
// am.amname as index_type,
|
||||
// a.attname as col_name,
|
||||
// (case when i.indisunique = true then 'true' else 'false' end) as is_unique,
|
||||
// irel.reltuples as cardinality,
|
||||
// 1 + Array_position(i.indkey, a.attnum) as column_position,
|
||||
// case o.OPTION & 1 when 1 then 'DESC' else 'ASC' end as direction,
|
||||
// CASE WHEN indpred IS NOT NULL THEN 'true' ELSE 'false' END as is_partial_index
|
||||
// from pg_index as i
|
||||
// join pg_class as trel on trel.oid = i.indrelid
|
||||
// join pg_namespace as tnsp on trel.relnamespace = tnsp.oid
|
||||
// join pg_class as irel on irel.oid = i.indexrelid
|
||||
// join pg_am as am on irel.relam = am.oid
|
||||
// cross join lateral unnest (i.indkey)
|
||||
// with ordinality as c (colnum, ordinality) left join lateral unnest (i.indoption)
|
||||
// with ordinality as o (option, ordinality)
|
||||
// on c.ordinality = o.ordinality join pg_attribute as a on trel.oid = a.attrelid and a.attnum = c.colnum
|
||||
// where tnsp.nspname not like 'pg_%'
|
||||
// group by tnsp.nspname, trel.relname, irel.relname, am.amname, i.indisunique, i.indexrelid, irel.reltuples, a.attname, array_position(i.indkey, a.attnum), o.OPTION, i.indpred
|
||||
// ),
|
||||
// cols as (
|
||||
// select array_to_string(array_agg(CONCAT('{"schema":"', cols.table_schema,
|
||||
// '","table":"', cols.table_name,
|
||||
// '","name":"', cols.column_name,
|
||||
// '","type":"', replace(cols.data_type, '"', ''),
|
||||
// '","ordinal_position":"', cols.ordinal_position,
|
||||
// '","nullable":', case when (cols.IS_NULLABLE = 'YES') then 'true' else 'false' end,
|
||||
// ',"is_pk":', case when (pk_column is not null) then 'true' else 'false' end,
|
||||
// ',"collation":"', coalesce(cols.COLLATION_NAME, ''), '"}')), ',') as cols_metadata
|
||||
// from information_schema.columns cols
|
||||
// left join pk_info as pk
|
||||
// on pk.schema_name = cols.table_schema and pk_table = cols.table_name and pk_column = cols.column_name
|
||||
// where cols.table_schema not in ('information_schema', 'pg_catalog')
|
||||
// ), indexes_metadata as (
|
||||
// select array_to_string(array_agg(CONCAT('{"schema":"', schema_name,
|
||||
// '","table":"', table_name,
|
||||
// '","name":"', index_name,
|
||||
// '","column":"', replace(col_name :: text, '"', E'"'),
|
||||
// '","index_type":"', index_type,
|
||||
// '","cardinality":', cardinality,
|
||||
// ',"size":', index_size,
|
||||
// ',"unique":', is_unique,
|
||||
// ',"is_partial_index":', is_partial_index,
|
||||
// ',"direction":"', lower(direction),
|
||||
// '"}')), ',') as indexes_metadata
|
||||
// from indexes_cols x
|
||||
// ), tbls as (
|
||||
// select array_to_string(array_agg(CONCAT('{', '"schema":"', TABLE_SCHEMA, '",', '"table":"', TABLE_NAME, '",', '"rows":',
|
||||
// coalesce((select s.n_live_tup
|
||||
// from pg_stat_user_tables s
|
||||
// where tbls.TABLE_SCHEMA = s.schemaname and tbls.TABLE_NAME = s.relname),
|
||||
// 0), ', "type":"', TABLE_TYPE, '",', '"engine":"",', '"collation":""}')),
|
||||
// ',') as tbls_metadata
|
||||
// from information_schema.tables tbls
|
||||
// where tbls.TABLE_SCHEMA not in ('information_schema', 'pg_catalog')
|
||||
// ), config as (
|
||||
// select array_to_string(
|
||||
// array_agg(CONCAT('{"name":"', conf.name, '","value":"', replace(conf.setting, '"', E'"'), '"}')),
|
||||
// ',') as config_metadata
|
||||
// from pg_settings conf
|
||||
// ), views as
|
||||
// (
|
||||
// select array_to_string(array_agg(CONCAT('{"schema":"', views.schemaname, '","view_name":"', viewname, '"}')),
|
||||
// ',') as views_metadata
|
||||
// from pg_views views
|
||||
// where views.schemaname not in ('information_schema', 'pg_catalog')
|
||||
// )
|
||||
// select CONCAT('{ "fk_info": [', coalesce(fk_metadata, ''),
|
||||
// '], "columns": [', coalesce(cols_metadata, ''),
|
||||
// '], "indexes": [', coalesce(indexes_metadata, ''),
|
||||
// '], "tables":[', coalesce(tbls_metadata, ''),
|
||||
// '], "views":[', coalesce(views_metadata, ''),
|
||||
// '], "server_name": "', '', '", "version": "', '',
|
||||
// '"}') as " "
|
||||
// from cols,indexes_metadata, tbls, config, views, fk_info;
|
||||
// `;
|
||||
export const postgresQuery = minimizeQuery(rawPostgresQuery);
|
||||
|
||||
23
src/lib/databases.ts
Normal file
23
src/lib/databases.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import MysqlLogo from '@/assets/mysql_logo.png';
|
||||
import PostgresqlLogo from '@/assets/postgresql_logo.png';
|
||||
import MariaDBLogo from '@/assets/mariadb_logo.png';
|
||||
import SqliteLogo from '@/assets/sqlite_logo.png';
|
||||
import SqlServerLogo from '@/assets/sql_server_logo.png';
|
||||
import { DatabaseType } from './domain/database-type';
|
||||
|
||||
export const getDatabaseLogo = (databaseType: DatabaseType) => {
|
||||
switch (databaseType) {
|
||||
case DatabaseType.MYSQL:
|
||||
return MysqlLogo;
|
||||
case DatabaseType.POSTGRESQL:
|
||||
return PostgresqlLogo;
|
||||
case DatabaseType.MARIADB:
|
||||
return MariaDBLogo;
|
||||
case DatabaseType.SQLITE:
|
||||
return SqliteLogo;
|
||||
case DatabaseType.SQL_SERVER:
|
||||
return SqlServerLogo;
|
||||
default:
|
||||
return PostgresqlLogo;
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,8 @@
|
||||
export enum DatabaseType {
|
||||
GENERIC = 'generic',
|
||||
POSTGRES = 'postgres',
|
||||
POSTGRESQL = 'postgresql',
|
||||
MYSQL = 'mysql',
|
||||
SQL_SERVER = 'sql_server',
|
||||
MARIADB = 'mariadb',
|
||||
SQLITE = 'sqlite',
|
||||
}
|
||||
|
||||
11
src/lib/domain/diagram.ts
Normal file
11
src/lib/domain/diagram.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { DatabaseType } from './database-type';
|
||||
import { DBRelationship } from './db-relationship';
|
||||
import { DBTable } from './db-table';
|
||||
|
||||
export interface Diagram {
|
||||
id: string;
|
||||
name: string;
|
||||
databaseType: DatabaseType;
|
||||
tables: DBTable[];
|
||||
relationships: DBRelationship[];
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { type ClassValue, clsx } from 'clsx';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
|
||||
const randomId = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz-', 23);
|
||||
const randomId = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 25);
|
||||
const randonNumber = customAlphabet('1234567890', 18);
|
||||
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { TopNavbar } from './top-navbar/top-navbar';
|
||||
import { Toolbar } from './toolbar/toolbar';
|
||||
import {
|
||||
@@ -8,8 +8,19 @@ import {
|
||||
} from '@/components/resizable/resizable';
|
||||
import { SidePanel } from './side-panel/side-panel';
|
||||
import { Canvas } from './canvas/canvas';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useCreateDiagramDialog } from '@/hooks/use-create-diagram-dialog';
|
||||
|
||||
export const EditorPage: React.FC = () => {
|
||||
const { openCreateDiagramDialog } = useCreateDiagramDialog();
|
||||
const { diagramId } = useParams<{ diagramId: string }>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!diagramId) {
|
||||
openCreateDiagramDialog();
|
||||
}
|
||||
}, [diagramId, openCreateDiagramDialog]);
|
||||
|
||||
return (
|
||||
<section className="bg-background h-screen w-screen flex flex-col">
|
||||
<TopNavbar />
|
||||
|
||||
@@ -40,7 +40,9 @@ export const RelationshipListItemHeader: React.FC<
|
||||
const editRelationshipName = useCallback(() => {
|
||||
if (!editMode) return;
|
||||
if (relationshipName.trim()) {
|
||||
updateRelationship(relationship.id, { name: relationshipName });
|
||||
updateRelationship(relationship.id, {
|
||||
name: relationshipName.trim(),
|
||||
});
|
||||
}
|
||||
|
||||
setEditMode(false);
|
||||
|
||||
@@ -40,7 +40,7 @@ export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({
|
||||
const editTableName = useCallback(() => {
|
||||
if (!editMode) return;
|
||||
if (tableName.trim()) {
|
||||
updateTable(table.id, { name: tableName });
|
||||
updateTable(table.id, { name: tableName.trim() });
|
||||
}
|
||||
|
||||
setEditMode(false);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import {
|
||||
Menubar,
|
||||
MenubarCheckboxItem,
|
||||
MenubarContent,
|
||||
MenubarItem,
|
||||
MenubarMenu,
|
||||
@@ -13,10 +12,39 @@ import {
|
||||
MenubarTrigger,
|
||||
} from '@/components/menubar/menubar';
|
||||
import { Label } from '@/components/label/label';
|
||||
import { Button } from '@/components/button/button';
|
||||
import { Check, Pencil } from 'lucide-react';
|
||||
import { Input } from '@/components/input/input';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { useClickAway, useKeyPressEvent } from 'react-use';
|
||||
|
||||
export interface TopNavbarProps {}
|
||||
|
||||
export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
const { diagramName, updateDiagramName } = useChartDB();
|
||||
const [editMode, setEditMode] = React.useState(false);
|
||||
const [editedDiagramName, setEditedDiagramName] =
|
||||
React.useState(diagramName);
|
||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||
|
||||
const editDiagramName = useCallback(() => {
|
||||
if (!editMode) return;
|
||||
if (editedDiagramName.trim()) {
|
||||
updateDiagramName(editedDiagramName.trim());
|
||||
}
|
||||
|
||||
setEditMode(false);
|
||||
}, [editedDiagramName, updateDiagramName, editMode]);
|
||||
|
||||
useClickAway(inputRef, editDiagramName);
|
||||
useKeyPressEvent('Enter', editDiagramName);
|
||||
|
||||
const enterEditMode = (
|
||||
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
||||
) => {
|
||||
event.stopPropagation();
|
||||
setEditMode(true);
|
||||
};
|
||||
return (
|
||||
<nav className="flex flex-row items-center justify-between px-4">
|
||||
<div className="flex flex-1 justify-start gap-x-3">
|
||||
@@ -27,17 +55,27 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
<MenubarTrigger>File</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem>
|
||||
New Tab{' '}
|
||||
New
|
||||
<MenubarShortcut>⌘T</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem>
|
||||
New Window{' '}
|
||||
New Window
|
||||
<MenubarShortcut>⌘N</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem disabled>
|
||||
New Incognito Window
|
||||
</MenubarItem>
|
||||
<MenubarItem>Open</MenubarItem>
|
||||
<MenubarItem>Save</MenubarItem>
|
||||
<MenubarItem>Save as</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>Export</MenubarItem>
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>
|
||||
Export as
|
||||
</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
<MenubarItem>PNG</MenubarItem>
|
||||
<MenubarItem>JPG</MenubarItem>
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>Share</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
@@ -47,10 +85,7 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>
|
||||
Print...{' '}
|
||||
<MenubarShortcut>⌘P</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem>Exit</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
@@ -63,57 +98,63 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
Redo <MenubarShortcut>⇧⌘Z</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>Find</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
<MenubarItem>
|
||||
Search the web
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>Find...</MenubarItem>
|
||||
<MenubarItem>Find Next</MenubarItem>
|
||||
<MenubarItem>Find Previous</MenubarItem>
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
<MenubarItem>
|
||||
Find <MenubarShortcut>⌘F</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>Cut</MenubarItem>
|
||||
<MenubarItem>Copy</MenubarItem>
|
||||
<MenubarItem>Paste</MenubarItem>
|
||||
<MenubarItem>Clear</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>View</MenubarTrigger>
|
||||
<MenubarTrigger>Help</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarCheckboxItem>
|
||||
Always Show Bookmarks Bar
|
||||
</MenubarCheckboxItem>
|
||||
<MenubarCheckboxItem checked>
|
||||
Always Show Full URLs
|
||||
</MenubarCheckboxItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>
|
||||
Reload <MenubarShortcut>⌘R</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem disabled inset>
|
||||
Force Reload{' '}
|
||||
<MenubarShortcut>⇧⌘R</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>
|
||||
Toggle Fullscreen
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>Hide Sidebar</MenubarItem>
|
||||
<MenubarItem>Report a bug</MenubarItem>
|
||||
<MenubarItem>Visit ChartDB</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
</Menubar>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-1 justify-center">
|
||||
<Label>Diagrams/</Label>
|
||||
<Label contentEditable suppressContentEditableWarning>
|
||||
aaa
|
||||
</Label>
|
||||
<div className="flex flex-row flex-1 justify-center items-center group">
|
||||
<div className="flex">
|
||||
<Label>Diagrams/</Label>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
{editMode ? (
|
||||
<>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
autoFocus
|
||||
type="text"
|
||||
placeholder={diagramName}
|
||||
value={editedDiagramName}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onChange={(e) =>
|
||||
setEditedDiagramName(e.target.value)
|
||||
}
|
||||
className="h-7 focus-visible:ring-0"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="hover:bg-primary-foreground p-2 w-7 h-7 text-slate-500 hover:text-slate-700 hidden group-hover:flex"
|
||||
onClick={editDiagramName}
|
||||
>
|
||||
<Check />
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Label>{diagramName}</Label>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="hover:bg-primary-foreground p-2 w-7 h-7 text-slate-500 hover:text-slate-700 hidden group-hover:flex"
|
||||
onClick={enterEditMode}
|
||||
>
|
||||
<Pencil />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-1 justify-end"></div>
|
||||
</nav>
|
||||
|
||||
@@ -4,18 +4,24 @@ import { NotFoundPage } from './pages/not-found-page/not-found-page';
|
||||
import { EditorPage } from './pages/editor-page/editor-page';
|
||||
import { ChartDBProvider } from './context/chartdb-context/chartdb-provider';
|
||||
import { ReactFlowProvider } from '@xyflow/react';
|
||||
import { DataProvider } from './context/data-context/data-provider';
|
||||
import { CreateDiagramDialogProvider } from './dialogs/create-diagram-dialog/create-diagram-dialog-provider';
|
||||
|
||||
const routes: RouteObject[] = [
|
||||
{
|
||||
path: '/',
|
||||
...['', 'diagrams/:diagramId'].map((path) => ({
|
||||
path,
|
||||
element: (
|
||||
<ChartDBProvider>
|
||||
<ReactFlowProvider>
|
||||
<EditorPage />
|
||||
</ReactFlowProvider>
|
||||
</ChartDBProvider>
|
||||
<DataProvider>
|
||||
<ChartDBProvider>
|
||||
<CreateDiagramDialogProvider>
|
||||
<ReactFlowProvider>
|
||||
<EditorPage />
|
||||
</ReactFlowProvider>
|
||||
</CreateDiagramDialogProvider>
|
||||
</ChartDBProvider>
|
||||
</DataProvider>
|
||||
),
|
||||
},
|
||||
})),
|
||||
{
|
||||
path: '*',
|
||||
element: <NotFoundPage />,
|
||||
|
||||
Reference in New Issue
Block a user