From 54608a25f6b83cdb8dc0e25cc1b07a0c94ef3bdf Mon Sep 17 00:00:00 2001 From: Aniket Ladukar Date: Fri, 7 Jul 2023 16:06:51 -0700 Subject: [PATCH 01/65] #1408 added draggable components for array fields --- package-lock.json | 231 ++++++++++++++ package.json | 2 + src/assets/scss/_array-field.scss | 112 +++++++ src/components/input-switch/array-field.tsx | 290 +++++++++++++----- src/components/input-switch/input-switch.tsx | 2 + src/components/input-switch/numeric-field.tsx | 4 +- src/utils/input-utils.ts | 20 +- src/utils/recordedit-utils.ts | 4 +- 8 files changed, 575 insertions(+), 90 deletions(-) create mode 100644 src/assets/scss/_array-field.scss diff --git a/package-lock.json b/package-lock.json index 7a6824765..1cdbbe334 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@types/bootstrap": "^5.1.9", "@types/q": "^1.5.5", "@types/react": "^18.0.0", + "@types/react-beautiful-dnd": "^13.1.4", "@types/react-dom": "^18.0.0", "axios": "^0.24.0", "babel-loader": "^8.2.2", @@ -31,6 +32,7 @@ "plotly.js-basic-dist-min": "^2.18.1", "q": "^1.5.1", "react": "^18.2.0", + "react-beautiful-dnd": "^13.1.1", "react-bootstrap": "^2.5.0", "react-colorful": "^5.6.1", "react-dom": "^18.2.0", @@ -2124,6 +2126,15 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -2173,6 +2184,14 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-beautiful-dnd": { + "version": "13.1.4", + "resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.4.tgz", + "integrity": "sha512-4bIBdzOr0aavN+88q3C7Pgz+xkb7tz3whORYrmSj77wfVEMfiWiooIwVWFR7KM2e+uGTe5BVrXqSfb0aHeflJA==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-dom": { "version": "18.0.6", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", @@ -2181,6 +2200,17 @@ "@types/react": "*" } }, + "node_modules/@types/react-redux": { + "version": "7.1.25", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.25.tgz", + "integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz", @@ -3647,6 +3677,14 @@ "node": ">= 8" } }, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, "node_modules/css-element-queries": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/css-element-queries/-/css-element-queries-1.2.3.tgz", @@ -6033,6 +6071,14 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/hsluv": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/hsluv/-/hsluv-0.0.3.tgz", @@ -7180,6 +7226,11 @@ "node": ">=6" } }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -8614,6 +8665,11 @@ "performance-now": "^2.1.0" } }, + "node_modules/raf-schd": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -8633,6 +8689,24 @@ "node": ">=0.10.0" } }, + "node_modules/react-beautiful-dnd": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", + "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==", + "dependencies": { + "@babel/runtime": "^7.9.2", + "css-box-model": "^1.2.0", + "memoize-one": "^5.1.1", + "raf-schd": "^4.0.2", + "react-redux": "^7.2.0", + "redux": "^4.0.4", + "use-memo-one": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.5 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-bootstrap": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.5.0.tgz", @@ -8750,6 +8824,35 @@ "react": ">0.13.0" } }, + "node_modules/react-redux": { + "version": "7.2.9", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", + "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "dependencies": { + "@babel/runtime": "^7.15.4", + "@types/react-redux": "^7.1.20", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + }, + "peerDependencies": { + "react": "^16.8.3 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -8801,6 +8904,14 @@ "node": ">= 0.10" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -9941,6 +10052,11 @@ "xtend": "~4.0.1" } }, + "node_modules/tiny-invariant": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" + }, "node_modules/tinycolor2": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", @@ -10304,6 +10420,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-memo-one": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", @@ -12604,6 +12728,15 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -12653,6 +12786,14 @@ "csstype": "^3.0.2" } }, + "@types/react-beautiful-dnd": { + "version": "13.1.4", + "resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.4.tgz", + "integrity": "sha512-4bIBdzOr0aavN+88q3C7Pgz+xkb7tz3whORYrmSj77wfVEMfiWiooIwVWFR7KM2e+uGTe5BVrXqSfb0aHeflJA==", + "requires": { + "@types/react": "*" + } + }, "@types/react-dom": { "version": "18.0.6", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", @@ -12661,6 +12802,17 @@ "@types/react": "*" } }, + "@types/react-redux": { + "version": "7.1.25", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.25.tgz", + "integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==", + "requires": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, "@types/react-transition-group": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz", @@ -13799,6 +13951,14 @@ "which": "^2.0.1" } }, + "css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "requires": { + "tiny-invariant": "^1.0.6" + } + }, "css-element-queries": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/css-element-queries/-/css-element-queries-1.2.3.tgz", @@ -15725,6 +15885,14 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, "hsluv": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/hsluv/-/hsluv-0.0.3.tgz", @@ -16569,6 +16737,11 @@ "p-is-promise": "^2.0.0" } }, + "memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -17668,6 +17841,11 @@ "performance-now": "^2.1.0" } }, + "raf-schd": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -17684,6 +17862,20 @@ "loose-envify": "^1.1.0" } }, + "react-beautiful-dnd": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", + "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==", + "requires": { + "@babel/runtime": "^7.9.2", + "css-box-model": "^1.2.0", + "memoize-one": "^5.1.1", + "raf-schd": "^4.0.2", + "react-redux": "^7.2.0", + "redux": "^4.0.4", + "use-memo-one": "^1.1.1" + } + }, "react-bootstrap": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.5.0.tgz", @@ -17759,6 +17951,26 @@ "prop-types": "^15.7.2" } }, + "react-redux": { + "version": "7.2.9", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", + "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "requires": { + "@babel/runtime": "^7.15.4", + "@types/react-redux": "^7.1.20", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + }, + "dependencies": { + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + } + } + }, "react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -17800,6 +18012,14 @@ "resolve": "^1.9.0" } }, + "redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "requires": { + "@babel/runtime": "^7.9.2" + } + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -18693,6 +18913,11 @@ "xtend": "~4.0.1" } }, + "tiny-invariant": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" + }, "tinycolor2": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", @@ -18989,6 +19214,12 @@ "punycode": "^2.1.0" } }, + "use-memo-one": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", + "requires": {} + }, "util": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", diff --git a/package.json b/package.json index d79b1b82c..41ae8c262 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@types/bootstrap": "^5.1.9", "@types/q": "^1.5.5", "@types/react": "^18.0.0", + "@types/react-beautiful-dnd": "^13.1.4", "@types/react-dom": "^18.0.0", "axios": "^0.24.0", "babel-loader": "^8.2.2", @@ -69,6 +70,7 @@ "plotly.js-basic-dist-min": "^2.18.1", "q": "^1.5.1", "react": "^18.2.0", + "react-beautiful-dnd": "^13.1.1", "react-bootstrap": "^2.5.0", "react-colorful": "^5.6.1", "react-dom": "^18.2.0", diff --git a/src/assets/scss/_array-field.scss b/src/assets/scss/_array-field.scss new file mode 100644 index 000000000..9cf5496b4 --- /dev/null +++ b/src/assets/scss/_array-field.scss @@ -0,0 +1,112 @@ +.entity-value { + container-type: inline-size; + container-name: row-container; + .fieldInputContainer { + ul { + padding-left: 0.15rem; + } + .input-items { + width: 100%; + margin-top: 2px; + margin-bottom: 2px; + display: flex; + flex-direction: column; + gap: 7px; + & *:focus { + outline: 1px dotted gray; + } + li { + list-style: none; + } + .item { + border-radius: 3px; + min-height: 30px; + height: auto; + width: 100%; + padding-top: 5px; + padding-bottom: 5px; + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 5px; + box-shadow: rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 1px 3px 1px; + .move-icon { + display: flex; + padding-left: 3px; + & * { + position: relative; + top: 50%; + transform: translateY(-7px); + } + } + .input-box, + .input-switch { + border: 2px solid transparent; + box-shadow: rgba(27, 31, 35, 0.04) 0px 1px 0px, rgba(255, 255, 255, 0.25) 0px 1px 0px inset; + &:focus { + outline: none; + } + } + button:not(.chaise-btn.chaise-btn-primary) { + background-color: transparent; + height: 100%; + width: auto; + padding: 1px 2px; + font-size: small; + } + .action-buttons { + height: 100%; + & * { + margin-right: 5px; + font-size: medium; + } + } + [class*="input-switch-container"] { + flex: 1 1 1px; + } + [class$="array_date"] { + flex: 1 1 1px; + } + [class$="array_timestamp"] { + flex: 1 1 1px; + } + } + } + .timestamp { + position: relative; + padding-bottom: 30px !important; + .move-icon * { + top: 70% !important; + } + .chaise-btn-group { + position: absolute; + } + .action-buttons { + position: absolute; + right: -3px; + top: 35px + } + } + } +} + +@container row-container (max-width:250px) { + .fieldInputContainer { + .input-items { + .item { + .action-buttons { + margin-right: 1px; + } + .action-buttons button { + margin-right: -4px !important; + margin-left: 1px; + } + } + .timestamp { + .action-buttons { + right: 4px; + } + } + } + } +} diff --git a/src/components/input-switch/array-field.tsx b/src/components/input-switch/array-field.tsx index b3a695dcc..78084f1c8 100644 --- a/src/components/input-switch/array-field.tsx +++ b/src/components/input-switch/array-field.tsx @@ -1,101 +1,239 @@ +import '@isrd-isi-edu/chaise/src/assets/scss/_array-field.scss'; + // components -import ClearInputBtn from '@isrd-isi-edu/chaise/src/components/clear-input-btn'; -import InputField, { InputFieldProps } from '@isrd-isi-edu/chaise/src/components/input-switch/input-field'; +import { InputFieldProps } from '@isrd-isi-edu/chaise/src/components/input-switch/input-field'; // utils -import { getSimpleColumnType } from '@isrd-isi-edu/chaise/src/utils/input-utils'; import { dataFormats } from '@isrd-isi-edu/chaise/src/utils/constants'; -import { arrayFieldPlaceholder } from '@isrd-isi-edu/chaise/src/utils/input-utils'; -import { windowRef } from '@isrd-isi-edu/chaise/src/utils/window-ref'; +import { formatDatetime, formatFloat, formatInt } from '@isrd-isi-edu/chaise/src/utils/input-utils'; +import { useEffect, useState } from 'react'; +import { DragDropContext, Draggable, DraggableProvided, Droppable, DroppableProvided } from 'react-beautiful-dnd'; +import { useFormContext } from 'react-hook-form'; +import InputSwitch from './input-switch'; type ArrayFieldProps = InputFieldProps & { /* the type of each element in the array */ baseArrayType: string, }; -const ArrayField = (props : ArrayFieldProps): JSX.Element => { - const arrayFieldValidation = (value: string) => { - if (!value) return; +const ArrayField = (props: ArrayFieldProps): JSX.Element => { + const [itemList, setItemList] = useState<{ id: number, value: string }[]>([]) + let [counter, setCounter] = useState(0); + const { disableInput, name, baseArrayType } = props; + const { getValues, setValue, watch } = useFormContext(); + + + useEffect(() => { + + let defaultValues = getValues(name); + + defaultValues = typeof defaultValues == 'object' || !defaultValues ? defaultValues : JSON.parse(defaultValues) + + if (defaultValues && defaultValues.length > 0) {// Populate default Values if present + + setItemList( + defaultValues.map((defVal: string | number, idx: number) => { + + if (baseArrayType === 'timestamp') { + const v = formatDatetime(defVal, { outputMomentFormat: dataFormats.timestamp }) + + setValue(`${name}-row-${idx}-date`, v?.date); + setValue(`${name}-row-${idx}-time`, v?.time); + } + setValue(`${name}-row-${idx}`, defVal) - // make sure it's an array - let validArray = false, arr; - try { - arr = JSON.parse(value); - validArray = Array.isArray(arr); - } catch (e) {} + return { + id: idx, + value: defVal + } + }) + ) + setCounter(defaultValues.length) - if (!validArray) { - return 'Please enter a valid array structure' + ((props.baseArrayType === 'text') ? ' e.g. [\"value1\", \"value2\"]' : '.'); + } else { // create a row with empty value if no default values exist + + setItemList([ + { + id: 0, + value: '' + } + ]) + setCounter(1); } + }, []) - const moment = windowRef.moment; - // validate individual array values - for (let i = 0; i < arr.length; i++) { - let isValid = false; - const val = arr[i]; - - // null is a valid value for any type - if (val === null) continue; - - switch (props.baseArrayType) { - case 'timestamptz': - case 'timestamp': - isValid = moment(val, moment.ISO_8601, true).isValid(); - break; - case 'date': - isValid = moment(val, ['YYYY-MM-DD', 'YYYY-M-DD', 'YYYY-M-D', 'YYYY-MM-D'], true).isValid(); - break; - case 'numeric': - case 'float4': - case 'float8': - isValid = dataFormats.regexp.float.test(val); - break; - case 'int2': - case 'int4': - case 'int8': - isValid = dataFormats.regexp.integer.test(val); - break; - case 'boolean': - isValid = (typeof val === 'boolean'); - break; - default: - isValid = (typeof val === 'string' || val instanceof String); - break; - } - if (!isValid) { - return '`' + val + '` is not a valid ' + getSimpleColumnType(props.baseArrayType) + ' value.'; + useEffect(() => { + + // Update the array field in the react-hook-form context + if (itemList.length) { + setValue(name, itemList.map(item => formatValue(item.value)).filter(value => value?.toString().trim())) + } + }, [itemList]) + + useEffect(() => { + + const sub = watch((data, options) => { + + // Update component state as per the changes observed in the individual row values in the form context + if (options.name?.startsWith(`${name}-row`)) { + + const itemId = parseInt(options.name.split('-')[3]) + onTextEdit(itemId, data[`${name}-row-${itemId}`]) } + }) + + return () => sub.unsubscribe(); + }, [watch]) + + const generateId = () => { + const curr = counter; + setCounter(prev => prev + 1); + return curr; + } + + const formatValue = (value: string) => { + switch (baseArrayType) { + case 'int4': + return formatInt(value); + case 'float4': + return formatFloat(value); + default: + return value; } + } + + /*** + * Adds a new row at a specified index with a given value. + * @param index - index at the which new row needs to be created + * @param value [optional] specify the placeholder value at the newly created row. Empty if no value provided + */ + const addItem = (index: number, value?: number[] | string[]) => () => { + const elementId = generateId(); + index = typeof index == "number" && index > -1 ? index + 1 : itemList.length + + setItemList([...itemList.slice(0, index), + { + id: elementId, + value: '' + }, + ...itemList.slice(index, itemList.length) + ]) + + setValue( + `${name}-row-${elementId}`, + value ? value : '', + { shouldValidate: true, shouldDirty: true, shouldTouch: true } + ) - return true; - }; + } - const placeholder = props.placeholder ? props.placeholder : arrayFieldPlaceholder(props.baseArrayType); + /** + * Delete row with a specific ID + * @param itemId id of row to be deleted + */ + const deleteItemWithId = (itemId: number) => { + setItemList([...itemList.filter((item: any) => item.id !== itemId)]) + } + + /** + * Updates the state value of a given row + * @param id specify the row id + * @param fieldValue new value for the row + */ + const onTextEdit = (id: number, fieldValue: any) => { + setItemList((prev: any) => { + return [...prev.map((el: any) => { + if (el.id === id) { + el.value = fieldValue; + return el; + } + return el; + }) + ] + }) + } + + const handleOnDragEnd = (result: any) => { + const items = Array.from(itemList); + + if (!result.destination) { + const [reorderedItem] = items.splice(result.source.index, 1); + items.splice(itemList.length - 1, 0, reorderedItem); + } else { + const [reorderedItem] = items.splice(result.source.index, 1); + items.splice(result.destination.index, 0, reorderedItem); + } + + setItemList(items); + } + + const draggableItemRenderer = (item: any, index: number, disableInput: boolean) => { + + return <> + + { + (provided: DraggableProvided) => ( + <> + +
  • +
    + +
    + + + +
    + { + !disableInput && +
    + + +
    + } +
    +
  • + + ) + } +
    + + } return ( - - {(field, onChange, showClear, clearInput) => ( -
    -
    -