diff --git a/.eslintignore b/.eslintignore index 50389a17..c5fec113 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,3 @@ node_modules/ public/ build/ -src/app-components/panel-group \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..2fcf15e4 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,33 @@ +# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions +# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +name: CI Build User Interface; Deploy to Environment TEST + +on: + push: + branches: + - test +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [16.x] + steps: + - uses: actions/checkout@v2 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: npm run build + + - name: Deploy to Test + env: + AWS_DEFAULT_REGION: ${{ secrets.CWBICI_TEST_AWS_REGION }} + AWS_ACCESS_KEY_ID: ${{ secrets.CWBICI_TEST_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.CWBICI_TEST_AWS_SECRET_ACCESS_KEY }} + run: npm run deploy-test diff --git a/.gitignore b/.gitignore index b3d1e63f..8228b3e7 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ # testing /coverage .eslintcache -rollup-analyze.json +rollup-analyze.* # production /build diff --git a/package-lock.json b/package-lock.json index 2422b18a..1046b8f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,9 @@ "@ag-grid-community/styles": "^30.0.3", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@iconify-json/mdi": "^1.1.64", + "@iconify/react": "^4.1.1", + "@iconify/utils": "^2.1.22", "@mui/icons-material": "^5.14.0", "@mui/material": "^5.14.2", "@tanstack/react-table": "^8.9.3", @@ -40,13 +43,13 @@ "react-dom": "^18.2.0", "react-papaparse": "^4.1.0", "react-plotly.js": "^2.6.0", + "react-resizable-panels": "^2.0.7", "react-select": "^5.7.4", "react-toastify": "^9.1.3", "react-tooltip": "^5.18.0", "react-use": "^17.4.0", "redux-bundler": "^28.0.3", "redux-bundler-react": "^1.2.0", - "source-map-explorer": "^2.5.3", "stream": "^0.0.2", "stream-browserify": "^3.0.0", "url": "^0.11.1", @@ -68,6 +71,7 @@ "prettier": "^3.0.0", "rollup-plugin-visualizer": "^5.9.2", "sass": "^1.63.6", + "source-map-explorer": "^2.5.3", "vite": "^4.5.2", "vite-plugin-checker": "^0.6.1", "vite-plugin-svgr": "^3.2.0" @@ -142,6 +146,26 @@ "node": ">=6.0.0" } }, + "node_modules/@antfu/install-pkg": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.1.1.tgz", + "integrity": "sha512-LyB/8+bSfa0DFGC06zpCEfs89/XoWZwws5ygEa5D+Xsm3OfI+aXQ86VgVG7Acyef+rSZ5HE7J8rrxzrQeM3PjQ==", + "dependencies": { + "execa": "^5.1.1", + "find-up": "^5.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.7.tgz", + "integrity": "sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@babel/code-frame": { "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", @@ -1387,6 +1411,47 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@iconify-json/mdi": { + "version": "1.1.64", + "resolved": "https://registry.npmjs.org/@iconify-json/mdi/-/mdi-1.1.64.tgz", + "integrity": "sha512-zGeo5TjhNFAY6FmSDBLAzDO811t77r6v/mDi7CAL9w5eXqKez6bIjk8R9AL/RHIeq44ALP4Ozr4lMqFTkHr7ug==", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/react": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@iconify/react/-/react-4.1.1.tgz", + "integrity": "sha512-jed14EjvKjee8mc0eoscGxlg7mSQRkwQG3iX3cPBCO7UlOjz0DtlvTqxqEcHUJGh+z1VJ31Yhu5B9PxfO0zbdg==", + "dependencies": { + "@iconify/types": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/cyberalien" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==" + }, + "node_modules/@iconify/utils": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.1.22.tgz", + "integrity": "sha512-6UHVzTVXmvO8uS6xFF+L/QTSpTzA/JZxtgU+KYGFyDYMEObZ1bu/b5l+zNJjHy+0leWjHI+C0pXlzGvv3oXZMA==", + "dependencies": { + "@antfu/install-pkg": "^0.1.1", + "@antfu/utils": "^0.7.5", + "@iconify/types": "^2.0.0", + "debug": "^4.3.4", + "kolorist": "^1.8.0", + "local-pkg": "^0.5.0", + "mlly": "^1.5.0" + } + }, "node_modules/@icons/material": { "version": "0.2.4", "license": "MIT", @@ -2347,10 +2412,9 @@ "license": "MIT" }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true, + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "bin": { "acorn": "bin/acorn" }, @@ -2538,6 +2602,7 @@ }, "node_modules/async": { "version": "3.2.4", + "dev": true, "license": "MIT" }, "node_modules/available-typed-arrays": { @@ -2566,6 +2631,7 @@ }, "node_modules/balanced-match": { "version": "1.0.2", + "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -2637,6 +2703,7 @@ }, "node_modules/brace-expansion": { "version": "1.1.11", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2687,6 +2754,7 @@ }, "node_modules/btoa": { "version": "1.2.1", + "dev": true, "license": "(MIT OR Apache-2.0)", "bin": { "btoa": "bin/btoa.js" @@ -2857,6 +2925,7 @@ }, "node_modules/cliui": { "version": "7.0.4", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -2941,6 +3010,7 @@ }, "node_modules/concat-map": { "version": "0.0.1", + "dev": true, "license": "MIT" }, "node_modules/concat-stream": { @@ -3208,7 +3278,6 @@ }, "node_modules/debug": { "version": "4.3.4", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.1.2" @@ -3482,6 +3551,7 @@ }, "node_modules/duplexer": { "version": "0.1.2", + "dev": true, "license": "MIT" }, "node_modules/duplexify": { @@ -3500,6 +3570,7 @@ }, "node_modules/ejs": { "version": "3.1.8", + "dev": true, "license": "Apache-2.0", "dependencies": { "jake": "^10.8.5" @@ -3734,6 +3805,7 @@ }, "node_modules/escalade": { "version": "3.1.1", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3741,6 +3813,7 @@ }, "node_modules/escape-html": { "version": "1.0.3", + "dev": true, "license": "MIT" }, "node_modules/escape-string-regexp": { @@ -4129,7 +4202,6 @@ }, "node_modules/execa": { "version": "5.1.1", - "dev": true, "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", @@ -4284,6 +4356,7 @@ }, "node_modules/filelist": { "version": "1.0.4", + "dev": true, "license": "Apache-2.0", "dependencies": { "minimatch": "^5.0.1" @@ -4291,6 +4364,7 @@ }, "node_modules/filelist/node_modules/brace-expansion": { "version": "2.0.1", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4298,6 +4372,7 @@ }, "node_modules/filelist/node_modules/minimatch": { "version": "5.1.0", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -4323,7 +4398,6 @@ }, "node_modules/find-up": { "version": "5.0.0", - "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -4391,6 +4465,7 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", + "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -4475,6 +4550,7 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", + "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -4567,6 +4643,7 @@ }, "node_modules/glob": { "version": "7.2.3", + "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -4796,6 +4873,7 @@ }, "node_modules/gzip-size": { "version": "6.0.0", + "dev": true, "license": "MIT", "dependencies": { "duplexer": "^0.1.2" @@ -4895,7 +4973,6 @@ }, "node_modules/human-signals": { "version": "2.1.0", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.17.0" @@ -4985,6 +5062,7 @@ }, "node_modules/inflight": { "version": "1.0.6", + "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -5113,6 +5191,7 @@ }, "node_modules/is-docker": { "version": "2.2.1", + "dev": true, "license": "MIT", "bin": { "is-docker": "cli.js" @@ -5151,6 +5230,7 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5323,7 +5403,6 @@ }, "node_modules/is-stream": { "version": "2.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5414,6 +5493,7 @@ }, "node_modules/is-wsl": { "version": "2.2.0", + "dev": true, "license": "MIT", "dependencies": { "is-docker": "^2.0.0" @@ -5432,6 +5512,7 @@ }, "node_modules/jake": { "version": "10.8.5", + "dev": true, "license": "Apache-2.0", "dependencies": { "async": "^3.2.3", @@ -5652,6 +5733,11 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==" + }, "node_modules/jsonfile": { "version": "6.1.0", "dev": true, @@ -5679,6 +5765,11 @@ "version": "3.0.0", "license": "ISC" }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==" + }, "node_modules/lerc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz", @@ -5700,9 +5791,23 @@ "version": "1.2.4", "license": "MIT" }, + "node_modules/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "6.0.0", - "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -5839,7 +5944,6 @@ }, "node_modules/merge-stream": { "version": "2.0.0", - "dev": true, "license": "MIT" }, "node_modules/merge2": { @@ -5867,7 +5971,6 @@ }, "node_modules/mimic-fn": { "version": "2.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5888,6 +5991,7 @@ }, "node_modules/minimatch": { "version": "3.1.2", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -5905,6 +6009,7 @@ }, "node_modules/mkdirp": { "version": "0.5.6", + "dev": true, "license": "MIT", "dependencies": { "minimist": "^1.2.6" @@ -5913,6 +6018,17 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mlly": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.5.0.tgz", + "integrity": "sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==", + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.0.3", + "ufo": "^1.3.2" + } + }, "node_modules/money-clip": { "version": "3.0.5", "license": "MIT", @@ -6076,7 +6192,6 @@ }, "node_modules/npm-run-path": { "version": "4.0.1", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.0.0" @@ -6238,7 +6353,6 @@ }, "node_modules/onetime": { "version": "5.1.2", - "dev": true, "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -6286,7 +6400,6 @@ }, "node_modules/p-limit": { "version": "3.1.0", - "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -6300,7 +6413,6 @@ }, "node_modules/p-locate": { "version": "5.0.0", - "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -6372,7 +6484,6 @@ }, "node_modules/path-exists": { "version": "4.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6380,6 +6491,7 @@ }, "node_modules/path-is-absolute": { "version": "1.0.1", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6403,6 +6515,11 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==" + }, "node_modules/pbf": { "version": "3.2.1", "license": "BSD-3-Clause", @@ -6437,6 +6554,16 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, "node_modules/plotly.js": { "version": "2.25.2", "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-2.25.2.tgz", @@ -6808,6 +6935,18 @@ "react-dom": "^16.8.0 || ^17 || ^18" } }, + "node_modules/react-resizable-panels": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.0.7.tgz", + "integrity": "sha512-+gD2FLfGmoP95DbAV6VgoGxvlGxRWrxd54EnIlTgSvA+t53Pvbe6UOMz4yTz/8cxi3JvbpHlgbAiTfTofKp+wA==", + "dependencies": { + "stacking-order": "^1" + }, + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-select": { "version": "5.7.4", "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.4.tgz", @@ -7057,6 +7196,7 @@ }, "node_modules/require-directory": { "version": "2.1.1", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7140,9 +7280,9 @@ } }, "node_modules/rollup-plugin-visualizer": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.9.2.tgz", - "integrity": "sha512-waHktD5mlWrYFrhOLbti4YgQCn1uR24nYsNuXxg7LkPH8KdTXVWR9DNY1WU0QqokyMixVXJS4J04HNrVTMP01A==", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.12.0.tgz", + "integrity": "sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==", "dev": true, "dependencies": { "open": "^8.4.0", @@ -7157,7 +7297,7 @@ "node": ">=14" }, "peerDependencies": { - "rollup": "2.x || 3.x" + "rollup": "2.x || 3.x || 4.x" }, "peerDependenciesMeta": { "rollup": { @@ -7385,7 +7525,6 @@ }, "node_modules/signal-exit": { "version": "3.0.7", - "dev": true, "license": "ISC" }, "node_modules/signum": { @@ -7429,6 +7568,7 @@ }, "node_modules/source-map": { "version": "0.7.4", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">= 8" @@ -7436,7 +7576,9 @@ }, "node_modules/source-map-explorer": { "version": "2.5.3", - "license": "Apache-2.0", + "resolved": "https://registry.npmjs.org/source-map-explorer/-/source-map-explorer-2.5.3.tgz", + "integrity": "sha512-qfUGs7UHsOBE5p/lGfQdaAj/5U/GWYBw2imEpD6UQNkqElYonkow8t+HBL1qqIl3CuGZx7n8/CQo4x1HwSHhsg==", + "dev": true, "dependencies": { "btoa": "^1.2.1", "chalk": "^4.1.0", @@ -7461,6 +7603,7 @@ }, "node_modules/source-map-explorer/node_modules/open": { "version": "7.4.2", + "dev": true, "license": "MIT", "dependencies": { "is-docker": "^2.0.0", @@ -7541,6 +7684,11 @@ "version": "1.3.4", "license": "MIT" }, + "node_modules/stacking-order": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stacking-order/-/stacking-order-1.0.1.tgz", + "integrity": "sha512-IxD2jw+DDiQ3FaXr6QcvSt5uE2vDWBYcC7bbCWfI5R9kEoj8Zgi4rFwPXALOjTtE96iIdZjcJFLQVr7r7xIdAA==" + }, "node_modules/stacktrace-gps": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz", @@ -7721,6 +7869,7 @@ }, "node_modules/string-width": { "version": "4.2.3", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -7733,6 +7882,7 @@ }, "node_modules/string-width/node_modules/emoji-regex": { "version": "8.0.0", + "dev": true, "license": "MIT" }, "node_modules/string.prototype.matchall": { @@ -7781,6 +7931,7 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -7791,7 +7942,6 @@ }, "node_modules/strip-final-newline": { "version": "2.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -7913,6 +8063,7 @@ }, "node_modules/temp": { "version": "0.9.4", + "dev": true, "license": "MIT", "dependencies": { "mkdirp": "^0.5.1", @@ -7924,6 +8075,7 @@ }, "node_modules/temp/node_modules/rimraf": { "version": "2.6.3", + "dev": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -8139,6 +8291,11 @@ "node": ">=4.2.0" } }, + "node_modules/ufo": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz", + "integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==" + }, "node_modules/unbox-primitive": { "version": "1.0.2", "dev": true, @@ -8883,6 +9040,7 @@ }, "node_modules/wrap-ansi": { "version": "7.0.0", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -8924,6 +9082,7 @@ }, "node_modules/y18n": { "version": "5.0.8", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -8944,6 +9103,7 @@ }, "node_modules/yargs": { "version": "16.2.0", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^7.0.2", @@ -8960,6 +9120,7 @@ }, "node_modules/yargs-parser": { "version": "20.2.9", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -8967,7 +9128,6 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -9033,6 +9193,20 @@ } } }, + "@antfu/install-pkg": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.1.1.tgz", + "integrity": "sha512-LyB/8+bSfa0DFGC06zpCEfs89/XoWZwws5ygEa5D+Xsm3OfI+aXQ86VgVG7Acyef+rSZ5HE7J8rrxzrQeM3PjQ==", + "requires": { + "execa": "^5.1.1", + "find-up": "^5.0.0" + } + }, + "@antfu/utils": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.7.tgz", + "integrity": "sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==" + }, "@babel/code-frame": { "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", @@ -9849,6 +10023,41 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@iconify-json/mdi": { + "version": "1.1.64", + "resolved": "https://registry.npmjs.org/@iconify-json/mdi/-/mdi-1.1.64.tgz", + "integrity": "sha512-zGeo5TjhNFAY6FmSDBLAzDO811t77r6v/mDi7CAL9w5eXqKez6bIjk8R9AL/RHIeq44ALP4Ozr4lMqFTkHr7ug==", + "requires": { + "@iconify/types": "*" + } + }, + "@iconify/react": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@iconify/react/-/react-4.1.1.tgz", + "integrity": "sha512-jed14EjvKjee8mc0eoscGxlg7mSQRkwQG3iX3cPBCO7UlOjz0DtlvTqxqEcHUJGh+z1VJ31Yhu5B9PxfO0zbdg==", + "requires": { + "@iconify/types": "^2.0.0" + } + }, + "@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==" + }, + "@iconify/utils": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.1.22.tgz", + "integrity": "sha512-6UHVzTVXmvO8uS6xFF+L/QTSpTzA/JZxtgU+KYGFyDYMEObZ1bu/b5l+zNJjHy+0leWjHI+C0pXlzGvv3oXZMA==", + "requires": { + "@antfu/install-pkg": "^0.1.1", + "@antfu/utils": "^0.7.5", + "@iconify/types": "^2.0.0", + "debug": "^4.3.4", + "kolorist": "^1.8.0", + "local-pkg": "^0.5.0", + "mlly": "^1.5.0" + } + }, "@icons/material": { "version": "0.2.4", "requires": {} @@ -10480,10 +10689,9 @@ "version": "0.1.1" }, "acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==" }, "acorn-jsx": { "version": "5.3.2", @@ -10604,7 +10812,8 @@ } }, "async": { - "version": "3.2.4" + "version": "3.2.4", + "dev": true }, "available-typed-arrays": { "version": "1.0.5" @@ -10620,7 +10829,8 @@ } }, "balanced-match": { - "version": "1.0.2" + "version": "1.0.2", + "dev": true }, "base64-js": { "version": "1.5.1" @@ -10662,6 +10872,7 @@ }, "brace-expansion": { "version": "1.1.11", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -10686,7 +10897,8 @@ } }, "btoa": { - "version": "1.2.1" + "version": "1.2.1", + "dev": true }, "buffer": { "version": "6.0.3", @@ -10774,6 +10986,7 @@ }, "cliui": { "version": "7.0.4", + "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -10841,7 +11054,8 @@ "version": "2.20.3" }, "concat-map": { - "version": "0.0.1" + "version": "0.0.1", + "dev": true }, "concat-stream": { "version": "1.6.2", @@ -11041,7 +11255,6 @@ }, "debug": { "version": "4.3.4", - "dev": true, "requires": { "ms": "2.1.2" } @@ -11214,7 +11427,8 @@ "version": "1.0.0" }, "duplexer": { - "version": "0.1.2" + "version": "0.1.2", + "dev": true }, "duplexify": { "version": "3.7.1", @@ -11230,6 +11444,7 @@ }, "ejs": { "version": "3.1.8", + "dev": true, "requires": { "jake": "^10.8.5" } @@ -11410,10 +11625,12 @@ } }, "escalade": { - "version": "3.1.1" + "version": "3.1.1", + "dev": true }, "escape-html": { - "version": "1.0.3" + "version": "1.0.3", + "dev": true }, "escape-string-regexp": { "version": "4.0.0" @@ -11677,7 +11894,6 @@ }, "execa": { "version": "5.1.1", - "dev": true, "requires": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -11798,18 +12014,21 @@ }, "filelist": { "version": "1.0.4", + "dev": true, "requires": { "minimatch": "^5.0.1" }, "dependencies": { "brace-expansion": { "version": "2.0.1", + "dev": true, "requires": { "balanced-match": "^1.0.0" } }, "minimatch": { "version": "5.1.0", + "dev": true, "requires": { "brace-expansion": "^2.0.1" } @@ -11829,7 +12048,6 @@ }, "find-up": { "version": "5.0.0", - "dev": true, "requires": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -11879,7 +12097,8 @@ } }, "fs.realpath": { - "version": "1.0.0" + "version": "1.0.0", + "dev": true }, "fsevents": { "version": "2.3.2", @@ -11931,7 +12150,8 @@ } }, "get-caller-file": { - "version": "2.0.5" + "version": "2.0.5", + "dev": true }, "get-canvas-context": { "version": "1.0.2" @@ -11997,6 +12217,7 @@ }, "glob": { "version": "7.2.3", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -12183,6 +12404,7 @@ }, "gzip-size": { "version": "6.0.0", + "dev": true, "requires": { "duplexer": "^0.1.2" } @@ -12241,8 +12463,7 @@ "version": "0.0.3" }, "human-signals": { - "version": "2.1.0", - "dev": true + "version": "2.1.0" }, "hyphenate-style-name": { "version": "1.0.4", @@ -12288,6 +12509,7 @@ }, "inflight": { "version": "1.0.6", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -12366,7 +12588,8 @@ } }, "is-docker": { - "version": "2.2.1" + "version": "2.2.1", + "dev": true }, "is-extglob": { "version": "2.1.1", @@ -12379,7 +12602,8 @@ "version": "1.0.3" }, "is-fullwidth-code-point": { - "version": "3.0.0" + "version": "3.0.0", + "dev": true }, "is-function": { "version": "1.0.2" @@ -12466,8 +12690,7 @@ } }, "is-stream": { - "version": "2.0.1", - "dev": true + "version": "2.0.1" }, "is-string": { "version": "1.0.7", @@ -12516,6 +12739,7 @@ }, "is-wsl": { "version": "2.2.0", + "dev": true, "requires": { "is-docker": "^2.0.0" } @@ -12528,6 +12752,7 @@ }, "jake": { "version": "10.8.5", + "dev": true, "requires": { "async": "^3.2.3", "chalk": "^4.0.2", @@ -12670,6 +12895,11 @@ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, + "jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==" + }, "jsonfile": { "version": "6.1.0", "dev": true, @@ -12689,6 +12919,11 @@ "kdbush": { "version": "3.0.0" }, + "kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==" + }, "lerc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz", @@ -12705,9 +12940,17 @@ "lines-and-columns": { "version": "1.2.4" }, + "local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "requires": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + } + }, "locate-path": { "version": "6.0.0", - "dev": true, "requires": { "p-locate": "^5.0.0" } @@ -12807,8 +13050,7 @@ "version": "6.0.0" }, "merge-stream": { - "version": "2.0.0", - "dev": true + "version": "2.0.0" }, "merge2": { "version": "1.4.1", @@ -12825,8 +13067,7 @@ } }, "mimic-fn": { - "version": "2.1.0", - "dev": true + "version": "2.1.0" }, "min-document": { "version": "2.19.0", @@ -12839,6 +13080,7 @@ }, "minimatch": { "version": "3.1.2", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -12848,10 +13090,22 @@ }, "mkdirp": { "version": "0.5.6", + "dev": true, "requires": { "minimist": "^1.2.6" } }, + "mlly": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.5.0.tgz", + "integrity": "sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==", + "requires": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.0.3", + "ufo": "^1.3.2" + } + }, "money-clip": { "version": "3.0.5", "requires": { @@ -12973,7 +13227,6 @@ }, "npm-run-path": { "version": "4.0.1", - "dev": true, "requires": { "path-key": "^3.0.0" } @@ -13074,7 +13327,6 @@ }, "onetime": { "version": "5.1.2", - "dev": true, "requires": { "mimic-fn": "^2.1.0" } @@ -13106,14 +13358,12 @@ }, "p-limit": { "version": "3.1.0", - "dev": true, "requires": { "yocto-queue": "^0.1.0" } }, "p-locate": { "version": "5.0.0", - "dev": true, "requires": { "p-limit": "^3.0.2" } @@ -13160,11 +13410,11 @@ "version": "1.0.1" }, "path-exists": { - "version": "4.0.0", - "dev": true + "version": "4.0.0" }, "path-is-absolute": { - "version": "1.0.1" + "version": "1.0.1", + "dev": true }, "path-key": { "version": "3.1.1" @@ -13175,6 +13425,11 @@ "path-type": { "version": "4.0.0" }, + "pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==" + }, "pbf": { "version": "3.2.1", "requires": { @@ -13195,6 +13450,16 @@ "picomatch": { "version": "2.3.1" }, + "pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "requires": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, "plotly.js": { "version": "2.25.2", "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-2.25.2.tgz", @@ -13442,6 +13707,14 @@ "warning": "^4.0.2" } }, + "react-resizable-panels": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.0.7.tgz", + "integrity": "sha512-+gD2FLfGmoP95DbAV6VgoGxvlGxRWrxd54EnIlTgSvA+t53Pvbe6UOMz4yTz/8cxi3JvbpHlgbAiTfTofKp+wA==", + "requires": { + "stacking-order": "^1" + } + }, "react-select": { "version": "5.7.4", "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.4.tgz", @@ -13639,7 +13912,8 @@ } }, "require-directory": { - "version": "2.1.1" + "version": "2.1.1", + "dev": true }, "resize-observer-polyfill": { "version": "1.5.1", @@ -13687,9 +13961,9 @@ } }, "rollup-plugin-visualizer": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.9.2.tgz", - "integrity": "sha512-waHktD5mlWrYFrhOLbti4YgQCn1uR24nYsNuXxg7LkPH8KdTXVWR9DNY1WU0QqokyMixVXJS4J04HNrVTMP01A==", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.12.0.tgz", + "integrity": "sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==", "dev": true, "requires": { "open": "^8.4.0", @@ -13834,8 +14108,7 @@ } }, "signal-exit": { - "version": "3.0.7", - "dev": true + "version": "3.0.7" }, "signum": { "version": "1.0.0" @@ -13863,10 +14136,14 @@ } }, "source-map": { - "version": "0.7.4" + "version": "0.7.4", + "dev": true }, "source-map-explorer": { "version": "2.5.3", + "resolved": "https://registry.npmjs.org/source-map-explorer/-/source-map-explorer-2.5.3.tgz", + "integrity": "sha512-qfUGs7UHsOBE5p/lGfQdaAj/5U/GWYBw2imEpD6UQNkqElYonkow8t+HBL1qqIl3CuGZx7n8/CQo4x1HwSHhsg==", + "dev": true, "requires": { "btoa": "^1.2.1", "chalk": "^4.1.0", @@ -13884,6 +14161,7 @@ "dependencies": { "open": { "version": "7.4.2", + "dev": true, "requires": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" @@ -13941,6 +14219,11 @@ "stackframe": { "version": "1.3.4" }, + "stacking-order": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stacking-order/-/stacking-order-1.0.1.tgz", + "integrity": "sha512-IxD2jw+DDiQ3FaXr6QcvSt5uE2vDWBYcC7bbCWfI5R9kEoj8Zgi4rFwPXALOjTtE96iIdZjcJFLQVr7r7xIdAA==" + }, "stacktrace-gps": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz", @@ -14081,6 +14364,7 @@ }, "string-width": { "version": "4.2.3", + "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -14088,7 +14372,8 @@ }, "dependencies": { "emoji-regex": { - "version": "8.0.0" + "version": "8.0.0", + "dev": true } } }, @@ -14126,13 +14411,13 @@ }, "strip-ansi": { "version": "6.0.1", + "dev": true, "requires": { "ansi-regex": "^5.0.1" } }, "strip-final-newline": { - "version": "2.0.0", - "dev": true + "version": "2.0.0" }, "strip-indent": { "version": "3.0.0", @@ -14216,6 +14501,7 @@ }, "temp": { "version": "0.9.4", + "dev": true, "requires": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" @@ -14223,6 +14509,7 @@ "dependencies": { "rimraf": { "version": "2.6.3", + "dev": true, "requires": { "glob": "^7.1.3" } @@ -14369,6 +14656,11 @@ "optional": true, "peer": true }, + "ufo": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz", + "integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==" + }, "unbox-primitive": { "version": "1.0.2", "dev": true, @@ -14807,6 +15099,7 @@ }, "wrap-ansi": { "version": "7.0.0", + "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -14834,7 +15127,8 @@ "version": "4.0.2" }, "y18n": { - "version": "5.0.8" + "version": "5.0.8", + "dev": true }, "yallist": { "version": "4.0.0", @@ -14847,6 +15141,7 @@ }, "yargs": { "version": "16.2.0", + "dev": true, "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -14858,11 +15153,11 @@ } }, "yargs-parser": { - "version": "20.2.9" + "version": "20.2.9", + "dev": true }, "yocto-queue": { - "version": "0.1.0", - "dev": true + "version": "0.1.0" } } } diff --git a/package.json b/package.json index eef645fb..2722a42c 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,9 @@ "@ag-grid-community/styles": "^30.0.3", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@iconify-json/mdi": "^1.1.64", + "@iconify/react": "^4.1.1", + "@iconify/utils": "^2.1.22", "@mui/icons-material": "^5.14.0", "@mui/material": "^5.14.2", "@tanstack/react-table": "^8.9.3", @@ -35,13 +38,13 @@ "react-dom": "^18.2.0", "react-papaparse": "^4.1.0", "react-plotly.js": "^2.6.0", + "react-resizable-panels": "^2.0.7", "react-select": "^5.7.4", "react-toastify": "^9.1.3", "react-tooltip": "^5.18.0", "react-use": "^17.4.0", "redux-bundler": "^28.0.3", "redux-bundler-react": "^1.2.0", - "source-map-explorer": "^2.5.3", "stream": "^0.0.2", "stream-browserify": "^3.0.0", "url": "^0.11.1", @@ -63,6 +66,7 @@ "prettier": "^3.0.0", "rollup-plugin-visualizer": "^5.9.2", "sass": "^1.63.6", + "source-map-explorer": "^2.5.3", "vite": "^4.5.2", "vite-plugin-checker": "^0.6.1", "vite-plugin-svgr": "^3.2.0" @@ -75,7 +79,7 @@ "serve-local-build": "npx serve -s build", "preview": "vite preview", "pretty": "prettier --write \"./**/*.{js,jsx,json,html,scss,css}\"", - "analyze": "./scripts/analyze.sh", + "analyze": "source-map-explorer 'build/assets/*.js'", "lint": "eslint . --ext .js,.jsx", "lint: fix": "eslint . --ext .js,.jsx --fix", "clean-install": "rm -rf node_modules/ && rm -rf package-lock.json && npm i", diff --git a/src/app-bundles/chart-editor-bundle.js b/src/app-bundles/chart-editor-bundle.js index 29266108..91496f5a 100644 --- a/src/app-bundles/chart-editor-bundle.js +++ b/src/app-bundles/chart-editor-bundle.js @@ -243,7 +243,6 @@ const chartEditorBundle = { }); store.doExploreDataLoad(idsToLoad, beforeString, afterString); - store.doInclinometerDataLoad(idsToLoad, beforeString, afterString); }, selectChartEditorShowSettings: (state) => state.chartEditor.showSettings, diff --git a/src/app-bundles/domains-bundle.js b/src/app-bundles/domains-bundle.js index 1f961302..8d8a876e 100644 --- a/src/app-bundles/domains-bundle.js +++ b/src/app-bundles/domains-bundle.js @@ -13,7 +13,7 @@ export default createRestBundle({ postTemplate: '/domains', deleteTemplate: '/domains/{:item.id}', fetchActions: ['URL_UPDATED', 'AUTH_LOGGED_IN'], - forceFetchActions: [], + forceFetchActions: ['EXPLOREMAP_INITIALIZE_START'], addons: { selectDomainsItemsByGroup: createSelector('selectDomainsItems', (items) => { if (!items) return null; diff --git a/src/app-bundles/explore-data-bundle.js b/src/app-bundles/explore-data-bundle.js index 935b5a1f..45fe8062 100644 --- a/src/app-bundles/explore-data-bundle.js +++ b/src/app-bundles/explore-data-bundle.js @@ -9,12 +9,23 @@ const exploreDataBundle = { const initialData = { data: [], inclinometers: [], + filters: { + status: [], + type: [], + }, + _isLoading: false, }; return (state = initialData, { type, payload }) => { switch (type) { case 'EXPLORE_DATA_CLEAR': - return Object.assign({}, initialData); + case 'URL_UPDATED': + return { ...initialData }; + case 'EXPLORE_DATA_LOADING': + return { + ...state, + _isLoading: payload, + }; case 'EXPLORE_DATA_LOAD': return { ...state, @@ -25,8 +36,11 @@ const exploreDataBundle = { ...state, inclinometers: payload, }; - case 'URL_UPDATED': - return Object.assign({}, initialData); + case 'EXPLORE_DATA_UPDATE_FILTERS_START': + return { + ...state, + filters: payload, + }; default: return state; } @@ -34,18 +48,26 @@ const exploreDataBundle = { }, doExploreDataClear: () => ({ dispatch }) => { - dispatch({ - type: 'EXPLORE_DATA_CLEAR', - }); + dispatch({ type: 'EXPLORE_DATA_CLEAR' }); }, - doExploreDataLoad: (instrumentIds, before, after) => ({ dispatch, store }) => { + doSetExploreDataFilters: (filters) => ({ dispatch, store }) => { + const isInstrumentLayerAdded = store.selectExploreMapDataHasAdded(); + + if (isInstrumentLayerAdded) { + dispatch({ type: 'EXPLORE_DATA_UPDATE_FILTERS_START', payload: filters }); + dispatch({ type: 'EXPLORE_DATA_UPDATE_FILTERS_END' }); + } + }, + + doExploreDataLoad: (instrumentIds, before, after) => async ({ dispatch, store }) => { store.doExploreDataClear(); + dispatch({ type: 'EXPLORE_DATA_LOADING', payload: true }); const apiRoot = store.selectApiRoot(); const token = store.selectAuthTokenRaw(); - fetch(`${apiRoot}/explorer?before=${before}&after=${after}`, { + await fetch(`${apiRoot}/explorer?before=${before}&after=${after}`, { method: 'POST', mode: 'cors', headers: { @@ -61,13 +83,8 @@ const exploreDataBundle = { payload: data, }); }); - }, - - doInclinometerDataLoad: (instrumentIds, before, after) => ({ dispatch, store }) => { - const apiRoot = store.selectApiRoot(); - const token = store.selectAuthTokenRaw(); - fetch(`${apiRoot}/inclinometer_explorer?before=${before}&after=${after}`, { + await fetch(`${apiRoot}/inclinometer_explorer?before=${before}&after=${after}`, { method: 'POST', mode: 'cors', headers: { @@ -83,9 +100,13 @@ const exploreDataBundle = { payload: data, }); }); + + dispatch({ type: 'EXPLORE_DATA_LOADING', payload: false }); }, selectExploreData: (state) => state.exploreData, + selectExploreDataLoading: state => state.exploreData._isLoading, + selectExploreDataFilters: state => state.exploreData.filters, selectExploreDataByInstrumentId: createSelector( 'selectExploreData', diff --git a/src/app-bundles/explore-map-bundle.js b/src/app-bundles/explore-map-bundle.jsx similarity index 56% rename from src/app-bundles/explore-map-bundle.js rename to src/app-bundles/explore-map-bundle.jsx index fea61397..7ab9344b 100644 --- a/src/app-bundles/explore-map-bundle.js +++ b/src/app-bundles/explore-map-bundle.jsx @@ -1,24 +1,9 @@ -import Layer from 'ol/layer/Vector'; -import Source from 'ol/source/Vector'; import GeoJSON from 'ol/format/GeoJSON'; -import Style from 'ol/style/Style'; -import Text from 'ol/style/Text'; -import Fill from 'ol/style/Fill'; -import Stroke from 'ol/style/Stroke'; -import Circle from 'ol/geom/Circle'; +import { createNewExplorerLayer, defaultLayer } from './map-helpers'; const geoJSON = new GeoJSON(); - const ignoreActions = ['APP_IDLE']; -const statusColors = { - active: '#43ac6a', - inactive: 'grey', - abandoned: 'grey', - destroyed: '#f04124', - lost: '#d08002', -}; - const exploreMapBundle = { name: 'exploreMap', @@ -31,6 +16,9 @@ const exploreMapBundle = { _mapLoaded: false, _instrumentsLoaded: false, _groupsLoaded: false, + _domainsLoaded: false, + _filterUpdated: false, + _hasAddedData: false, }; return (state = initialData, { type, payload }) => { @@ -41,28 +29,39 @@ const exploreMapBundle = { }; switch (type) { case 'INSTRUMENTS_FETCH_FINISHED': - return Object.assign({}, state, { + return { + ...state, _instrumentsLoaded: true, - }); + }; case 'INSTRUMENTGROUPS_FETCH_FINISHED': - return Object.assign({}, state, { + return { + ...state, _groupsLoaded: true, - }); + }; + case 'DOMAINS_FETCH_FINISHED': + return { + ...state, + _domainsLoaded: true, + }; + case 'EXPLORE_DATA_UPDATE_FILTERS_END': + return { + ...state, + _filterUpdated: true, + }; case 'MAPS_INITIALIZED': if (Object.prototype.hasOwnProperty.call(payload, initialData._mapKey)) { - return Object.assign({}, state, { + return { + ...state, _mapLoaded: true, - }); + }; } else { return state; } case 'MAPS_SHUTDOWN': if (Object.prototype.hasOwnProperty.call(payload, initialData._mapKey)) { - return Object.assign({}, state, { - _mapLoaded: false, - _instrumentsLoaded: false, - _groupsLoaded: false, - }); + return { + ...initialData + }; } else { return state; } @@ -70,7 +69,10 @@ const exploreMapBundle = { case 'EXPLOREMAP_INITIALIZE_FINISH': case 'EXPLOREMAP_ADD_DATA_START': case 'EXPLOREMAP_ADD_DATA_FINISH': - return Object.assign({}, state, payload); + return { + ...state, + ...payload, + } default: return state; } @@ -85,40 +87,10 @@ const exploreMapBundle = { }, }); - const lyr = new Layer({ - source: new Source(), - declutter: false, - style: (f, r) => - new Style({ - geometry: new Circle(f.getGeometry().getCoordinates(), 5 * r), - fill: new Fill({ - color: '#ffffff', - }), - stroke: new Stroke({ - color: statusColors[f.getProperties()['status']], - width: 3, - }), - text: new Text({ - fill: new Fill({ - color: '#000000', - }), - font: '16px sans-serif', - offsetX: 12, - offsetY: -12, - padding: [2, 2, 2, 2], - stroke: new Stroke({ - color: '#ffffff', - width: 2, - }), - text: f.get('name'), - textAlign: 'left', - }), - }), - }); dispatch({ type: 'EXPLOREMAP_INITIALIZE_FINISH', payload: { - layer: lyr, + layer: defaultLayer, }, }); }, @@ -128,38 +100,54 @@ const exploreMapBundle = { type: 'EXPLOREMAP_ADD_DATA_START', payload: { _mapLoaded: false, + _filterUpdated: false, }, }); + + const domains = store.selectDomainsItemsByGroup(); const mapKey = store.selectExploreMapKey(); + const map = store.selectMapsObject()[mapKey]; const geoProjection = store.selectMapsGeoProjection(); const webProjection = store.selectMapsWebProjection(); - const map = store.selectMapsObject()[mapKey]; - const lyr = store.selectExploreMapLayer(); - const src = lyr.getSource(); + + // Remove old layers + const oldLyr = store.selectExploreMapLayer(); + const src = oldLyr.getSource(); + map.removeLayer(oldLyr); + map.removeSource + src.clear(); + + //add new layers + const newLyr = createNewExplorerLayer(domains); + const newSrc = newLyr.getSource(); const data = store.selectInstrumentsItemsGeoJSON(); - map.removeLayer(lyr); - src.clear(); const features = geoJSON.readFeatures(data, { featureProjection: webProjection, dataProjection: geoProjection, }); - src.addFeatures(features); - map.addLayer(lyr); + map.addLayer(newLyr); + newSrc.addFeatures(features); + const view = map.getView(); if (features && features.length) { - view.fit(src.getExtent(), { + view.fit(newSrc.getExtent(), { padding: [50, 50, 50, 50], maxZoom: 16, }); } + dispatch({ type: 'EXPLOREMAP_ADD_DATA_FINISH', + payload: { + layer: newLyr, + _hasAddedData: true, + } }); }, - selectExploreMapKey: (state) => state.exploreMap._mapKey, - - selectExploreMapLayer: (state) => state.exploreMap.layer, + selectExploreMapKey: state => state.exploreMap._mapKey, + selectExploreMapLayer: state => state.exploreMap.layer, + selectExploreMapDataHasAdded: state => state.exploreMap._hasAddedData, reactExploreMapShouldInitialize: (state) => { if (state.exploreMap._shouldInitialize) @@ -168,11 +156,14 @@ const exploreMapBundle = { reactExploreMapShouldAddData: (state) => { if ( - state.exploreMap._instrumentsLoaded && - state.exploreMap._groupsLoaded && - state.exploreMap._mapLoaded - ) - return { actionCreator: 'doExploreMapAddData' }; + state.exploreMap._filterUpdated || + ( + state.exploreMap._instrumentsLoaded && + state.exploreMap._groupsLoaded && + state.exploreMap._mapLoaded && + state.exploreMap._domainsLoaded + ) + ) return { actionCreator: 'doExploreMapAddData' }; }, }; diff --git a/src/app-bundles/explore-map-interaction-bundle.js b/src/app-bundles/explore-map-interaction-bundle.js index bc43bec0..2a6b158c 100644 --- a/src/app-bundles/explore-map-interaction-bundle.js +++ b/src/app-bundles/explore-map-interaction-bundle.js @@ -2,6 +2,7 @@ import Select from 'ol/interaction/Select'; import DragBox from 'ol/interaction/DragBox'; import { defaults } from 'ol/interaction'; import debounce from 'lodash.debounce'; +import { createIconStyle } from './map-helpers'; const exploreMapInteractionBundle = { name: 'exploreMapInteractions', @@ -21,14 +22,17 @@ const exploreMapInteractionBundle = { case 'EXPLOREMAPINTERACTIONS_RESET_START': case 'EXPLOREMAPINTERACTIONS_SELECT_UPDATED': return Object.assign({}, state, payload); - case 'MAPS_INITIALIZED': - if (Object.prototype.hasOwnProperty.call(payload, 'exploreMap')) { - return Object.assign({}, state, { - _shouldInitialize: true, - }); - } else { - return state; - } + case 'EXPLOREMAP_ADD_DATA_START': + return { + ...state, + select: null, + dragBox: null, + }; + case 'EXPLOREMAP_ADD_DATA_FINISH': + return { + ...state, + _shouldInitialize: true, + }; default: return state; } @@ -43,12 +47,29 @@ const exploreMapInteractionBundle = { }, }); + const domains = store.selectDomainsItemsByGroup(); + const instrumentTypes = domains['instrument_type'] || {}; + + const version = store.selectExploreMapInteractionsVersion(); const mapKey = store.selectExploreMapKey(); const map = store.selectMapsObject()[mapKey]; - const select = new Select(); + const blue = '#0000FF'; + + const select = new Select({ + style: (feature, _r) => createIconStyle({ + feature, + instrumentTypes, + imageOpts: { + status: 'selected', + }, + textOpts: { + color: blue, + } + }) + }); const handleSelectionChange = debounce( - store.doExploreMapInteractionsIncrementVersion, + () => store.doExploreMapInteractionsIncrementVersion(), 200 ); const collection = select.getFeatures(); @@ -57,7 +78,7 @@ const exploreMapInteractionBundle = { map.addInteraction(select); const dragBox = new DragBox(); - dragBox.on('boxend', store.doExploreMapInteractionsSelectByArea); + dragBox.on('boxend', () => store.doExploreMapInteractionsSelectByArea()); dragBox.on('boxstart', () => { select.getFeatures().clear(); }); @@ -67,6 +88,7 @@ const exploreMapInteractionBundle = { payload: { select, dragBox, + version: version + 1, }, }); }, @@ -118,7 +140,7 @@ const exploreMapInteractionBundle = { } }, - doExploreMapInteractionsSelectByArea: (_e) => ({ store }) => { + doExploreMapInteractionsSelectByArea: () => ({ store }) => { /** * Based on openlayers example from https://openlayers.org/en/latest/examples/box-selection.html */ @@ -142,7 +164,7 @@ const exploreMapInteractionBundle = { const oblique = rotation % (Math.PI / 2) !== 0; const candidateFeatures = oblique ? [] : selectedFeatures; const extent = dragBox.getGeometry().getExtent(); - src.forEachFeatureIntersectingExtent(extent, function (feature) { + src.forEachFeatureIntersectingExtent(extent, (feature) => { candidateFeatures.push(feature); }); @@ -156,7 +178,7 @@ const exploreMapInteractionBundle = { const geometry = dragBox.getGeometry().clone(); geometry.rotate(-rotation, anchor); const extent$1 = geometry.getExtent(); - candidateFeatures.forEach(function (feature) { + candidateFeatures.forEach((feature) => { const geometry = feature.getGeometry().clone(); geometry.rotate(-rotation, anchor); if (geometry.intersectsExtent(extent$1)) { @@ -166,8 +188,6 @@ const exploreMapInteractionBundle = { } }, - selectExploreMapInteractionsVersion: (state) => state.exploreMapInteractions.version, - selectExploreMapSelectedInstruments: (state) => { const select = state.exploreMapInteractions.select; if (select) { @@ -180,9 +200,9 @@ const exploreMapInteractionBundle = { } }, - selectExploreMapInteractionsSelect: (state) => state.exploreMapInteractions.select, - - selectExploreMapInteractionsDragBox: (state) => state.exploreMapInteractions.dragBox, + selectExploreMapInteractionsSelect: state => state.exploreMapInteractions.select, + selectExploreMapInteractionsDragBox: state => state.exploreMapInteractions.dragBox, + selectExploreMapInteractionsVersion: state => state.exploreMapInteractions.version, reactExploreMapInteractionsShouldInitialize: (state) => { if (state.exploreMapInteractions._shouldInitialize) diff --git a/src/app-bundles/inclinometer-measurements.js b/src/app-bundles/inclinometer-measurements.js index d41b1167..72e95b01 100644 --- a/src/app-bundles/inclinometer-measurements.js +++ b/src/app-bundles/inclinometer-measurements.js @@ -7,8 +7,7 @@ export default createRestBundle({ persist: false, routeParam: '', - // TODO: default before and after time periods should be implemented on - // the backend. + // TODO: default before and after time periods should be implemented on the backend. getTemplate: '/timeseries/:timeseriesId/inclinometer_measurements?before=2025-01-01T00:00:00.00Z&after=1900-01-01T00:00:00.00Z', putTemplate: '', postTemplate: '/timeseries/:timeseriesId/inclinometer_measurements', diff --git a/src/app-bundles/instrument-bundle.js b/src/app-bundles/instrument-bundle.js index 8bc65700..525cdec5 100644 --- a/src/app-bundles/instrument-bundle.js +++ b/src/app-bundles/instrument-bundle.js @@ -138,18 +138,30 @@ export default createRestBundle({ selectInstrumentsItemsGeoJSON: createSelector( 'selectInstrumentsItems', - (items) => ({ - type: 'FeatureCollection', - features: items.map((item) => { - const feature = { - type: 'Feature', - geometry: { ...item.geometry }, - properties: { ...item }, - }; - delete feature.properties.geometry; - return feature; - }), - }) + 'selectExploreDataFilters', + (items, filters) => { + return ({ + type: 'FeatureCollection', + features: items.map((item) => { + const { geometry = {}, ...rest } = item || {}; + const { type_id, status } = rest || {}; + const { status: statusFilters, type: typeFilters } = filters || {}; + + const feature = { + type: 'Feature', + geometry: { ...geometry }, + properties: { ...rest }, + }; + + const inStatusFilter = statusFilters.length ? statusFilters.includes(status) : true; + const inTypeFilter = typeFilters.length ? typeFilters.includes(type_id) : true; + + return (inStatusFilter && inTypeFilter) + ? feature + : null; + }).filter(e => e), + }) + } ), selectInstrumentsByRouteGeoJSON: createSelector( diff --git a/src/app-bundles/map-helpers/index.js b/src/app-bundles/map-helpers/index.js new file mode 100644 index 00000000..14ccae6b --- /dev/null +++ b/src/app-bundles/map-helpers/index.js @@ -0,0 +1,95 @@ +import { asString } from 'ol/color'; +import Fill from 'ol/style/Fill'; +import Icon from 'ol/style/Icon'; +import Layer from 'ol/layer/Vector'; +import Source from 'ol/source/Vector'; +import Stroke from 'ol/style/Stroke'; +import Style from 'ol/style/Style'; +import Text from 'ol/style/Text'; + +import { icons } from '@iconify-json/mdi'; +import { getIconData, iconToSVG, iconToHTML, replaceIDs } from '@iconify/utils'; + +export const STATUS_COLORS = { + active: [67, 172, 106, 1], + lost: [208, 128, 2, 1], + inactive: [136, 136, 136, 1], + abandoned: [136, 136, 136, 1], + destroyed: [136, 136, 136, 1], + selected: [21, 71, 158, 1], +}; + +export const defaultLayer = new Layer({ + source: new Source(), + declutter: false, +}); + +export const genIconSourceSvg = (typeId, types, color) => { + const { description: icon } = types ? types.find(el => el.id === typeId) : {}; + + // Get content for icon + const iconData = getIconData(icons, icon); + if (!iconData) { + // eslint-disable-next-line no-console + console.log(`test: "${icon}" is missing.`); + return ''; + } + + const renderData = iconToSVG(iconData, { + height: '18px', + width: '18px', + }); + + const svg = iconToHTML(replaceIDs(renderData.body), { + ...renderData.attributes, + color: asString(color), + }); + + return svg; +}; + +export const createIconStyle = ({ feature, instrumentTypes, imageOpts = {}, textOpts = {}, styleOpts = {} }) => { + const { status, ...rest } = imageOpts; + + const color = STATUS_COLORS[status || feature.getProperties()['status']]; + + return new Style({ + image: new Icon({ + opacity: 1, + color: color, + src: `data:image/svg+xml;utf8,${genIconSourceSvg(feature.getProperties()['type_id'], instrumentTypes, color)}`, + ...rest, + }), + text: new Text({ + fill: new Fill({ + color: status === 'selected' ? '#15479e' : '#000000', + }), + font: '14px sans-serif', + offsetX: 12, + offsetY: -12, + padding: [2, 2, 2, 2], + stroke: new Stroke({ + color: '#ffffff', + width: 2, + }), + text: feature.getProperties()['name'], + textAlign: 'left', + ...textOpts, + }), + ...styleOpts, + }) +}; + +export const createNewExplorerLayer = (domains) => { + const instrumentTypes = domains['instrument_type'] || []; + + if (!instrumentTypes?.length) return defaultLayer; + + const lyr = new Layer({ + source: new Source(), + declutter: false, + style: (feature, _r) => createIconStyle({ feature, instrumentTypes }), + }); + + return lyr; +}; \ No newline at end of file diff --git a/src/app-bundles/maps-bundle.js b/src/app-bundles/maps-bundle.js index e257e991..f4a2c8ad 100644 --- a/src/app-bundles/maps-bundle.js +++ b/src/app-bundles/maps-bundle.js @@ -1,11 +1,9 @@ /* eslint-disable no-mixed-operators */ import { createSelector } from 'redux-bundler'; + import olMap from 'ol/Map.js'; import View from 'ol/View'; - import ScaleBar from 'ol/control/ScaleLine'; -// import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'; -// import { OSM, Vector as VectorSource } from 'ol/source'; import BasemapPicker from '../ol-controls/basemap-picker'; import 'ol/ol.css'; @@ -25,10 +23,20 @@ const MapsBundle = { }; return (state = initialData, { type, payload }) => { + const mapKeys = Object.keys(payload || {}); + const clone = { ...state }; + switch (type) { case actions.MAPS_INITIALIZED: - case actions.MAPS_SHUTDOWN: - return Object.assign({}, state, payload); + return { + ...state, + ...payload, + } + case actions.MAPS_SHUTDOWN: + if (mapKeys?.length) { + mapKeys.forEach(key => delete clone[key]); + return clone; + } else return state; default: return state; } diff --git a/src/app-components/chart/container.jsx b/src/app-components/chart/container.jsx index 264f9378..88437614 100644 --- a/src/app-components/chart/container.jsx +++ b/src/app-components/chart/container.jsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react'; import DatePicker from 'react-datepicker'; import { connect } from 'redux-bundler-react'; +import { Icon } from '@iconify/react'; import { subDays, differenceInDays, isSameDay } from 'date-fns'; import { Settings as SettingsIcon } from '@mui/icons-material'; @@ -21,6 +22,7 @@ export default connect( 'selectChartEditorLayout', 'selectChartEditorCorrelationMinDate', 'selectChartEditorCorrelationMaxDate', + 'selectExploreDataLoading', ({ doChartEditorSetShowSettings, doChartEditorSetLayout, @@ -31,6 +33,7 @@ export default connect( chartEditorLayout: layout, chartEditorCorrelationMinDate: minDate, chartEditorCorrelationMaxDate: maxDate, + exploreDataLoading, }) => { const [from, setFrom] = useState(minDate); const [to, setTo] = useState(maxDate); @@ -163,10 +166,22 @@ export default connect( {showSettings ? ( - ) : chartType === 'timeseries' ? ( - ) : ( - + <> + {exploreDataLoading ? ( +
+ +
+ ) : ( + <> + {chartType === 'timeseries' ? ( + + ) : ( + + )} + + )} + )} ); diff --git a/src/app-components/classMap.jsx b/src/app-components/classMap.jsx index 74e19fe6..dea9608f 100644 --- a/src/app-components/classMap.jsx +++ b/src/app-components/classMap.jsx @@ -8,8 +8,12 @@ const Map = forwardRef(({ mapsObject, mapKey, options, + isRelative = false, }, ref) => { const el = useRef(null); + const styles = isRelative + ? { position: 'relative', top: 0, left: 0, width: '100%', height: '100%' } + : { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }; const updateSize = debounce(() => { if (mapsObject[mapKey]) mapsObject[mapKey].updateSize(); @@ -44,12 +48,7 @@ const Map = forwardRef(({ }; }, [doMapsShutdown, mapKey]); - return ( -
- ); + return
; }); export default Map; diff --git a/src/app-components/panel-group/Divider.jsx b/src/app-components/panel-group/Divider.jsx deleted file mode 100644 index 1762d5e3..00000000 --- a/src/app-components/panel-group/Divider.jsx +++ /dev/null @@ -1,181 +0,0 @@ -import React from 'react'; - -export default class Divider extends React.Component { - static defaultProps = { - dividerWidth: 1, - handleBleed: 4, - direction: undefined, - showHandles: false, - borderColor: undefined, - onResizeStart: undefined, - onResizeEnd: undefined - }; - - constructor(...args) { - super(...args); - - this.state = { - dragging: false, - initPos: { x: null, y: null } - }; - } - - // Add/remove event listeners based on drag state - componentDidUpdate(props, state) { - if (this.state.dragging && !state.dragging) { - document.addEventListener('mousemove', this.onMouseMove); - document.addEventListener('touchmove', this.onTouchMove, { - passive: false - }); - document.addEventListener('mouseup', this.handleDragEnd); - document.addEventListener('touchend', this.handleDragEnd, { - passive: false - }); - // maybe move it to setState callback ? - this.props.onResizeStart(); - } else if (!this.state.dragging && state.dragging) { - document.removeEventListener('mousemove', this.onMouseMove); - document.removeEventListener('touchmove', this.onTouchMove, { - passive: false - }); - document.removeEventListener('mouseup', this.handleDragEnd); - document.removeEventListener('touchend', this.handleDragEnd, { - passive: false - }); - this.props.onResizeEnd(); - } - } - - // Start drag state and set initial position - handleDragStart = (e, x, y) => { - this.setState({ - dragging: true, - initPos: { - x, - y - } - }); - - e.stopPropagation(); - e.preventDefault(); - }; - - // End drag state - handleDragEnd = (e) => { - this.setState({ dragging: false }); - e.stopPropagation(); - e.preventDefault(); - }; - - // Call resize handler if we're dragging - handleDragMove = (e, x, y) => { - if (!this.state.dragging) return; - - const initDelta = { - x: x - this.state.initPos.x, - y: y - this.state.initPos.y - }; - - const flowMask = { - x: this.props.direction === 'row' ? 1 : 0, - y: this.props.direction === 'column' ? 1 : 0 - }; - - const flowDelta = initDelta.x * flowMask.x + initDelta.y * flowMask.y; - - // Resize the panels - const resultDelta = this.handleResize(this.props.panelID, initDelta); - - // if the divider moved, reset the initPos - if (resultDelta + flowDelta !== 0) { - // Did we move the expected amount? (snapping will result in a larger delta) - const expectedDelta = resultDelta === flowDelta; - - this.setState({ - initPos: { - // if we moved more than expected, add the difference to the Position - x: x + (expectedDelta ? 0 : resultDelta * flowMask.x), - y: y + (expectedDelta ? 0 : resultDelta * flowMask.y) - } - }); - } - - e.stopPropagation(); - e.preventDefault(); - }; - - // Call resize on mouse events - // Event onMosueDown - onMouseDown = (e) => { - // only left mouse button - if (e.button !== 0) return; - this.handleDragStart(e, e.pageX, e.pageY); - }; - // Event onMouseMove - onMouseMove = (e) => { - this.handleDragMove(e, e.pageX, e.pageY); - }; - - // Call resize on Touch events (mobile) - // Event ontouchstart - onTouchStart = (e) => { - this.handleDragStart(e, e.touches[0].clientX, e.touches[0].clientY); - }; - - // Event ontouchmove - onTouchMove = (e) => { - this.handleDragMove(e, e.touches[0].clientX, e.touches[0].clientY); - }; - - // Handle resizing - handleResize = (i, delta) => this.props.handleResize(i, delta); - - // Utility functions for handle size provided how much bleed - // we want outside of the actual divider div - getHandleWidth = () => this.props.dividerWidth + this.props.handleBleed * 2; - getHandleOffset = () => this.props.dividerWidth / 2 - this.getHandleWidth() / 2; - - // Render component - render() { - const style = { - divider: { - width: this.props.direction === 'row' ? this.props.dividerWidth : 'auto', - minWidth: this.props.direction === 'row' ? this.props.dividerWidth : 'auto', - maxWidth: this.props.direction === 'row' ? this.props.dividerWidth : 'auto', - height: this.props.direction === 'column' ? this.props.dividerWidth : 'auto', - minHeight: this.props.direction === 'column' ? this.props.dividerWidth : 'auto', - maxHeight: this.props.direction === 'column' ? this.props.dividerWidth : 'auto', - flexGrow: 0, - position: 'relative' - }, - handle: { - position: 'absolute', - width: this.props.direction === 'row' ? this.getHandleWidth() : '100%', - height: this.props.direction === 'column' ? this.getHandleWidth() : '100%', - left: this.props.direction === 'row' ? this.getHandleOffset() : 0, - top: this.props.direction === 'column' ? this.getHandleOffset() : 0, - backgroundColor: this.props.showHandles ? 'rgba(0,128,255,0.25)' : 'auto', - cursor: this.props.direction === 'row' ? 'col-resize' : 'row-resize', - zIndex: 100 - } - }; - Object.assign(style.divider, { backgroundColor: this.props.borderColor }); - - // Add custom class if dragging - let className = 'divider'; - if (this.state.dragging) { - className += ' dragging'; - } - - return ( -
-
-
- ); - } -} diff --git a/src/app-components/panel-group/Panel.jsx b/src/app-components/panel-group/Panel.jsx deleted file mode 100644 index 9fe73c74..00000000 --- a/src/app-components/panel-group/Panel.jsx +++ /dev/null @@ -1,94 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -export default class Panel extends React.Component { - static propTypes = { - resize: PropTypes.string, - onWindowResize: PropTypes.func, - panelID: PropTypes.number.isRequired, - style: PropTypes.object.isRequired, - children: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired - }; - - static defaultProps = { - resize: undefined, - onWindowResize: undefined - }; - // Find the resizeObject if it has one - componentDidMount() { - if (this.props.resize === 'stretch') { - this.refs.resizeObject.addEventListener('load', () => this.onResizeObjectLoad()); - this.refs.resizeObject.data = 'about:blank'; - this.calculateStretchWidth(); // this.onNextFrame(this.calculateStretchWidth); - } - } - - // Attach resize event listener to resizeObject - onResizeObjectLoad = () => { - this.refs.resizeObject.contentDocument.defaultView.addEventListener('resize', () => - this.calculateStretchWidth() - ); - }; - - // Utility function to wait for next render before executing a function - onNextFrame = (callback) => { - setTimeout(() => { - window.requestAnimationFrame(callback); - }, 0); - }; - - // Recalculate the stretchy panel if it's container has been resized - calculateStretchWidth = () => { - if (this.props.onWindowResize !== null) { - const rect = this.node.getBoundingClientRect(); - - this.props.onWindowResize( - this.props.panelID, - { x: rect.width, y: rect.height }, - undefined, - this.node.parentElement - // recalcalculate again if the width is below minimum - // Kinda hacky, but for large resizes like fullscreen/Restore - // it can't solve it in one pass. - // function() {this.onNextFrame(this.calculateStretchWidth)}.bind(this) - ); - } - }; - - createResizeObject() { - const style = { - resizeObject: { - position: 'absolute', - top: 0, - left: 0, - width: '100%', - height: '100%', - zIndex: -1, - opacity: 0 - } - }; - - // only attach resize object if panel is stretchy. Others dont need it - return this.props.resize === 'stretch' ? ( - - ) : null; - } - - // Render component - render() { - const resizeObject = this.createResizeObject(); - - return ( -
{ - this.node = node; - }} - className="panelWrapper" - style={this.props.style} - > - {resizeObject} - {this.props.children} -
- ); - } -} diff --git a/src/app-components/panel-group/PanelGroup.jsx b/src/app-components/panel-group/PanelGroup.jsx deleted file mode 100644 index 3b3d31ac..00000000 --- a/src/app-components/panel-group/PanelGroup.jsx +++ /dev/null @@ -1,506 +0,0 @@ -import React from 'react'; - -import Panel from './Panel'; -import Divider from './Divider'; - -export { Divider, Panel }; - -export default class PanelGroup extends React.Component { - static defaultProps = { - spacing: 1, - direction: 'row', - panelWidths: [], - onUpdate: undefined, - onResizeStart: undefined, - onResizeEnd: undefined, - panelColor: undefined, - borderColor: undefined, - showHandles: false - }; - - // Load initial panel configuration from props - constructor(...args) { - super(...args); - - this.state = this.loadPanels(this.props); - } - - // reload panel configuration if props update - componentWillReceiveProps(nextProps) { - const nextPanels = nextProps.panelWidths; - - // Only update from props if we're supplying the props in the first place - if (nextPanels.length) { - // if the panel array is a different size we know to update - if (this.state.panels.length !== nextPanels.length) { - this.setState(this.loadPanels(nextProps)); - } else { - // otherwise we need to iterate to spot any difference - for (let i = 0; i < nextPanels.length; i++) { - if ( - this.state.panels[i].size !== nextPanels[i].size || - this.state.panels[i].minSize !== nextPanels[i].minSize || - this.state.panels[i].maxSize !== nextPanels[i].maxSize || - this.state.panels[i].resize !== nextPanels[i].resize - ) { - this.setState(this.loadPanels(nextProps)); - break; - } - } - } - } - } - defaultResize = (props, index, defaultResize) => { - let resize = defaultResize; - if (props.panelWidths[index].resize) { - resize = props.panelWidths[index].resize; // eslint-disable-line - } else { - resize = props.panelWidths[index].size ? 'dynamic' : resize; - } - return resize; - }; - // load provided props into state - loadPanels = (props) => { - const panels = []; - - if (props.children) { - // Default values if none were provided - const defaultSize = 256; - const defaultMinSize = 48; - const defaultMaxSize = 0; - const defaultResize = 'stretch'; - - let stretchIncluded = false; - const children = React.Children.toArray(props.children); - - for (let i = 0; i < children.length; i++) { - if (i < props.panelWidths.length && props.panelWidths[i]) { - const widthObj = { - size: props.panelWidths[i].size !== undefined ? props.panelWidths[i].size : defaultSize, - minSize: - props.panelWidths[i].minSize !== undefined - ? props.panelWidths[i].minSize - : defaultMinSize, - maxSize: - props.panelWidths[i].maxSize !== undefined - ? props.panelWidths[i].maxSize - : defaultMaxSize, - resize: this.defaultResize(props, i, defaultResize), - snap: props.panelWidths[i].snap !== undefined ? props.panelWidths[i].snap : [], - style: { - // making the ability to not have to be so terse for style settings on panel - ...this.getPanelClass().defaultProps.style, - ...(props.panelWidths[i].style || {}) - } - }; - panels.push(widthObj); - } else { - // default values if no props are given - panels.push({ - size: defaultSize, - resize: defaultResize, - minSize: defaultMinSize, - maxSize: defaultMaxSize, - snap: [], - style: {} - }); - } - - // if none of the panels included was stretchy, make the last one stretchy - if (panels[i].resize === 'stretch') stretchIncluded = true; - if (!stretchIncluded && i === children.length - 1) panels[i].resize = 'stretch'; - } - } - - return { - panels - }; - }; - - // Pass internal state out if there's a callback for it - // Useful for saving panel configuration - onUpdate = (panels) => { - if (this.props.onUpdate) { - this.props.onUpdate(panels.slice()); - } - }; - - onResizeStart = () => { - if (this.props.onResizeStart) { - // actually this slice clones only array, underlying objects stays the same - this.props.onResizeStart(this.state.panels.slice()); - } - }; - - onResizeEnd = () => { - if (this.props.onResizeEnd) { - this.props.onResizeEnd(this.state.panels.slice()); - } - }; - - // For styling, track which direction to apply sizing to - getSizeDirection = (caps) => { - if (caps) { - return this.props.direction === 'column' ? 'Height' : 'Width'; - } - return this.props.direction === 'column' ? 'height' : 'width'; - }; - - getStyle() { - const container = { - width: '100%', - height: '100%', - [`min${this.getSizeDirection(true)}`]: this.getPanelGroupMinSize(this.props.spacing), - display: 'flex', - flexDirection: this.props.direction, - flexGrow: 1 - }; - - return { - container, - panel: { - flexGrow: 0, - display: 'flex' - } - }; - } - - getPanelStyle(index) { - const { direction, panelColor } = this.props; - - const panel = this.state.panels[index]; - const { style } = panel; - - // setting up the style for this panel. Should probably be handled - // in the child component, but this was easier for now - let newPanelStyle = { - [this.getSizeDirection()]: panel.size, - [direction === 'row' ? 'height' : 'width']: '100%', - [`min${this.getSizeDirection(true)}`]: panel.resize === 'stretch' ? 0 : panel.size, - - flexGrow: panel.resize === 'stretch' ? 1 : 0, - flexShrink: panel.resize === 'stretch' ? 1 : 0, - display: 'flex', - overflow: 'hidden', - position: 'relative', - ...style - }; - if (panelColor !== null) { - // patch in the background color if it was supplied as a prop - newPanelStyle = { - ...newPanelStyle, - backgroundColor: panelColor - }; - } - - return newPanelStyle; - } - - createPanelProps({ panelStyle, index, initialChildren }) { - const panelState = this.state.panels[index]; - let stretchIncluded = false; - // give position info to children - const metadata = { - isFirst: index === 0, - isLast: index === initialChildren.length - 1, - resize: panelState.resize, - - // window resize handler if this panel is stretchy - onWindowResize: panelState.resize === 'stretch' ? this.setPanelSize : null - }; - - // if none of the panels included was stretchy, make the last one stretchy - if (panelState.resize === 'stretch') stretchIncluded = true; - if (!stretchIncluded && metadata.isLast) metadata.resize = 'stretch'; - - return { - style: panelStyle, - key: index, - panelID: index, - ...metadata - }; - } - - createPanel({ panelStyle, index, initialChildren }) { - const Klass = this.getPanelClass(); - return ( - - {initialChildren[index]} - - ); - } - // eslint-disable-next-line class-methods-use-this - getPanelClass() { - // mainly for accessing default props of panels - return Panel; - } - - maybeDivide({ initialChildren, newChildren, index }) { - // add a handle between panels - if (index < initialChildren.length - 1) { - newChildren.push( - - ); - } - } - - // Entry point for resizing panels. - // We clone the panel array and perform operations on it so we can - // setState after the recursive operations are finished - handleResize = (i, delta) => { - const tempPanels = this.state.panels.slice(); - const returnDelta = this.resizePanel( - i, - this.props.direction === 'row' ? delta.x : delta.y, - tempPanels - ); - this.setState({ panels: tempPanels }); - this.onUpdate(tempPanels); - return returnDelta; - }; - - // Recursive panel resizing so we can push other panels out of the way - // if we've exceeded the target panel's extents - resizePanel = (panelIndex, delta, panels) => { - // 1) first let's calculate and make sure all the sizes add up to be correct. - let masterSize = 0; - for (let iti = 0; iti < panels.length; iti += 1) { - masterSize += panels[iti].size; - } - const boundingRect = this.node.getBoundingClientRect(); - const boundingSize = - (this.props.direction === 'column' ? boundingRect.height : boundingRect.width) - - this.props.spacing * (this.props.children.length - 1); - if (Math.abs(boundingSize - masterSize) <= 0.01) { - // Debug log - // console.log({ panels }, `ERROR! SIZES DON'T MATCH!: ${masterSize}, ${boundingSize}`) - - // 2) Rectify the situation by adding all the unacounted for space to the first panel - panels[panelIndex].size += boundingSize - masterSize; - } - - let minsize; - let maxsize; - - // track the progressive delta so we can report back how much this panel - // actually moved after all the adjustments have been made - let resultDelta = delta; - - // make the changes and deal with the consequences later - panels[panelIndex].size += delta; - panels[panelIndex + 1].size -= delta; - - // Min and max for LEFT panel - minsize = this.getPanelMinSize(panelIndex, panels); - maxsize = this.getPanelMaxSize(panelIndex, panels); - - // if we made the left panel too small - if (panels[panelIndex].size < minsize) { - delta = minsize - panels[panelIndex].size; - - if (panelIndex === 0) { - resultDelta = this.resizePanel(panelIndex, delta, panels); - } else { - resultDelta = this.resizePanel(panelIndex - 1, -delta, panels); - } - } - - // if we made the left panel too big - if (maxsize !== 0 && panels[panelIndex].size > maxsize) { - delta = panels[panelIndex].size - maxsize; - - if (panelIndex === 0) { - resultDelta = this.resizePanel(panelIndex, -delta, panels); - } else { - resultDelta = this.resizePanel(panelIndex - 1, delta, panels); - } - } - - // Min and max for RIGHT panel - minsize = this.getPanelMinSize(panelIndex + 1, panels); - maxsize = this.getPanelMaxSize(panelIndex + 1, panels); - - // if we made the right panel too small - if (panels[panelIndex + 1].size < minsize) { - delta = minsize - panels[panelIndex + 1].size; - - if (panelIndex + 1 === panels.length - 1) { - resultDelta = this.resizePanel(panelIndex, -delta, panels); - } else { - resultDelta = this.resizePanel(panelIndex + 1, delta, panels); - } - } - - // if we made the right panel too big - if (maxsize !== 0 && panels[panelIndex + 1].size > maxsize) { - delta = panels[panelIndex + 1].size - maxsize; - - if (panelIndex + 1 === panels.length - 1) { - resultDelta = this.resizePanel(panelIndex, delta, panels); - } else { - resultDelta = this.resizePanel(panelIndex + 1, -delta, panels); - } - } - - // Iterate through left panel's snap positions - for (let i = 0; i < panels[panelIndex].snap.length; i++) { - if (Math.abs(panels[panelIndex].snap[i] - panels[panelIndex].size) < 20) { - delta = panels[panelIndex].snap[i] - panels[panelIndex].size; - - if ( - delta !== 0 && - panels[panelIndex].size + delta >= this.getPanelMinSize(panelIndex, panels) && - panels[panelIndex + 1].size - delta >= this.getPanelMinSize(panelIndex + 1, panels) - ) { - resultDelta = this.resizePanel(panelIndex, delta, panels); - } - } - } - - // Iterate through right panel's snap positions - for (let i = 0; i < panels[panelIndex + 1].snap.length; i++) { - if (Math.abs(panels[panelIndex + 1].snap[i] - panels[panelIndex + 1].size) < 20) { - delta = panels[panelIndex + 1].snap[i] - panels[panelIndex + 1].size; - - if ( - delta !== 0 && - panels[panelIndex].size + delta >= this.getPanelMinSize(panelIndex, panels) && - panels[panelIndex + 1].size - delta >= this.getPanelMinSize(panelIndex + 1, panels) - ) { - resultDelta = this.resizePanel(panelIndex, -delta, panels); - } - } - } - - // return how much this panel actually resized - return resultDelta; - }; - - // Utility function for getting min pixel size of panel - getPanelMinSize = (panelIndex, panels) => { - if (panels[panelIndex].resize === 'fixed') { - if (!panels[panelIndex].fixedSize) { - panels[panelIndex].fixedSize = panels[panelIndex].size; - } - return panels[panelIndex].fixedSize; - } - return panels[panelIndex].minSize; - }; - - // Utility function for getting max pixel size of panel - getPanelMaxSize = (panelIndex, panels) => { - if (panels[panelIndex].resize === 'fixed') { - if (!panels[panelIndex].fixedSize) { - panels[panelIndex].fixedSize = panels[panelIndex].size; - } - return panels[panelIndex].fixedSize; - } - return panels[panelIndex].maxSize; - // return 0; - }; - - // Utility function for getting min pixel size of the entire panel group - getPanelGroupMinSize = (spacing) => { - let size = 0; - for (let i = 0; i < this.state.panels.length; i++) { - size += this.getPanelMinSize(i, this.state.panels); - } - return size + (this.state.panels.length - 1) * spacing; - }; - - // Utility function for getting max pixel size of the entire panel group - getPanelGroupMaxSize = (spacing) => { - let size = 0; - for (let i = 0; i < this.state.panels.length; i++) { - size += this.getPanelMaxSize(i, this.state.panels); - } - return size + (this.state.panels.length - 1) * spacing; - }; - - // Hard-set a panel's size - // Used to recalculate a stretchy panel when the window is resized - setPanelSize = (panelIndex, size, callback, node) => { - if (!this.node && node) { - // due to timing child elements may have parent node first! - this.node = node; - } - size = this.props.direction === 'column' ? size.y : size.x; - if (size !== this.state.panels[panelIndex].size) { - const tempPanels = this.state.panels.map(panel => ({ ...panel })); - - // make sure we can actually resize this panel this small - if (size < tempPanels[panelIndex].minSize) { - let diff = tempPanels[panelIndex].minSize - size; - tempPanels[panelIndex].size = tempPanels[panelIndex].minSize; - - // 1) Find all of the dynamic panels that we can resize and - // decrease them until the difference is gone - for (let i = 0; i < tempPanels.length; i += 1) { - if (i !== panelIndex && tempPanels[i].resize === 'dynamic') { - const available = tempPanels[i].size - tempPanels[i].minSize; - const cut = Math.min(diff, available); - tempPanels[i].size -= cut; - // if the difference is gone then we are done! - diff -= cut; - if (diff === 0) { - break; - } - } - } - } else { - tempPanels[panelIndex].size = size; - } - this.setState({ panels: tempPanels }); - this.onUpdate(tempPanels); - - if (panelIndex > 0) { - this.handleResize(panelIndex - 1, { x: 0, y: 0 }); - } else if (this.state.panels.length > 2) { - this.handleResize(panelIndex + 1, { x: 0, y: 0 }); - } - - if (callback) { - callback(); - } - } - }; - - render() { - const { children } = this.props; - - const style = this.getStyle(); - - // lets build up a new children array with added resize borders - const initialChildren = React.Children.toArray(children); - const newChildren = []; - - for (let i = 0; i < initialChildren.length; i++) { - const panelStyle = this.getPanelStyle(i); - const newPanel = this.createPanel({ panelStyle, index: i, initialChildren }); - newChildren.push(newPanel); - this.maybeDivide({ initialChildren, newChildren, index: i }); - } - - return ( -
{ - this.node = node; - }} - > - {newChildren} -
- ); - } -} diff --git a/src/app-components/panel-group/index.js b/src/app-components/panel-group/index.js deleted file mode 100644 index 0a89d4f0..00000000 --- a/src/app-components/panel-group/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import PanelGroup from './PanelGroup'; - -export { default as Panel } from './Panel'; -export { default as Divider } from './Divider'; -export default PanelGroup; diff --git a/src/app-pages/explorer/explorer.jsx b/src/app-pages/explorer/explorer.jsx index 8efd19a1..e5f75aad 100644 --- a/src/app-pages/explorer/explorer.jsx +++ b/src/app-pages/explorer/explorer.jsx @@ -1,8 +1,8 @@ -import React, { useState, useCallback, useRef } from 'react'; +import React, { useState, useCallback, useRef, useEffect } from 'react'; import { connect } from 'redux-bundler-react'; +import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; +import { useDeepCompareEffect } from 'react-use'; -import Panel from './panel'; -import PanelGroup from '../../app-components/panel-group'; import Map from '../../app-components/classMap'; import MapLegend from './map-legend'; import MapTools from './map-tools'; @@ -12,55 +12,72 @@ import { classArray } from '../../common/helpers/utils'; import './explorer.scss'; +const hasDevBanner = import.meta.env.VITE_DEVELOPMENT_BANNER = 'true'; + export default connect( 'doMapsInitialize', 'doMapsShutdown', + 'doSetExploreDataFilters', 'selectExploreMapKey', 'selectMapsObject', - ({ doMapsInitialize, doMapsShutdown, exploreMapKey: mapKey, mapsObject }) => { + ({ + doMapsInitialize, + doMapsShutdown, + doSetExploreDataFilters, + exploreMapKey: mapKey, + mapsObject, + }) => { const [landscapeMode, setLandscapeMode] = useState(false); + const [legendFilters, setLegendFilters] = useState({ + type: [], + status: [], + }); const mapRef = useRef(); + const cls = classArray([ + 'explorer-container', + hasDevBanner && 'with-banner', + ]); + const toggleLandscape = useCallback( (e) => { if (e.keyCode === 86 && e.shiftKey) { setLandscapeMode(!landscapeMode); + if (mapRef && mapRef.current) mapRef.current.updateSize(); } }, - [setLandscapeMode, landscapeMode] + [setLandscapeMode, landscapeMode, mapRef.current] ); - useWindowListener('keydown', toggleLandscape); + useDeepCompareEffect(() => { + doSetExploreDataFilters(legendFilters); + }, [legendFilters]); - const hasDevBanner = import.meta.env.VITE_DEVELOPMENT_BANNER = 'true'; - const cls = classArray([ - 'explorer-container', - hasDevBanner && 'with-banner', - ]); + useEffect(() => { + if (mapRef && mapRef.current) mapRef.current.updateSize(); + }, [mapRef.current]); + + useWindowListener('keydown', toggleLandscape); return ( -
- - mapRef && mapRef.current && mapRef.current.updateSize() - } - > - - - - +
+ + +
+ + + +
- + +
diff --git a/src/app-pages/explorer/explorer.scss b/src/app-pages/explorer/explorer.scss index 464c8a24..5400f5ae 100644 --- a/src/app-pages/explorer/explorer.scss +++ b/src/app-pages/explorer/explorer.scss @@ -1,3 +1,5 @@ +@import '../../css/variables'; + .explorer-container { height: calc(100vh - 158px); border: 1px solid lightgray; @@ -5,4 +7,40 @@ &.with-banner { height: calc(100vh - 190px); } -} \ No newline at end of file +} + +.active-item { + font-weight: 500; +} + +.inactive-item { + color: #9a9a9a; +} + +.legend-icon { + &.active { + stroke: $success-color; + stroke-width: 3; + fill: white; + } + &.inactive { + stroke: $inactive-color; + stroke-width: 3; + fill: white; + } + &.abandoned { + stroke: $inactive-color; + stroke-width: 3; + fill: white; + } + &.destroyed { + stroke: $inactive-color; + stroke-width: 3; + fill: white; + } + &.lost { + stroke: $warning-color; + stroke-width: 3; + fill: white; + } +} diff --git a/src/app-pages/explorer/map-legend.jsx b/src/app-pages/explorer/map-legend.jsx index d23c9c55..9756896b 100644 --- a/src/app-pages/explorer/map-legend.jsx +++ b/src/app-pages/explorer/map-legend.jsx @@ -1,51 +1,150 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import { connect } from 'redux-bundler-react'; +import { Icon } from '@iconify/react'; +import { VisibilityOffOutlined, VisibilityOutlined } from '@mui/icons-material'; +import { IconButton } from '@mui/material'; import Card from '../../app-components/card'; +import { classArray } from '../../common/helpers/utils'; + +const STATUS_ORDER = ['Active', 'Lost', 'Inactive', 'Abandoned', 'Destroyed']; + +const getLegendItemClasses = (allItems = [], currentItem) => { + const isEmpty = allItems.length === 0; + const contains = allItems.includes(currentItem); + + const classes = classArray([ + 'mb-2', + 'ml-1', + 'pointer', + !isEmpty && !contains ? 'inactive-item' : 'active-item', + ]); + + return classes; +}; + +const displayInstrumentTypes = (legendTypes = [], currentTypes = [], onClick = () => {}) => ( + currentTypes?.map(type => { + const { id, value, description: icon } = type; + + return ( +

onClick(type)}> + {icon && } + {value} +

+ ) + }) +); export default connect( 'selectDomainsItemsByGroup', - ({ domainsItemsByGroup: domains }) => { - const [statuses, setStatuses] = useState([]); + 'selectInstrumentsItems', + ({ + domainsItemsByGroup: domains, + instrumentsItems: instruments, + legendFilters = {}, + setLegendFilters = () => {}, + }) => { + const [isVisible, setIsVisible] = useState(true); + const { status = [], instrument_type = [] } = domains || {}; + const { status: legendStatus = [], type: legendType = [] } = legendFilters || {}; + + const statuses = status.map(s => { + const title = s.value[0].toUpperCase() + s.value.substr(1).toLowerCase(); - useEffect(() => { - const { status } = domains; - const statusOrder = ['Active', 'Lost', 'Inactive', 'Abandoned', 'Destroyed']; + return { + title, + value: s.value, + order: STATUS_ORDER.indexOf(title), + }; + }).sort((a, b) => (a.order > b.order ? 1 : -1)); - if (domains && status) { - const newStatuses = status.map(s => { - const title = s.value[0].toUpperCase() + s.value.substr(1).toLowerCase(); + const currentTypes = instrument_type.filter(type => instruments.some(i => i.type_id === type.id)); - return { - title, - value: s.value, - order: statusOrder.indexOf(title), + const toggleVisibleInstruments = (key, item, maxLength) => { + if (key === 'status') { + const { value } = item; + + setLegendFilters(prev => { + const found = prev['status'].findIndex(el => el === value); + + if (found >= 0) { + const clone = [...prev['status']]; + clone.splice(found, 1); + return { + ...prev, + status: clone.length === maxLength - 1 ? [value] : clone, + }; + } else { + return { + ...prev, + status: [...prev['status'], value], + }; }; }); + } else if (key === 'type') { + const { id } = item; - newStatuses.sort((a, b) => (a.order > b.order ? 1 : -1)); - setStatuses(newStatuses); + setLegendFilters(prev => { + const found = prev['type'].findIndex(el => el === id); + if (found >= 0) { + const clone = [...prev['type']]; + clone.splice(found, 1); + return { + ...prev, + type: clone.length === maxLength - 1 ? [id] : clone, + }; + } else { + return { + ...prev, + type: [...prev['type'], id], + }; + } + }); + } else { + // eslint-disable-next-line no-console + console.log('Invalid Type. Skipping Execution of `toggleVisibleInstruments()`'); } - }, [domains, setStatuses]); + }; return ( -
+
-
Instrument Status
- {statuses.map(x => ( -

- - - - {x.title} -

- ))} + + Map Legend + setIsVisible(prev => !prev)} + title={isVisible ? 'Hide Legend' : 'Show Legend'} + > + {isVisible ? : } + + + {isVisible && ( + <> +
+ {statuses?.map(s => ( +

toggleVisibleInstruments('status', s, statuses.length)}> + + + + {s.title} +

+ ))} + {!!currentTypes?.length && ( + <> +
+ {displayInstrumentTypes(legendType, currentTypes, (i) => toggleVisibleInstruments('type', i, currentTypes.length))} + + )} + + )}
diff --git a/src/app-pages/explorer/map-tools.jsx b/src/app-pages/explorer/map-tools.jsx index 4bc9029a..b63f0cab 100644 --- a/src/app-pages/explorer/map-tools.jsx +++ b/src/app-pages/explorer/map-tools.jsx @@ -8,7 +8,12 @@ import Button from '../../app-components/button'; export default connect( 'doExploreMapInteractionsReset', 'doExploreMapInteractionsSelectMode', - ({ doExploreMapInteractionsReset, doExploreMapInteractionsSelectMode }) => { + 'selectExploreMapInteractionsVersion', + ({ + doExploreMapInteractionsReset, + doExploreMapInteractionsSelectMode, + exploreMapInteractionsVersion, + }) => { const [selectMode, setSelectMode] = useState(false); useEffect(() => { @@ -23,10 +28,11 @@ export default connect( selectMode, doExploreMapInteractionsReset, doExploreMapInteractionsSelectMode, + exploreMapInteractionsVersion, ]); return ( -
+