diff --git a/package-lock.json b/package-lock.json index e2c968f5a9..f02830f002 100644 --- a/package-lock.json +++ b/package-lock.json @@ -105,6 +105,7 @@ "react-helmet": "^6.1.0", "react-icons": "^4.3.1", "react-input-range": "^1.3.0", + "react-joyride": "^2.8.2", "react-leaflet": "^4.2.0", "react-leaflet-custom-control": "^1.3.5", "react-leaflet-draw": "^0.20.4", @@ -4274,6 +4275,12 @@ "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==" }, + "node_modules/@gilbarbara/deep-equal": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.3.1.tgz", + "integrity": "sha512-I7xWjLs2YSVMc5gGx1Z3ZG1lgFpITPndpi8Ku55GeEIKpACCPQNS/OTqQbxgTCfq0Ncvcc+CrFov96itVh6Qvw==", + "license": "MIT" + }, "node_modules/@hapi/accept": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-6.0.0.tgz", @@ -17776,6 +17783,12 @@ "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==" }, + "node_modules/is-lite": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-1.2.1.tgz", + "integrity": "sha512-pgF+L5bxC+10hLBgf6R2P4ZZUBOQIIacbdo8YvuCP8/JvsWxG7aZ9p10DYuLtifFci4l3VITphhMlMV4Y+urPw==", + "license": "MIT" + }, "node_modules/is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", @@ -26295,7 +26308,6 @@ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -27181,6 +27193,54 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" }, + "node_modules/react-floater": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/react-floater/-/react-floater-0.7.9.tgz", + "integrity": "sha512-NXqyp9o8FAXOATOEo0ZpyaQ2KPb4cmPMXGWkx377QtJkIXHlHRAGer7ai0r0C1kG5gf+KJ6Gy+gdNIiosvSicg==", + "license": "MIT", + "dependencies": { + "deepmerge": "^4.3.1", + "is-lite": "^0.8.2", + "popper.js": "^1.16.0", + "prop-types": "^15.8.1", + "tree-changes": "^0.9.1" + }, + "peerDependencies": { + "react": "15 - 18", + "react-dom": "15 - 18" + } + }, + "node_modules/react-floater/node_modules/@gilbarbara/deep-equal": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.1.2.tgz", + "integrity": "sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA==", + "license": "MIT" + }, + "node_modules/react-floater/node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-floater/node_modules/is-lite": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-0.8.2.tgz", + "integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw==", + "license": "MIT" + }, + "node_modules/react-floater/node_modules/tree-changes": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.9.3.tgz", + "integrity": "sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==", + "license": "MIT", + "dependencies": { + "@gilbarbara/deep-equal": "^0.1.1", + "is-lite": "^0.8.2" + } + }, "node_modules/react-helmet": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", @@ -27208,6 +27268,16 @@ "react": "*" } }, + "node_modules/react-innertext": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/react-innertext/-/react-innertext-1.1.5.tgz", + "integrity": "sha512-PWAqdqhxhHIv80dT9znP2KvS+hfkbRovFp4zFYHFFlOoQLRiawIic81gKb3U1wEyJZgMwgs3JoLtwryASRWP3Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": ">=0.0.0 <=99", + "react": ">=0.0.0 <=99" + } + }, "node_modules/react-input-range": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/react-input-range/-/react-input-range-1.3.0.tgz", @@ -27226,6 +27296,62 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-joyride": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/react-joyride/-/react-joyride-2.8.2.tgz", + "integrity": "sha512-2QY8HB1G0I2OT0PKMUz7gg2HAjdkG2Bqi13r0Bb1V16PAwfb9khn4wWBTOJsGsjulbAWiQ3/0YrgNUHGFmuifw==", + "license": "MIT", + "dependencies": { + "@gilbarbara/deep-equal": "^0.3.1", + "deep-diff": "^1.0.2", + "deepmerge": "^4.3.1", + "is-lite": "^1.2.1", + "react-floater": "^0.7.9", + "react-innertext": "^1.1.5", + "react-is": "^16.13.1", + "scroll": "^3.0.1", + "scrollparent": "^2.1.0", + "tree-changes": "^0.11.2", + "type-fest": "^4.18.2" + }, + "peerDependencies": { + "react": "15 - 18", + "react-dom": "15 - 18" + } + }, + "node_modules/react-joyride/node_modules/deep-diff": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz", + "integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==", + "license": "MIT" + }, + "node_modules/react-joyride/node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-joyride/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-joyride/node_modules/type-fest": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.25.0.tgz", + "integrity": "sha512-bRkIGlXsnGBRBQRAY56UXBm//9qH4bmJfFvq83gSz41N282df+fjy8ofcEgc1sM8geNt5cl6mC2g9Fht1cs8Aw==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/react-leaflet": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.0.tgz", @@ -28514,6 +28640,18 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/scroll": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scroll/-/scroll-3.0.1.tgz", + "integrity": "sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==", + "license": "MIT" + }, + "node_modules/scrollparent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.1.0.tgz", + "integrity": "sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==", + "license": "ISC" + }, "node_modules/scss-tokenizer": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz", @@ -30870,6 +31008,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tree-changes": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.11.2.tgz", + "integrity": "sha512-4gXlUthrl+RabZw6lLvcCDl6KfJOCmrC16BC5CRdut1EAH509Omgg0BfKLY+ViRlzrvYOTWR0FMS2SQTwzumrw==", + "license": "MIT", + "dependencies": { + "@gilbarbara/deep-equal": "^0.3.1", + "is-lite": "^1.2.0" + } + }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -35725,6 +35873,11 @@ "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==" }, + "@gilbarbara/deep-equal": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.3.1.tgz", + "integrity": "sha512-I7xWjLs2YSVMc5gGx1Z3ZG1lgFpITPndpi8Ku55GeEIKpACCPQNS/OTqQbxgTCfq0Ncvcc+CrFov96itVh6Qvw==" + }, "@hapi/accept": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-6.0.0.tgz", @@ -45821,6 +45974,11 @@ "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==" }, + "is-lite": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-1.2.1.tgz", + "integrity": "sha512-pgF+L5bxC+10hLBgf6R2P4ZZUBOQIIacbdo8YvuCP8/JvsWxG7aZ9p10DYuLtifFci4l3VITphhMlMV4Y+urPw==" + }, "is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", @@ -52343,8 +52501,7 @@ "popper.js": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "peer": true + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" }, "postcss": { "version": "8.4.39", @@ -52948,6 +53105,44 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" }, + "react-floater": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/react-floater/-/react-floater-0.7.9.tgz", + "integrity": "sha512-NXqyp9o8FAXOATOEo0ZpyaQ2KPb4cmPMXGWkx377QtJkIXHlHRAGer7ai0r0C1kG5gf+KJ6Gy+gdNIiosvSicg==", + "requires": { + "deepmerge": "^4.3.1", + "is-lite": "^0.8.2", + "popper.js": "^1.16.0", + "prop-types": "^15.8.1", + "tree-changes": "^0.9.1" + }, + "dependencies": { + "@gilbarbara/deep-equal": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.1.2.tgz", + "integrity": "sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA==" + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" + }, + "is-lite": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-0.8.2.tgz", + "integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw==" + }, + "tree-changes": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.9.3.tgz", + "integrity": "sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==", + "requires": { + "@gilbarbara/deep-equal": "^0.1.1", + "is-lite": "^0.8.2" + } + } + } + }, "react-helmet": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", @@ -52972,6 +53167,12 @@ "integrity": "sha512-/ngzDP/77tlCfqthiiGNZeYFACw85fUjZtLbedmJ5DTlNDIwETxhwBzdOJ21zj4iJdvc0J3y7yOsX3PpxAJzrw==", "requires": {} }, + "react-innertext": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/react-innertext/-/react-innertext-1.1.5.tgz", + "integrity": "sha512-PWAqdqhxhHIv80dT9znP2KvS+hfkbRovFp4zFYHFFlOoQLRiawIic81gKb3U1wEyJZgMwgs3JoLtwryASRWP3Q==", + "requires": {} + }, "react-input-range": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/react-input-range/-/react-input-range-1.3.0.tgz", @@ -52986,6 +53187,46 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "react-joyride": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/react-joyride/-/react-joyride-2.8.2.tgz", + "integrity": "sha512-2QY8HB1G0I2OT0PKMUz7gg2HAjdkG2Bqi13r0Bb1V16PAwfb9khn4wWBTOJsGsjulbAWiQ3/0YrgNUHGFmuifw==", + "requires": { + "@gilbarbara/deep-equal": "^0.3.1", + "deep-diff": "^1.0.2", + "deepmerge": "^4.3.1", + "is-lite": "^1.2.1", + "react-floater": "^0.7.9", + "react-innertext": "^1.1.5", + "react-is": "^16.13.1", + "scroll": "^3.0.1", + "scrollparent": "^2.1.0", + "tree-changes": "^0.11.2", + "type-fest": "^4.18.2" + }, + "dependencies": { + "deep-diff": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz", + "integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==" + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "type-fest": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.25.0.tgz", + "integrity": "sha512-bRkIGlXsnGBRBQRAY56UXBm//9qH4bmJfFvq83gSz41N282df+fjy8ofcEgc1sM8geNt5cl6mC2g9Fht1cs8Aw==" + } + } + }, "react-leaflet": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.0.tgz", @@ -53899,6 +54140,16 @@ "ajv-keywords": "^3.5.2" } }, + "scroll": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scroll/-/scroll-3.0.1.tgz", + "integrity": "sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==" + }, + "scrollparent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.1.0.tgz", + "integrity": "sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==" + }, "scss-tokenizer": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz", @@ -55671,6 +55922,15 @@ "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz", "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==" }, + "tree-changes": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.11.2.tgz", + "integrity": "sha512-4gXlUthrl+RabZw6lLvcCDl6KfJOCmrC16BC5CRdut1EAH509Omgg0BfKLY+ViRlzrvYOTWR0FMS2SQTwzumrw==", + "requires": { + "@gilbarbara/deep-equal": "^0.3.1", + "is-lite": "^1.2.0" + } + }, "trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", diff --git a/package.json b/package.json index 5428a78469..08341d45c4 100644 --- a/package.json +++ b/package.json @@ -180,6 +180,7 @@ "react-helmet": "^6.1.0", "react-icons": "^4.3.1", "react-input-range": "^1.3.0", + "react-joyride": "^2.8.2", "react-leaflet": "^4.2.0", "react-leaflet-custom-control": "^1.3.5", "react-leaflet-draw": "^0.20.4", diff --git a/static/src/assets/images/tour-video-thumbnail.png b/static/src/assets/images/tour-video-thumbnail.png new file mode 100644 index 0000000000..fc86921a2c Binary files /dev/null and b/static/src/assets/images/tour-video-thumbnail.png differ diff --git a/static/src/js/App.jsx b/static/src/js/App.jsx index eaca8aae91..460ff8a086 100644 --- a/static/src/js/App.jsx +++ b/static/src/js/App.jsx @@ -40,6 +40,7 @@ import KeyboardShortcutsModalContainer from './containers/KeyboardShortcutsModal import MetricsEventsContainer from './containers/MetricsEventsContainer/MetricsEventsContainer' import NotFound from './components/Errors/NotFound' import PortalContainer from './containers/PortalContainer/PortalContainer' +import SearchTour from './components/Tour/SearchTour' import ShapefileDropzoneContainer from './containers/ShapefileDropzoneContainer/ShapefileDropzoneContainer' import ShapefileUploadModalContainer from './containers/ShapefileUploadModalContainer/ShapefileUploadModalContainer' import Spinner from './components/Spinner/Spinner' @@ -72,12 +73,32 @@ const Subscriptions = lazy(() => import('./routes/Subscriptions/Subscriptions')) class App extends Component { constructor(props) { super(props) - this.state = {} + this.state = { + runTour: false + } + this.store = configureStore() const { edscHost } = getEnvironmentConfig() const { env } = getApplicationConfig() this.edscHost = edscHost this.env = env + this.startTour = this.startTour.bind(this) + this.setRunTour = this.setRunTour.bind(this) + } + + componentDidMount() { + const dontShowTour = localStorage.getItem('dontShowTour') !== 'true' + this.setState({ + runTour: dontShowTour + }) + } + + setRunTour(value) { + this.setState({ runTour: value }) + } + + startTour() { + this.setState({ runTour: true }) } // Portal paths have been removed, but this needs to stay in order to redirect users using @@ -119,7 +140,7 @@ class App extends Component { - + ( <> + }> diff --git a/static/src/js/components/AppHeader/AppHeader.jsx b/static/src/js/components/AppHeader/AppHeader.jsx index 7ddbe0c163..137ea00e6d 100644 --- a/static/src/js/components/AppHeader/AppHeader.jsx +++ b/static/src/js/components/AppHeader/AppHeader.jsx @@ -1,4 +1,5 @@ import React from 'react' +import PropTypes from 'prop-types' import SecondaryToolbarContainer from '../../containers/SecondaryToolbarContainer/SecondaryToolbarContainer' @@ -6,11 +7,15 @@ import AppLogoContainer from '../../containers/AppLogoContainer/AppLogoContainer import './AppHeader.scss' -const AppHeader = () => ( +const AppHeader = ({ onStartTour }) => (
- +
) +AppHeader.propTypes = { + onStartTour: PropTypes.func.isRequired +} + export default AppHeader diff --git a/static/src/js/components/Panels/Panels.jsx b/static/src/js/components/Panels/Panels.jsx index 1a78f9c68a..9a1f26aeb2 100644 --- a/static/src/js/components/Panels/Panels.jsx +++ b/static/src/js/components/Panels/Panels.jsx @@ -652,6 +652,7 @@ export class Panels extends PureComponent {
{focusedMeta}
+
) } diff --git a/static/src/js/components/Panels/Panels.scss b/static/src/js/components/Panels/Panels.scss index 2514b3504d..e095a2bb64 100644 --- a/static/src/js/components/Panels/Panels.scss +++ b/static/src/js/components/Panels/Panels.scss @@ -78,3 +78,15 @@ pointer-events: none; } } + +.right-overlay { + position: absolute; + top: 0; + bottom: 0; + right: 0; + z-index: 1000; + background-color: rgba(255, 255, 255, 0); + width: 4000px; + left: calc(100%); + pointer-events: none; +} \ No newline at end of file diff --git a/static/src/js/components/SecondaryToolbar/SecondaryToolbar.jsx b/static/src/js/components/SecondaryToolbar/SecondaryToolbar.jsx index 314bb9319e..9d33700abd 100644 --- a/static/src/js/components/SecondaryToolbar/SecondaryToolbar.jsx +++ b/static/src/js/components/SecondaryToolbar/SecondaryToolbar.jsx @@ -118,7 +118,8 @@ class SecondaryToolbar extends Component { location, retrieval = {}, secondaryToolbarEnabled, - ursProfile + ursProfile, + onStartTour } = this.props const { first_name: firstName = '' } = ursProfile @@ -321,6 +322,18 @@ class SecondaryToolbar extends Component { onToggle={this.onToggleProjectDropdown} alignRight > + { + location.pathname === '/search' && ( + + Start Tour + + ) + } ( +

+ {currentStep} + {' '} + OF + {maxSteps} +

+) + +StepCounter.propTypes = { + currentStep: PropTypes.number.isRequired, + maxSteps: PropTypes.number.isRequired +} + +const TourButtons = ({ stepIndex, setStepIndex }) => ( +
+ + +
+) + +TourButtons.propTypes = { + stepIndex: PropTypes.number.isRequired, + setStepIndex: PropTypes.func.isRequired +} + +const SearchTour = ({ runTour, setRunTour }) => { + const [stepIndex, setStepIndex] = useState(0) + const MAX_STEPS = 12 + + useEffect(() => { + const dontShowTour = localStorage.getItem('dontShowTour') + if (dontShowTour === 'true') { + setRunTour(false) + } + }, [setRunTour]) + + useEffect(() => { + const handleKeyDown = (event) => { + if (event.key === 'ArrowRight') { + setStepIndex((prevIndex) => Math.min(prevIndex + 1, MAX_STEPS + 1)) + } else if (event.key === 'ArrowLeft') { + setStepIndex((prevIndex) => Math.max(prevIndex - 1, 0)) + } + } + + window.addEventListener('keydown', handleKeyDown) + + return () => { + window.removeEventListener('keydown', handleKeyDown) + } + }, []) + + useEffect(() => { + if (runTour) { + setStepIndex(0) + } + + localStorage.setItem('dontShowTour', runTour ? 'false' : 'true') + }, [runTour]) + + useEffect(() => { + if (stepIndex === 6) { // Scrolling to the top to ensure "Browse Portals" is visible. + const element = document.querySelector('.sidebar__content .simplebar-content-wrapper') + if (element) { + element.scrollTop = 0 + } + } + }, [stepIndex]) + + const steps = [ + { + target: '.search', + content: ( +
+

Welcome to Earthdata Search!

+

Let’s start with a quick tour...

+

+ Get acquainted with Earthdata Search by taking our guided tour, where you’ll learn + how to search for data, use the map, create your first project, and manage your + preferences. +

+

+ If you want to skip the tour for now, it is always available by clicking + {' '} + Show Tour + {' '} + at the top of the page. +

+
+ + +
+
+ ), + disableBeacon: true, + placement: 'center' + }, + { + target: '.sidebar__inner', + content: ( +
+ +

+ This area contains the filters used when searching for collections + (datasets produced by an organization) + and their granules (sets of files containing data). +

+

+ Available filters include keyword search, spatial and temporal bounds, + and advanced search options. +

+ +
+ ), + placement: 'right', + styles: { + tooltip: { + width: '400px' + }, + tooltipContent: { + fontSize: '14px', + textAlign: 'left', + lineHeight: '1.5' + } + } + }, + { + target: '.search-form__primary', + content: ( +
+ +

+ Search for collections by topic (e.g., "Land Surface Temperature"), + by collection name, or by CMR Concept ID. +

+

+ As you type, suggestions for matching topics and keywords will be + displayed. When selected, they will be applied as additional search filters. +

+
+

+ Find more information about the + {' '} + + Common Metadata Repository (CMR) + +

+
+ +
+ ), + placement: 'right', + styles: { + tooltip: { + width: '400px' + }, + tooltipContent: { + fontSize: '14px', + textAlign: 'left', + lineHeight: '1.5' + } + } + }, + { + target: '.temporal-selection-dropdown', + content: ( +
+ +

+ Use the temporal filters to limit search results to a specific date + and time range. +

+

+ A recurring filter can be applied to search a repeating range between + specified years. +

+ +
+ ), + placement: 'right', + styles: { + tooltip: { + width: '400px' + }, + tooltipContent: { + fontSize: '14px', + textAlign: 'left', + lineHeight: '1.5' + } + } + }, + { + target: '.spatial-selection-dropdown', + content: ( +
+ +

+ Use the spatial filters to limit search results to the specified + area of interest. +

+

+ To set the spatial area using a polygon, rectangle, point and radius, + or circle, select an option from the menu and then draw on the map + or manually enter coordinates. +

+

+ Upload a shapefile (KML, KMZ, ESRI, etc.) to set the spatial area with + a file. +

+ +
+ ), + placement: 'right', + styles: { + tooltip: { + width: '400px' + }, + tooltipContent: { + fontSize: '14px', + textAlign: 'left', + lineHeight: '1.5' + } + } + }, + { + target: '.search-form__button--advanced-search', + content: ( +
+ +

+ Use Advanced Search parameters to filter results using features like + Hydrologic Unit Code (HUC) or SWORD River Reach. +

+ +
+ ), + placement: 'right', + styles: { + tooltip: { + width: '400px' + }, + tooltipContent: { + fontSize: '14px', + textAlign: 'left', + lineHeight: '1.5' + } + } + }, + { + target: '.sidebar-browse-portals', + content: ( +
+ +

+ Choose a portal to refine search results to a particular area of study, + project, or organization. +

+ +
+ ), + placement: 'right', + disableScrolling: true, + styles: { + tooltip: { + width: '400px' + } + } + }, + { + target: '.sidebar-section-body', + content: ( +
+ +

+ Refine your search further using categories like Features, Keywords, + Platforms, Organizations, etc. +

+ +
+ ), + placement: 'right-start', + disableScrolling: true, + styles: { + tooltip: { + width: '400px' + } + } + }, + { + target: '.panel-section', + content: ( +
+ +

+ A high-level description is displayed for each search result to help you find + the right data, including a summary, temporal range, and information about + capabilities. To view more information about a collection, click the + {' '} + + {' '} + icon. +

+

+ Add granules to a project and customize options before accessing the data. + To add a collection to your project, click the + {' '} + + {' '} + icon. + To add individual granules to a project, click on a search result to view and + add its granules. +

+ +
+ ), + placement: 'right', + styles: { + tooltip: { + width: '400px' + } + }, + floaterProps: { + disableFlip: true, + offset: 10 + } + }, + { + target: '.panels__handle', + content: ( +
+ +

+ To make more room to view the map, the search results can be resized by clicking + or dragging the bar above. The panel can be hidden or shown by clicking the + handle or using the + {' '} + ] + {' '} + key. +

+
+

+ All keyboard shortcuts can be displayed by pressing the + {' '} + ? + {' '} + key at any time. +

+
+ +
+ ), + placement: 'right', + styles: { + tooltip: { + width: '400px' + } + } + }, + { + target: '.right-overlay', + content: ( +
+ +

+ Pan the map by clicking and dragging, and zoom by using the scroll wheel or + map tools. +

+

+ When a collection is selected, the granules will be displayed on the map, + along with any available GIBS imagery. When a granule is focused on the + map, any additional thumbnails will be displayed. +

+ +
+ ), + placement: 'left', + styles: { + tooltip: { + width: '400px', + textAlign: 'left' + } + } + }, + { + target: '.leaflet-bottom.leaflet-right', + content: ( +
+ +

+ Use the map tools to switch map projections, draw, edit, or remove spatial + bounds, zoom the map, or select the base map. +

+ +
+ ), + placement: 'left', + styles: { + tooltip: { + width: '400px' + }, + tooltipContent: { + fontSize: '14px', + textAlign: 'left', + lineHeight: '1.5' + } + } + }, + { + target: '.secondary-toolbar__begin-tour-button', + content: ( +
+ +

+ You can replay this tour anytime by clicking + {' '} + Show Tour + . +

+
+ + +
+
+ ), + placement: 'right', + styles: { + tooltip: { + width: '400px' + } + } + }, + { + target: '.search', + content: ( +
+

+ Want to learn more? +

+

+ Check out our latest webinar where you will see a hands-on example of + how to search for data in Earthdata Search. +

+
+
+ Webinar Thumbnail +
+
+
+ Discover and Access Earth Science Data Using Earthdata Search +
+

+ + Watch the webinar + +

+
+
+

+ Find more information here: +

+

+ + Earthdata Search wiki + +

+

+ + Earthdata Search FAQs + +

+
+ ), + disableBeacon: true, + placement: 'center', + styles: { + tooltip: { + width: '600px', + padding: '20px' + }, + tooltipContent: { + fontSize: '16px' + } + } + } + ] + + const handleJoyrideCallback = (data) => { + const { + action, index, status, type + } = data + + if ([STATUS.FINISHED, STATUS.SKIPPED, STATUS.PAUSED].includes(status) + || action === ACTIONS.CLOSE) { + setRunTour(false) + setStepIndex(0) + } else if (type === 'step:after') { + setStepIndex(action === ACTIONS.NEXT ? index + 1 : index - 1) + } + + if (type === 'step:before') { + const element = document.querySelector('.sidebar-section-body') + if (element) element.scrollTop = 0 + } + } + + return ( + + ) +} + +SearchTour.propTypes = { + runTour: PropTypes.bool.isRequired, + setRunTour: PropTypes.func.isRequired +} + +export default SearchTour diff --git a/static/src/js/components/Tour/SearchTour.scss b/static/src/js/components/Tour/SearchTour.scss new file mode 100644 index 0000000000..d2ff03152a --- /dev/null +++ b/static/src/js/components/Tour/SearchTour.scss @@ -0,0 +1,103 @@ +:root { + --color-white: #ffffff; + --color-black: #000000; + --color-heading: #0063A6; +} + +.button-tour-finish { + min-width: 90px; + } + +.step-counter-text { + font-family: "DM Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: 12px; + margin-bottom: 15px; + color: #9a989a; + text-align: left; +} + + +.text-icon { + display: inline-block; + vertical-align: center; + margin-top: -2px; + height: 0.85rem; + width: 0.85rem; +} + +.tour-heading { + font-size: 14px; + margin-bottom: 10px; + color: var(--color-black); + text-transform: uppercase; + letter-spacing: 1px; +} + +kbd { + display: inline-block; + vertical-align: center; + font-size: 0.75em; + padding: 2px 3px; +} + +.tour-subheading { + font-size: 24px; + font-weight: bold; + margin-bottom: 20px; +} + +.tour-content { + font-size: 16px; + margin-bottom: 20px; + text-align: left; +} + +.tour-note { + font-size: 14px; + margin-bottom: 20px; + text-align: left; +} +.tour-intro-buttons { + display: flex; + justify-content: flex-end; + gap: 15px; + position: absolute; + bottom: 20px; + right: 20px; + width: 19rem; +} +.tour-buttons { + display: flex; + justify-content: flex-end; + gap: 15px; + position: absolute; + bottom: 20px; + right: 20px; + width: 9rem; +} + +.tour-info-box { + background-color: #f8f9fa; + padding: 1rem; + margin-bottom: 15px; + border-radius: 4px; + font-size: 14px; + text-align: left; +} + +.tour-info-box a { + color: var(--color-black); + text-decoration: underline; + font-weight: bold; +} + +.tour-tooltip { + width: 600px; + padding: 20px; + background-color: var(--color-white); + border-radius: 10px; + font-size: 16px; + text-align: center; + position: relative; + min-height: 200px; +} diff --git a/static/src/js/components/Tour/__tests__/SearchTour.test.js b/static/src/js/components/Tour/__tests__/SearchTour.test.js new file mode 100644 index 0000000000..0ce6eb69c8 --- /dev/null +++ b/static/src/js/components/Tour/__tests__/SearchTour.test.js @@ -0,0 +1,27 @@ +import React from 'react' +import { render, screen } from '@testing-library/react' +import '@testing-library/jest-dom/extend-expect' +import SearchTour from '../SearchTour' + +// Mock the local storage +const localStorageMock = { + getItem: jest.fn(), + setItem: jest.fn(), + clear: jest.fn() +} +global.localStorage = localStorageMock + +describe('SearchTour', () => { + const mockSetRunTour = jest.fn() + + beforeEach(() => { + jest.clearAllMocks() + }) + + describe('when rendered', () => { + it('displays the tour component', () => { + render() + expect(screen.getByTestId('mocked-joyride')).toBeInTheDocument() + }) + }) +}) diff --git a/static/src/js/containers/SecondaryToolbarContainer/SecondaryToolbarContainer.jsx b/static/src/js/containers/SecondaryToolbarContainer/SecondaryToolbarContainer.jsx index 60a2962087..6f17570a24 100644 --- a/static/src/js/containers/SecondaryToolbarContainer/SecondaryToolbarContainer.jsx +++ b/static/src/js/containers/SecondaryToolbarContainer/SecondaryToolbarContainer.jsx @@ -44,7 +44,8 @@ export const SecondaryToolbarContainer = (props) => { savedProject, retrieval, ursProfile, - onFetchContactInfo + onFetchContactInfo, + onStartTour } = props useEffect(() => { @@ -66,6 +67,7 @@ export const SecondaryToolbarContainer = (props) => { retrieval={retrieval} secondaryToolbarEnabled={secondaryToolbarEnabled} ursProfile={ursProfile} + onStartTour={onStartTour} /> ) } @@ -82,7 +84,8 @@ SecondaryToolbarContainer.propTypes = { savedProject: PropTypes.shape({}).isRequired, ursProfile: PropTypes.shape({ first_name: PropTypes.string - }).isRequired + }).isRequired, + onStartTour: PropTypes.func.isRequired } export default withRouter( diff --git a/static/src/js/routes/Search/Search.jsx b/static/src/js/routes/Search/Search.jsx index bcb9dd7906..2f9c63f848 100644 --- a/static/src/js/routes/Search/Search.jsx +++ b/static/src/js/routes/Search/Search.jsx @@ -171,69 +171,73 @@ export const Search = ({ - + Browse Portals + + {/* eslint-disable-next-line max-len */} + Enable a portal in order to refine the data available within Earthdata Search + + ) + } + > + + + +
- - - - - - +
+ + - - - handleCheckboxCheck(event)} - /> - - - handleCheckboxCheck(event)} - /> - - + - - - + + + + + handleCheckboxCheck(event)} + /> + + + handleCheckboxCheck(event)} + /> + + + + + + +