diff --git a/docs/screenshots/views/maps/LegendView.png b/docs/screenshots/views/maps/LegendView.png deleted file mode 100644 index 5308a065e..000000000 Binary files a/docs/screenshots/views/maps/LegendView.png and /dev/null differ diff --git a/package-lock.json b/package-lock.json index 78ef9d378..f86c87483 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "metacatui", - "version": "2.30.0", + "version": "2.31.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "metacatui", - "version": "2.30.0", + "version": "2.31.0", "license": "Apache-2.0", "dependencies": { "@actions/core": "^1.9.1", "cheerio": "^1.0.0-rc.11", - "express": "^4.19.2", + "express": "^4.21.0", "fomantic-ui": "^2.9.4-beta.65", "puppeteer": "^22.11.2", "sinon": "^17.0.1" @@ -1663,9 +1663,9 @@ "dev": true }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -1675,7 +1675,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -2803,7 +2803,7 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { "version": "1.5.0", @@ -2816,9 +2816,9 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } @@ -3038,7 +3038,7 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "node_modules/escape-string-regexp": { "version": "2.0.0", @@ -3459,7 +3459,7 @@ "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "engines": { "node": ">= 0.6" } @@ -3481,36 +3481,36 @@ "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==" }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -3713,12 +3713,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -3740,7 +3740,7 @@ "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/find-up": { "version": "5.0.0", @@ -3956,7 +3956,7 @@ "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "engines": { "node": ">= 0.6" } @@ -6656,9 +6656,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -7653,9 +7656,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/path-type": { "version": "4.0.0", @@ -7926,11 +7929,11 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -8400,9 +8403,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -8433,7 +8436,15 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } }, "node_modules/send/node_modules/ms": { "version": "2.1.3", @@ -8441,14 +8452,14 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -9352,7 +9363,7 @@ "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "engines": { "node": ">= 0.8" } @@ -11108,9 +11119,9 @@ "dev": true }, "body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "requires": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -11120,7 +11131,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -11923,7 +11934,7 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { "version": "1.5.0", @@ -11936,9 +11947,9 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, "encoding": { "version": "0.1.13", @@ -12115,7 +12126,7 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "escape-string-regexp": { "version": "2.0.0", @@ -12422,7 +12433,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "expand-tilde": { "version": "2.0.2", @@ -12438,36 +12449,36 @@ "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==" }, "express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -12632,12 +12643,12 @@ } }, "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -12656,7 +12667,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -12823,7 +12834,7 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, "fs-extra": { "version": "11.2.0", @@ -14909,9 +14920,9 @@ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" }, "merge-stream": { "version": "2.0.0", @@ -15626,9 +15637,9 @@ } }, "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "path-type": { "version": "4.0.0", @@ -15820,11 +15831,11 @@ } }, "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "requires": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" } }, "queue-microtask": { @@ -16158,9 +16169,9 @@ } }, "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "requires": { "debug": "2.6.9", "depd": "2.0.0", @@ -16188,10 +16199,15 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -16200,14 +16216,14 @@ } }, "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "requires": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" } }, "set-blocking": { @@ -16903,7 +16919,7 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, "update-browserslist-db": { "version": "1.1.0", diff --git a/package.json b/package.json index bf1653711..24e1341e9 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "dependencies": { "@actions/core": "^1.9.1", "cheerio": "^1.0.0-rc.11", - "express": "^4.19.2", + "express": "^4.21.0", "fomantic-ui": "^2.9.4-beta.65", "puppeteer": "^22.11.2", "sinon": "^17.0.1" diff --git a/src/css/map-view.css b/src/css/map-view.css index c91de861b..0e87e1e7c 100644 --- a/src/css/map-view.css +++ b/src/css/map-view.css @@ -888,7 +888,7 @@ represents 1 unit of the given distance measurement. */ background-color: var(--portal-col-primary-1, #15324e); } - .layer-item__legend-and-settings { + .layer-item__settings { /* Show button when layer is hovered. */ display: flex; border: 1px solid var(--portal-col-neutral-4); @@ -919,19 +919,14 @@ represents 1 unit of the given distance measurement. */ } } - .layer-item__legend-and-settings { + .layer-item__settings { /* Show button when layer is shown. */ display: flex; border: 1px solid var(--portal-col-primary-2); - - /* Only show legend when layer is shown. */ - .layer-item__legend-container { - display: block; - } } } - .layer-item__legend-and-settings { + .layer-item__settings { /* By default, don't show button. */ display: none; align-items: center; @@ -948,12 +943,6 @@ represents 1 unit of the given distance measurement. */ color: var(--portal-col-neutral-6, currentColor); } - .layer-item__legend-container { - /* By default, don't show legend. */ - display: none; - margin-left: -0.5rem; - } - /* Use the same style on button hover and when selected. */ &.layer-item--selected, &:hover { diff --git a/src/js/templates/maps/layer-item.html b/src/js/templates/maps/layer-item.html index 85b0af64d..113f50ed6 100644 --- a/src/js/templates/maps/layer-item.html +++ b/src/js/templates/maps/layer-item.html @@ -7,7 +7,6 @@ <%= label %> - + - diff --git a/src/js/themes/arctic/config.js b/src/js/themes/arctic/config.js index 4d2e81efe..481c88b26 100644 --- a/src/js/themes/arctic/config.js +++ b/src/js/themes/arctic/config.js @@ -101,6 +101,8 @@ MetacatUI.AppConfig = Object.assign( // CesiumMap enableCesium: true, + cesiumToken: + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJiMTE3MTBiYy1iODY1LTQxNjMtODUzNS0yMzM1NGE3M2JhMWIiLCJpZCI6NjkzOTcsImlhdCI6MTY3ODkwMDg5M30.xzIlG_tPIzB9FmYPzPTzOJ49R2J4yIG2y0ittFqdbMc", useDeprecatedDataCatalogView: false, catalogSearchMapOptions: { clickFeatureAction: "zoom", diff --git a/src/js/themes/dataone/config.js b/src/js/themes/dataone/config.js index 32b55fd75..6ad97caab 100644 --- a/src/js/themes/dataone/config.js +++ b/src/js/themes/dataone/config.js @@ -6,7 +6,7 @@ MetacatUI.AppConfig = Object.assign( title: "DataONE Data Catalog", theme: "dataone", baseUrl: "https://search.dataone.org", - mapKey: "AIzaSyDuQ9r_7EeSfspKYs2SET7sv4c8FysLIk4", + mapKey: "AIzaSyB61wRRIFZngUXO81ipq3ljUjsClKcT9mk", repositoryName: "DataONE Data Catalog", emailContact: "support@dataone.org", nodeId: "urn:node:CN", diff --git a/src/js/views/filters/FilterEditorView.js b/src/js/views/filters/FilterEditorView.js index 782c2cd4c..f611f73cd 100644 --- a/src/js/views/filters/FilterEditorView.js +++ b/src/js/views/filters/FilterEditorView.js @@ -291,7 +291,7 @@ define([ description: "Allow people to select a search term from a list of options", filterTypes: ["filter"], - blockedFields: [...MetacatUI.appModel.get("querySemanticFields")], + blockedFields: [], modelFunction: function (attrs) { return new ChoiceFilter(attrs); }, diff --git a/src/js/views/maps/LayerDetailsView.js b/src/js/views/maps/LayerDetailsView.js index d62ccd9cb..41feec734 100644 --- a/src/js/views/maps/LayerDetailsView.js +++ b/src/js/views/maps/LayerDetailsView.js @@ -11,7 +11,6 @@ define([ "views/maps/LayerOpacityView", "views/maps/LayerInfoView", "views/maps/LayerNavigationView", - "views/maps/LegendView", ], ( $, _, @@ -23,7 +22,6 @@ define([ LayerOpacityView, LayerInfoView, LayerNavigationView, - LegendView, ) => { /** * @class LayerDetailsView @@ -120,13 +118,6 @@ define([ showTitle: false, hideIfError: true, }, - { - label: "Legend", - view: LegendView, - collapsible: false, - showTitle: true, - hideIfError: true, - }, { label: "Opacity", view: LayerOpacityView, @@ -143,12 +134,7 @@ define([ }, ], - /** - * Creates an object that gives the events this view will listen to and the - * associated function to call. Each entry in the object has the format 'event - * selector': 'function'. - * @returns {object} - */ + /** @inheritdoc */ events() { const events = {}; // Close the layer details panel when the toggle button is clicked. Get the @@ -168,134 +154,106 @@ define([ * @param {object} [options] - A literal object with options to pass to the view */ initialize(options) { - try { - // Get all the options and apply them to this view - if (typeof options === "object") { - Object.keys(options).forEach((key) => { - this[key] = options[key]; - }); - } - } catch (e) { - console.log( - `A LayerDetailsView failed to initialize. Error message: ${e}`, - ); + // Get all the options and apply them to this view + if (typeof options === "object") { + Object.assign(this, options); } }, /** * Renders this view - * @returns {LayerDetailsView | null} Returns the rendered view element + * @returns {LayerDetailsView} Returns the rendered view element */ render() { - try { - const { model } = this; + const { model } = this; - // Show the layer details box as open if the view is set to have it open - // already - if (this.isOpen) { - this.el.classList.add(this.classes.open); - } - - // Insert the template into the view - this.$el.html( - this.template({ - label: model ? model.get("label") || "" : "", - }), - ); + // Show the layer details box as open if the view is set to have it open + // already + if (this.isOpen) { + this.el.classList.add(this.classes.open); + } - // Ensure the view's main element has the given class name - this.el.classList.add(this.className); + // Insert the template into the view + this.$el.html( + this.template({ + label: model ? model.get("label") || "" : "", + }), + ); - // Select elements in the template that we will need to manipulate - const sectionsContainer = this.el.querySelector( - `.${this.classes.sections}`, - ); - const labelEl = this.el.querySelector(`.${this.classes.label}`); + // Ensure the view's main element has the given class name + this.el.classList.add(this.className); - // Render each section in the Details panel - this.renderedSections = _.clone(this.sections); + // Select elements in the template that we will need to manipulate + const sectionsContainer = this.el.querySelector( + `.${this.classes.sections}`, + ); + const labelEl = this.el.querySelector(`.${this.classes.label}`); - // Remove and do not render opacity section if showOpacitySlider is false - if (model?.get("showOpacitySlider") === false) { - this.renderedSections = this.renderedSections.filter( - (item) => item.label !== "Opacity", - ); + this.renderedSections = this.sections.map((section) => { + const detailSection = new LayerDetailView({ + label: section.label, + contentView: section.view, + model, + collapsible: section.collapsible, + showTitle: section.showTitle, + }); + sectionsContainer.append(detailSection.el); + detailSection.render(); + // Hide the section if there is an error with the asset, and this section + // does make sense to show for a layer that can't be displayed + if (section.hideIfError && model) { + if (model && model.get("status") === "error") { + detailSection.el.style.display = "none"; + } } + return { ...section, renderedView: detailSection }; + }); + // Hide/show sections with the 'hideIfError' property when the status of the + // MapAsset changes + this.stopListening(model, "change:status"); + this.listenTo(model, "change:status", (_model, status) => { + let displayProperty = ""; + if (status === "error") { + displayProperty = "none"; + } this.renderedSections.forEach((section) => { - const detailSection = new LayerDetailView({ - label: section.label, - contentView: section.view, - model, - collapsible: section.collapsible, - showTitle: section.showTitle, - }); - sectionsContainer.append(detailSection.el); - detailSection.render(); - // Hide the section if there is an error with the asset, and this section - // does make sense to show for a layer that can't be displayed - if (section.hideIfError && model) { - if (model && model.get("status") === "error") { - detailSection.el.style.display = "none"; - } + if (section.hideIfError) { + // eslint-disable-next-line no-param-reassign + section.renderedView.el.style.display = displayProperty; } - section.renderedView = detailSection; }); + }); - // Hide/show sections with the 'hideIfError' property when the status of the - // MapAsset changes - this.stopListening(model, "change:status"); - this.listenTo(model, "change:status", (_model, status) => { - const hideIfErrorSections = _.filter( - this.renderedSections, - (section) => section.hideIfError, - ); - let displayProperty = ""; - if (status === "error") { - displayProperty = "none"; - } - hideIfErrorSections.forEach((section) => { - const renderedViewEl = section.renderedView.el; - renderedViewEl.style.display = displayProperty; - }); - }); - - // If this layer has a notification, show the badge and notification + // If this layer has a notification, show the badge and notification + // message + const notice = model ? model.get("notification") : null; + if (notice && (notice.message || notice.badge)) { // message - const notice = model ? model.get("notification") : null; - if (notice && (notice.message || notice.badge)) { - // message - if (notice.message) { - const noticeEl = document.createElement("div"); - noticeEl.classList.add(this.classes.notification); - noticeEl.innerText = notice.message; - if (notice.style) { - const badgeClass = `${this.classes.notification}--${notice.style}`; - noticeEl.classList.add(badgeClass); - } - sectionsContainer.prepend(noticeEl); + if (notice.message) { + const noticeEl = document.createElement("div"); + noticeEl.classList.add(this.classes.notification); + noticeEl.innerText = notice.message; + if (notice.style) { + const badgeClass = `${this.classes.notification}--${notice.style}`; + noticeEl.classList.add(badgeClass); } - // badge - if (notice.badge) { - const badge = document.createElement("span"); - badge.classList.add(this.classes.badge); - badge.innerText = notice.badge; - if (notice.style) { - const badgeClass = `${this.classes.badge}--${notice.style}`; - badge.classList.add(badgeClass); - } - labelEl.append(badge); + sectionsContainer.prepend(noticeEl); + } + // badge + if (notice.badge) { + const badge = document.createElement("span"); + badge.classList.add(this.classes.badge); + badge.innerText = notice.badge; + if (notice.style) { + const badgeClass = `${this.classes.badge}--${notice.style}`; + badge.classList.add(badgeClass); } + labelEl.append(badge); } - - return this; - } catch (error) { - console.log( - `There was an error rendering a LayerDetailsView` + - `. Error details: ${error}`, - ); - return null; } + + return this; }, /** @@ -303,18 +261,11 @@ define([ * MapAsset model's 'selected attribute' to true. */ open() { - try { - this.el.classList.add(this.classes.open); - this.isOpen = true; - // Ensure that the model is marked as selected - if (this.model) { - this.model.set("selected", true); - } - } catch (error) { - console.log( - `There was an error opening the LayerDetailsView` + - `. Error details: ${error}`, - ); + this.el.classList.add(this.classes.open); + this.isOpen = true; + // Ensure that the model is marked as selected + if (this.model) { + this.model.set("selected", true); } }, @@ -323,18 +274,11 @@ define([ * MapAsset model's 'selected attribute' to false. */ close() { - try { - this.el.classList.remove(this.classes.open); - this.isOpen = false; - // Ensure that the model is not marked as selected - if (this.model) { - this.model.set("selected", false); - } - } catch (error) { - console.log( - `There was an error closing the LayerDetailsView` + - `. Error details: ${error}`, - ); + this.el.classList.remove(this.classes.open); + this.isOpen = false; + // Ensure that the model is not marked as selected + if (this.model) { + this.model.set("selected", false); } }, @@ -346,24 +290,17 @@ define([ * information. */ updateModel(newModel) { - try { - // Remove listeners from sub-views - this.renderedSections.forEach((section) => { - if ( - section.renderedView && - typeof section.renderedView.onClose === "function" - ) { - section.renderedView.onClose(); - } - }); - this.model = newModel; - this.render(); - } catch (error) { - console.log( - `There was an error updating the MapAsset model in a LayerDetailsView` + - `. Error details: ${error}`, - ); - } + // Remove listeners from sub-views + this.renderedSections.forEach((section) => { + if ( + section.renderedView && + typeof section.renderedView.onClose === "function" + ) { + section.renderedView.onClose(); + } + }); + this.model = newModel; + this.render(); }, }, ); diff --git a/src/js/views/maps/LayerItemView.js b/src/js/views/maps/LayerItemView.js index 9019b93bb..9f3e30aee 100644 --- a/src/js/views/maps/LayerItemView.js +++ b/src/js/views/maps/LayerItemView.js @@ -7,34 +7,22 @@ define([ "models/maps/assets/MapAsset", "common/IconUtilities", "text!templates/maps/layer-item.html", - // Sub-views - "views/maps/LegendView", -], function ( - $, - _, - Backbone, - MapAsset, - IconUtilities, - Template, - // Sub-views - Legend, -) { +], ($, _, Backbone, MapAsset, IconUtilities, Template) => { /** * @class LayerItemView * @classdesc One item in a Layer List: shows some basic information about the Map * Asset (Layer), including label and icon. Also has a button that changes the * visibility of the Layer of the map (by updating the 'visibility' attribute in the * MapAsset model). Clicking on the Layer Item opens the Layer Details panel (by - * setting the 'selected' attribute to true in the Layer model.) Additionally, shows a - * small preview of a legend for the data that's on the map. + * setting the 'selected' attribute to true in the Layer model.) * @classcategory Views/Maps * @name LayerItemView - * @extends Backbone.View + * @augments Backbone.View * @screenshot views/maps/LayerItemView.png * @since 2.18.0 * @constructs */ - var LayerItemView = Backbone.View.extend( + const LayerItemView = Backbone.View.extend( /** @lends LayerItemView.prototype */ { /** * The type of View this is @@ -70,13 +58,11 @@ define([ /** * Classes that are used to identify or create the HTML elements that comprise this * view. - * @type {Object} + * @type {object} * @property {string} label The element that contains the layer's name/label * @property {string} icon The span element that contains the SVG icon * @property {string} visibilityToggle The element that acts like a button to * switch the Layer's visibility on and off - * @property {string} legendContainer The element that the legend preview will be - * inserted into. * @property {string} selected The class that gets added to the view when the Layer * Item is selected * @property {string} shown The class that gets added to the view when the Layer @@ -89,13 +75,12 @@ define([ label: "layer-item__label", icon: "layer-item__icon", visibilityToggle: "layer-item__visibility-toggle", - legendContainer: "layer-item__legend-container", selected: "layer-item--selected", shown: "layer-item--shown", labelText: "layer-item__label-text", highlightedText: "layer-item__highlighted-text", categorized: "layer-item__categorized", - legendAndSettings: "layer-item__legend-and-settings", + settings: "layer-item__settings", badge: "map-view__badge", tooltip: "map-tooltip", }, @@ -111,147 +96,98 @@ define([ /** * A function that gives the events this view will listen to and the associated * function to call. - * @returns {Object} Returns an object with events in the format 'event selector': + * @returns {object} Returns an object with events in the format 'event selector': * 'function' */ - events: function () { - try { - var events = {}; - events["click ." + this.classes.legendAndSettings] = "toggleSelected"; - events["click"] = "toggleVisibility"; - return events; - } catch (error) { - console.log( - "There was an error setting the events object in a LayerItemView" + - ". Error details: " + - error, - ); - } + events() { + const events = {}; + events[`click .${this.classes.settings}`] = "toggleSelected"; + events.click = "toggleVisibility"; + return events; }, /** * Executed when a new LayerItemView is created - * @param {Object} [options] - A literal object with options to pass to the view + * @param {object} [options] - A literal object with options to pass to the view */ - initialize: function (options) { - try { - // Get all the options and apply them to this view - if (typeof options == "object") { - for (const [key, value] of Object.entries(options)) { - this[key] = value; - } - } - } catch (e) { - console.log( - "A LayerItemView failed to initialize. Error message: " + e, - ); + initialize(options) { + // Get all the options and apply them to this view + if (typeof options === "object") { + Object.assign(this, options); } }, - /** - * Renders this view - * @return {LayerItemView} Returns the rendered view element - */ - render: function () { - try { - // Save a reference to this view - var view = this; - - if (!this.model) { - return; - } - - // Insert the template into the view - this.$el.html( - this.template({ - label: this.model.get("label"), - classes: this.classes, - }), - ); - // Save a reference to the label element - this.labelEl = this.el.querySelector("." + this.classes.label); - - // Insert the icon on the left - if (!this.isCategorized) { - this.insertIcon(); - } - - // Add a thumbnail / legend preview - const legendContainer = this.el.querySelector( - "." + this.classes.legendContainer, - ); - const legendPreview = new Legend({ - model: this.model, - mode: "preview", - }); - legendContainer.append(legendPreview.render().el); - - // Ensure the view's main element has the given class name - this.el.classList.add(this.className); - - // Show the item as hidden and/or selected depending on the model properties - // that are set initially - this.showVisibility(); - this.showSelection(); - // Show the current status of this layer - this.showStatus(); - - // When the Layer is selected, highlight this item in the Layer List. When - // it's no longer selected, then make sure it's no longer highlighted. Set a - // listener because the 'selected' attribute can be changed within this view, - // from the parent Layers collection, or from the Layer Details View. - this.stopListening(this.model, "change:selected"); - this.listenTo(this.model, "change:selected", this.showSelection); - - // Similar to above, add or remove the shown class when the layer's - // visibility changes - this.stopListening(this.model, "change:visible"); - this.listenTo(this.model, "change:visible", this.showVisibility); - - // Update the item in the list to show when it is loading, loaded, or there's - // been an error. - this.stopListening(this.model, "change:status"); - this.listenTo(this.model, "change:status", this.showStatus); - + /** @inheritdoc */ + render() { + if (!this.model) { return this; - } catch (error) { - console.log( - "There was an error rendering a LayerItemView" + - ". Error details: " + - error, - ); } + + // Insert the template into the view + this.$el.html( + this.template({ + label: this.model.get("label"), + classes: this.classes, + }), + ); + // Save a reference to the label element + this.labelEl = this.el.querySelector(`.${this.classes.label}`); + + // Insert the icon on the left + if (!this.isCategorized) { + this.insertIcon(); + } + + // Ensure the view's main element has the given class name + this.el.classList.add(this.className); + + // Show the item as hidden and/or selected depending on the model properties + // that are set initially + this.showVisibility(); + this.showSelection(); + // Show the current status of this layer + this.showStatus(); + + // When the Layer is selected, highlight this item in the Layer List. When + // it's no longer selected, then make sure it's no longer highlighted. Set a + // listener because the 'selected' attribute can be changed within this view, + // from the parent Layers collection, or from the Layer Details View. + this.stopListening(this.model, "change:selected"); + this.listenTo(this.model, "change:selected", this.showSelection); + + // Similar to above, add or remove the shown class when the layer's + // visibility changes + this.stopListening(this.model, "change:visible"); + this.listenTo(this.model, "change:visible", this.showVisibility); + + // Update the item in the list to show when it is loading, loaded, or there's + // been an error. + this.stopListening(this.model, "change:status"); + this.listenTo(this.model, "change:status", this.showStatus); + + return this; }, /** * Waits for the icon attribute to be ready in the Map Asset model, then inserts * the icon before the label. */ - insertIcon: function () { - try { - const model = this.model; - let icon = model.get("icon"); - if (!icon || typeof icon !== "string" || !IconUtilities.isSVG(icon)) { - icon = model.defaults().icon; - } - const iconContainer = document.createElement("span"); - iconContainer.classList.add(this.classes.icon); - iconContainer.innerHTML = icon; - this.el - .querySelector("." + this.classes.visibilityToggle) - .replaceChildren(iconContainer); - - const iconStatus = model.get("iconStatus"); - if (iconStatus && iconStatus === "fetching") { - this.listenToOnce(model, "change:iconStatus", this.insertIcon); - return; - } - } catch (error) { - console.log( - "There was an error inserting an icon in a LayerItemView" + - ". Error details: " + - error, - ); + insertIcon() { + const { model } = this; + let icon = model.get("icon"); + if (!icon || typeof icon !== "string" || !IconUtilities.isSVG(icon)) { + icon = model.defaults().icon; + } + const iconContainer = document.createElement("span"); + iconContainer.classList.add(this.classes.icon); + iconContainer.innerHTML = icon; + this.el + .querySelector(`.${this.classes.visibilityToggle}`) + .replaceChildren(iconContainer); + + const iconStatus = model.get("iconStatus"); + if (iconStatus && iconStatus === "fetching") { + this.listenToOnce(model, "change:iconStatus", this.insertIcon); } }, @@ -260,55 +196,39 @@ define([ * to false if it's true. Executed when a user clicks on this Layer Item in a * Layer List view. */ - toggleSelected: function () { - try { - var layerModel = this.model; - if (layerModel.get("selected")) { - layerModel.set("selected", false); - } else { - layerModel.set("selected", true); - } - } catch (error) { - console.log( - "There was an error selecting or unselecting a layer in a LayerItemView" + - ". Error details: " + - error, - ); + toggleSelected() { + const layerModel = this.model; + if (layerModel.get("selected")) { + layerModel.set("selected", false); + } else { + layerModel.set("selected", true); } }, /** * Sets the Layer model's visibility status attribute to true if it's false, and - * to false if it's true. Executed when a user clicks on the visibility toggle. + * to false if it's true. Executed when a user clicks on this view. + * @param {object} event The click event on this view component. */ - toggleVisibility: function (event) { - try { - if ( - this.$(`.${this.classes.legendAndSettings}`).is(event.target) || - this.$(`.${this.classes.legendAndSettings}`).has(event.target) - .length > 0 - ) { - return; - } + toggleVisibility(event) { + if ( + this.$(`.${this.classes.settings}`).is(event.target) || + this.$(`.${this.classes.settings}`).has(event.target).length > 0 + ) { + return; + } - const layerModel = this.model; - // Hide if visible - if (layerModel.get("visible")) { - layerModel.set("visible", false); - // Show if hidden - } else { - // If user is trying to make the layer visible, make sure the opacity is not 0 - if (layerModel.get("opacity") === 0) { - layerModel.set("opacity", 0.5); - } - layerModel.set("visible", true); + const layerModel = this.model; + // Hide if visible + if (layerModel.get("visible")) { + layerModel.set("visible", false); + // Show if hidden + } else { + // If user is trying to make the layer visible, make sure the opacity is not 0 + if (layerModel.get("opacity") === 0) { + layerModel.set("opacity", 0.5); } - } catch (error) { - console.log( - "There was an error selecting or unselecting a layer in a LayerItemView" + - ". Error details: " + - error, - ); + layerModel.set("visible", true); } }, @@ -320,23 +240,13 @@ define([ * toggleSelected function), from the parent Layers collection, or from the * Layer Details View. */ - showSelection: function () { - try { - var layerModel = this.model; - if (layerModel.get("selected")) { - this.$(`.${this.classes.legendAndSettings}`).addClass( - this.classes.selected, - ); - } else { - this.$(`.${this.classes.legendAndSettings}`).removeClass( - this.classes.selected, - ); - } - } catch (error) { - console.log( - "There was an error changing the highlighting in a LayerItemView" + - ". Error details: " + - error, + showSelection() { + const layerModel = this.model; + if (layerModel.get("selected")) { + this.$(`.${this.classes.settings}`).addClass(this.classes.selected); + } else { + this.$(`.${this.classes.settings}`).removeClass( + this.classes.selected, ); } }, @@ -346,20 +256,12 @@ define([ * set in the Layer model's 'visible' attribute. Executed whenever the 'visible' * attribute changes. */ - showVisibility: function () { - try { - var layerModel = this.model; - if (layerModel.get("visible")) { - this.$el.addClass(this.classes.shown); - } else { - this.$el.removeClass(this.classes.shown); - } - } catch (error) { - console.log( - "There was an error changing the shown styles in a LayerItemView" + - ". Error details: " + - error, - ); + showVisibility() { + const layerModel = this.model; + if (layerModel.get("visible")) { + this.$el.addClass(this.classes.shown); + } else { + this.$el.removeClass(this.classes.shown); } }, @@ -367,29 +269,21 @@ define([ * Gets the Map Asset model's status and updates this Layer Item View to reflect * that status to the user. */ - showStatus: function () { - try { - var layerModel = this.model; - var status = layerModel.get("status"); - if (status === "error") { - const errorMessage = layerModel.get("statusDetails"); - this.showError(errorMessage); - } else if (status === "ready") { - this.removeStatuses(); - const notice = layerModel.get("notification"); - const badge = notice ? notice.badge : null; - if (badge) { - this.showBadge(badge, notice.style); - } - } else if (status === "loading") { - this.showLoading(); + showStatus() { + const layerModel = this.model; + const status = layerModel.get("status"); + if (status === "error") { + const errorMessage = layerModel.get("statusDetails"); + this.showError(errorMessage); + } else if (status === "ready") { + this.removeStatuses(); + const notice = layerModel.get("notification"); + const badge = notice ? notice.badge : null; + if (badge) { + this.showBadge(badge, notice.style); } - } catch (error) { - console.log( - "There was an error showing the status in a LayerItemView" + - ". Error details: " + - error, - ); + } else if (status === "loading") { + this.showLoading(); } }, @@ -397,22 +291,14 @@ define([ * Remove any icons, tooltips, or other visual indicators of a Map Asset's error * or loading status in this view */ - removeStatuses: function () { - try { - if (this.statusIcon) { - this.statusIcon.remove(); - } - if (this.badge) { - this.badge.remove(); - } - this.$el.tooltip("destroy"); - } catch (error) { - console.log( - "There was an error removing status indicators in a LayerItemView" + - ". Error details: " + - error, - ); + removeStatuses() { + if (this.statusIcon) { + this.statusIcon.remove(); + } + if (this.badge) { + this.badge.remove(); } + this.$el.tooltip("destroy"); }, /** @@ -421,26 +307,18 @@ define([ * @param {string} [style] - The style of the badge. Can be any of the styles * defined in the {@link MapConfig#Notification} style property, e.g. 'green' */ - showBadge: function (text, style) { - try { - if (!text) { - return; - } - this.removeStatuses(); - this.badge = document.createElement("span"); - this.badge.classList.add(this.classes.badge); - this.badge.innerText = text; - this.labelEl.append(this.badge); - if (style) { - const badgeClass = this.classes.badge + "--" + style; - this.badge.classList.add(badgeClass); - } - } catch (error) { - console.log( - "There was an error showing the badge in a LayerItemView" + - ". Error details: " + - error, - ); + showBadge(text, style) { + if (!text) { + return; + } + this.removeStatuses(); + this.badge = document.createElement("span"); + this.badge.classList.add(this.classes.badge); + this.badge.innerText = text; + this.labelEl.append(this.badge); + if (style) { + const badgeClass = `${this.classes.badge}--${style}`; + this.badge.classList.add(badgeClass); } }, @@ -450,66 +328,47 @@ define([ * with more details * @param {string} message The error message to show in the tooltip. */ - showError: function (message = "") { - try { - const view = this; - - // Remove any style elements for other statuses - this.removeStatuses(); - - // Show a warning icon - this.statusIcon = document.createElement("span"); - this.statusIcon.innerHTML = ``; - this.statusIcon.style.opacity = "0.6"; - this.labelEl.append(this.statusIcon); - - // Show a tooltip with the error message - let fullMessage = this.errorMessage; - if (message) { - fullMessage = fullMessage + " Error details: " + message; - } - this.$el.tooltip({ - placement: "top", - trigger: "hover", - title: fullMessage, - container: "body", - animation: false, - template: - '
', - delay: { show: 250, hide: 5 }, - }); - } catch (error) { - console.log( - "Failed to show the error status in a LayerItemView" + - ". Error details: " + - error, - ); + showError(message = "") { + const view = this; + + // Remove any style elements for other statuses + this.removeStatuses(); + + // Show a warning icon + this.statusIcon = document.createElement("span"); + this.statusIcon.innerHTML = ``; + this.statusIcon.style.opacity = "0.6"; + this.labelEl.append(this.statusIcon); + + // Show a tooltip with the error message + let fullMessage = this.errorMessage; + if (message) { + fullMessage = `${fullMessage} Error details: ${message}`; } + this.$el.tooltip({ + placement: "top", + trigger: "hover", + title: fullMessage, + container: "body", + animation: false, + template: `
`, + delay: { show: 250, hide: 5 }, + }); }, /** * Show a spinner icon to the right of the Map Asset label to indicate that this * layer is loading */ - showLoading: function () { - try { - // Remove any style elements for other statuses - this.removeStatuses(); - - // Show a spinner icon - this.statusIcon = document.createElement("span"); - this.statusIcon.innerHTML = ``; - this.statusIcon.style.opacity = "0.6"; - this.labelEl.append(this.statusIcon); - } catch (error) { - console.log( - "There was an error showing the loading status in a LayerItemView" + - ". Error details: " + - error, - ); - } + showLoading() { + // Remove any style elements for other statuses + this.removeStatuses(); + + // Show a spinner icon + this.statusIcon = document.createElement("span"); + this.statusIcon.innerHTML = ``; + this.statusIcon.style.opacity = "0.6"; + this.labelEl.append(this.statusIcon); }, /** @@ -524,12 +383,12 @@ define([ const regex = new RegExp(text, "ig"); newLabel = this.model .get("label") - .replaceAll(regex, (matchedText) => { - return $("") + .replaceAll(regex, (matchedText) => + $("") .addClass(this.classes.highlightedText) .html(matchedText) - .prop("outerHTML"); - }); + .prop("outerHTML"), + ); // Label is unchanged. if (newLabel === this.model.get("label")) { diff --git a/src/js/views/maps/LegendView.js b/src/js/views/maps/LegendView.js deleted file mode 100644 index 31036bf4b..000000000 --- a/src/js/views/maps/LegendView.js +++ /dev/null @@ -1,555 +0,0 @@ -"use strict"; - -define([ - "jquery", - "underscore", - "backbone", - "d3", - "models/maps/AssetColorPalette", - "common/Utilities", - "text!templates/maps/legend.html", -], ($, _, Backbone, d3, AssetColorPalette, Utilities, Template) => { - /** - * @class LegendView - * @classdesc Creates a legend for a given Map Asset (Work In Progress). Currently - * supports making 'preview' legends for CesiumImagery assets and Cesium3DTileset - * assets (only for color palettes that are type 'categorical'). Eventually, will - * support full-sized legend for these, and other assets, and all types of color - * palettes (including 'continuous' and 'classified') - * @classcategory Views/Maps - * @name LegendView - * @augments Backbone.View - * @screenshot views/maps/LegendView.png - * @since 2.18.0 - * @constructs - */ - const LegendView = Backbone.View.extend( - /** @lends LegendView.prototype */ { - /** - * The type of View this is - * @type {string} - */ - type: "LegendView", - - /** - * The HTML classes to use for this view's element - * @type {string} - */ - className: "map-legend", - - /** - * The MapAsset model that this view uses - currently supports CesiumImagery and - * Cesium3DTileset models. - * @type {MapAsset} - */ - model: null, - - /** - * The primary HTML template for this view - * @type {Underscore.template} - */ - template: _.template(Template), - - /** - * The events this view will listen to and the associated function to call. - * @type {object} - */ - events: { - // 'event selector': 'function', - }, - - /** - * Which type of legend to show? Can be set to either 'full' for a complete legend - * with labels, title, and all color coding, or 'preview' for just a small - * thumbnail of the colors used in the full legend. - * @type {string} - */ - mode: "preview", - - /** - * For vector preview legends, the relative dimensions to use. The SVG's - * dimensions are set with a viewBox property only, so the height and width - * represent an aspect ratio rather than absolute size. - * @type {object} - * @property {number} previewSvgDimensions.width - The width of the entire SVG - * @property {number} previewSvgDimensions.height - The height of the entire SVG - * @property {number} squareSpacing - Maximum spacing between each of the squares - * in the preview legend. Squares will be spaced 20% closed than this when the - * legend is not hovered over. - */ - previewSvgDimensions: { - width: 160, - height: 45, - squareSpacing: 20, - }, - - /** - * Classes that are used to identify, or that are added to, the HTML elements that - * comprise this view. - * @type {object} - * @property {string} preview Additional class to add to legend that are the - * preview/thumbnail version - * @property {string} previewSVG The SVG element that holds the shapes with all - * the legend colours in the preview legend. - * @property {string} previewImg The image element that represents a thumbnail of - * image layers, in preview legends - * @property {string} tooltip Class added to tooltips used in preview legends - */ - classes: { - preview: "map-legend--preview", - previewSVG: "map-legend__svg--preview", - previewImg: "map-legend__img--preview", - tooltip: "map-tooltip", - }, - - /** - * Executed when a new LegendView is created - * @param {object} [options] - A literal object with options to pass to the view - */ - initialize(options) { - try { - // Get all the options and apply them to this view - if (typeof options === "object") { - for (const [key, value] of Object.entries(options)) { - this[key] = value; - } - } - } catch (e) { - console.log(`A LegendView failed to initialize. Error message: ${e}`); - } - }, - - /** - * Renders this view - * @returns {LegendView} Returns the rendered view element - */ - render() { - try { - if (!this.model) { - return; - } - - // Save a reference to this view - const view = this; - - // The color palette maps colors to attributes of the map asset - let colorPalette = null; - // For color palettes, - let paletteType = null; - const { mode } = this; - - // Insert the template into the view - this.$el.html(this.template({})); - - // Ensure the view's main element has the given class name - this.el.classList.add(this.className); - - // Add a modifier class if this is a preview of a legend - if (mode === "preview") { - this.el.classList.add(this.classes.preview); - } - - // Check for a color palette model in the Map Asset model. Even imagery layers - // may have a color palette configured, specifically to use to create a - // legend. - for (const attr in this.model.attributes) { - if (this.model.attributes[attr] instanceof AssetColorPalette) { - colorPalette = this.model.get(attr); - paletteType = colorPalette.get("paletteType"); - } - } - - if (mode === "preview") { - // For categorical vector color palettes, in preview mode - if (colorPalette && paletteType === "categorical") { - this.renderCategoricalPreviewLegend(colorPalette); - } else if (colorPalette && paletteType === "continuous") { - this.renderContinuousPreviewLegend(colorPalette); - } - // For imagery layers that do not have a color palette, in preview mode - else if (typeof this.model.getThumbnail === "function") { - if (!this.model.get("thumbnail")) { - this.listenToOnce(this.model, "change:thumbnail", function () { - this.renderImagePreviewLegend(this.model.get("thumbnail")); - }); - } else { - this.renderImagePreviewLegend(this.model.get("thumbnail")); - } - } - } - // TODO: - // - preview classified legend - // - full legends with labels, title, etc. - - return this; - } catch (error) { - console.log( - `There was an error rendering a Legend View` + - `. Error details: ${error}`, - ); - } - }, - - /** - * Inserts a thumbnail in image into this view - * @param {string} thumbnailURL A url to use for the src property of the thumbnail - * image - */ - renderImagePreviewLegend(thumbnailURL) { - try { - const img = new Image(); - img.src = thumbnailURL; - img.classList.add(this.classes.previewImg); - this.el.append(img); - } catch (error) { - console.log( - `There was an error rendering an image preview legend in a LegendView` + - `. Error details: ${error}`, - ); - } - }, - - /** - * Creates a preview legend for categorical color palettes and inserts it into the - * view - * @param {AssetColorPalette} colorPalette - The AssetColorPalette that maps - * feature attributes to colors, used to create the legend - */ - renderCategoricalPreviewLegend(colorPalette) { - try { - if (!colorPalette) { - return; - } - const view = this; - // Data to use in d3 - let data = colorPalette.get("colors").toJSON().reverse(); - - if (data.length === 0) { - return; - } - // The max width of the SVG, to be reduced if there are few colours - let { width } = this.previewSvgDimensions; - // The height of the SVG - const { height } = this.previewSvgDimensions; - // Height and width of the square is the height of the SVG, leaving some room - // for shadow to show - const squareSize = height * 0.92; - // Maximum spacing between squares. When not hovered, the squares will be - // spaced 80% of this value. - const { squareSpacing } = this.previewSvgDimensions; - // The maximum number of squares that can fit on the SVG without any spilling - // over - const maxNumSquares = Math.floor( - (width - squareSize) / squareSpacing + 1, - ); - - // If there are more colors than fit in the max width of the SVG space, only - // show the first n squares that will fit - if (data.length > maxNumSquares) { - data = data.slice(0, maxNumSquares); - } - // Add index to data for sorting later (also works as unique ID) - data.forEach((d, i) => { - d.i = i; - }); - - // Don't create an SVG that is wider than it need to be. - width = squareSize + (data.length - 1) * squareSpacing; - - // SVG element - const svg = this.createSVG({ - dropshadowFilter: true, - width, - height, - }); - - // Add the preview class and dropshadow to the SVG - svg.classed(this.classes.previewSVG, true); - svg.style("filter", "url(#dropshadow)"); - - // Calculates the placement of the square along x-axis, when SVG is hovered - // and when it's not - /** - * - * @param i - * @param hovered - */ - function getSquareX(i, hovered) { - const multiplier = hovered ? 1 : 0.8; - return width - squareSize - i * (squareSpacing * multiplier); - } - - // Draw the legend (d3) - const legendSquares = svg - .selectAll("rect") - .data(data) - .enter() - .append("rect") - .attr("x", (d, i) => getSquareX(i, false)) - .attr("height", squareSize) - .attr("width", squareSize) - .attr("rx", squareSize * 0.1) - .style( - "fill", - (d) => - `rgb(${d.color.red * 255},${d.color.green * 255},${d.color.blue * 255})`, - ) - .style("filter", "url(#dropshadow)"); - - // For legend with multiple colours, show a tooltip with the value/label when - // the user hovers over a square. Also bring that square to the fore-front of - // the legend when hovered. Only when MapAsset is visible though. - if (data.length > 1) { - // Space the squares further apart when they are hovered over - svg - .on("mouseenter", () => { - if (view.model.get("visible")) { - legendSquares - .transition() - .duration(250) - .attr("x", (d, i) => getSquareX(i, true)); - } - }) - .on("mouseleave", () => { - legendSquares - .transition() - .duration(200) - .attr("x", (d, i) => getSquareX(i, false)); - }); - - legendSquares - .on("mouseenter", function (d) { - // Bring the hovered element to the front, while keeping other - // legendSquares in order - legendSquares.sort((a, b) => d3.ascending(a.i, b.i)); - this.parentNode.appendChild(this); - // Show tooltip - if (d.label || d.value || d.value === 0) { - $(this) - .tooltip({ - placement: "bottom", - trigger: "manual", - title: d.label || d.value, - container: view.$el, - animation: false, - template: `
`, - }) - .tooltip("show"); - } - }) - // Hide tooltip and return squares to regular z-ordering - .on("mouseleave", function (d) { - $(this).tooltip("destroy"); - legendSquares.sort((a, b) => d3.ascending(a.i, b.i)); - }); - } - } catch (error) { - console.log( - `There was an error creating a categorical legend preview in a LegendView` + - `. Error details: ${error}`, - ); - } - }, - - /** - * Creates a preview legend for continuous color palettes and inserts it into the - * view - * @param {AssetColorPalette} colorPalette - The AssetColorPalette that maps - * feature attributes to colors, used to create the legend - */ - renderContinuousPreviewLegend(colorPalette) { - try { - if (!colorPalette) { - return; - } - const view = this; - // Data to use in d3 - let data = colorPalette.get("colors").toJSON(); - // The max width of the SVG - const { width } = this.previewSvgDimensions; - // The height of the SVG - const { height } = this.previewSvgDimensions; - // Height of the gradient rectangle, leaving some room for the drop shadow - const gradientHeight = height * 0.92; - - // A unique ID for the gradient - const gradientId = `gradient-${view.cid}`; - - // Calculate the rounding precision we should use based on the - // range of the data. This determines how each value in the legend - // is displayed in the tooltip on mouseover. See the - // rect.on('mousemove'... function, below - data = data.sort((a, b) => a.value - b.value); - const min = data[0].value; - const max = data[data.length - 1].value; - const range = max - min; - const numDecimalPlaces = Utilities.getNumDecimalPlaces(range); - - // SVG element - const svg = this.createSVG({ - dropshadowFilter: false, - width, - height, - }); - - // Add the preview class and dropshadow to the SVG - svg.classed(this.classes.previewSVG, true); - svg.style("filter", "url(#dropshadow)"); - - // Create a gradient using the data - const gradient = svg - .append("defs") - .append("linearGradient") - .attr("id", gradientId) - .attr("x1", "0%") - .attr("y1", "0%"); - - const getOffset = function (d, data) { - return `${((d.value - min) / range) * 100}%`; - }; - const getStopColor = function (d) { - const r = d.color.red * 255; - const g = d.color.green * 255; - const b = d.color.blue * 255; - return `rgb(${r},${g},${b})`; - }; - - // Add the gradient stops - data.forEach((d, i) => { - gradient - .append("stop") - // offset should be relative to the value in the data - .attr("offset", getOffset(d, data)) - .attr("stop-color", getStopColor(d)); - }); - - // Create the rectangle - const rect = svg - .append("rect") - .attr("x", 0) - .attr("y", 0) - .attr("width", width) - .attr("height", gradientHeight) - .attr("rx", gradientHeight * 0.1) - .style("fill", `url(#${gradientId})`); - - // Create a proxy element to attach the tooltip to, so that we can move the - // tooltip to follow the mouse (by moving the proxy element to follow the mouse) - const proxyEl = svg.append("rect").attr("y", gradientHeight); - - rect - .on("mousemove", function () { - if (view.model.get("visible")) { - // Get the coordinates of the mouse relative to the rectangle - let xMouse = d3.mouse(this)[0]; - if (xMouse < 0) { - xMouse = 0; - } - if (xMouse > width) { - xMouse = width; - } - // Get the relative position of the mouse to the gradient - const relativePosition = xMouse / width; - // Get the value at the relative position by interpolating the data - let value = d3.interpolate( - data[0].value, - data[data.length - 1].value, - )(relativePosition); - // Show tooltip with the value - if (value || value === 0) { - // Round or show in scientific notation - if (numDecimalPlaces !== null) { - value = value.toFixed(numDecimalPlaces); - } else { - value = value.toExponential(2).toString(); - } - // Move the proxy element to follow the mouse - proxyEl.attr("x", xMouse); - // Attach the tooltip to the proxy element. Tooltip needs to be - // refreshed every time the mouse moves - $(proxyEl).tooltip("destroy"); - $(proxyEl) - .tooltip({ - placement: "bottom", - trigger: "manual", - title: value, - container: view.$el, - animation: false, - template: `
`, - }) - .tooltip("show"); - } - } - }) - // Hide tooltip - .on("mouseleave", () => { - $(proxyEl).tooltip("destroy"); - }); - } catch (error) { - console.log( - `There was an error rendering a continuous preview legend in a LegendView` + - `. Error details: ${error}`, - ); - } - }, - - /** - * Creates an SVG element and inserts it into the view - * @param {object} options Used to configure parts of the SVG - * @property {boolean} options.dropshadowFilter Set to true to create a filter - * element that creates a dropshadow behind any element it is applied to. It can - * be added to child elements of the SVG by setting a `filter: url(#dropshadow);` - * style rule on the child. - * @property {number} options.height The relative height of the SVG (for the - * viewBox property) - * @property {number} options.width The relative width of the SVG (for the viewBox - * property) - * @returns {SVG} Returns the SVG element that is in the view - */ - createSVG(options = {}) { - try { - // Create an SVG to hold legend elements - const container = this.el; - const { width } = options; - const { height } = options; - - const svg = d3 - .select(container) - .append("svg") - .attr("preserveAspectRatio", "xMidYMid") - .attr("viewBox", [0, 0, width, height]); - - if (options.dropshadowFilter) { - const filterText = ` - - - - - - - - - - `; - - const filterEl = new DOMParser().parseFromString( - `${filterText}`, - "application/xml", - ).documentElement.firstChild; - - svg.node().appendChild(document.importNode(filterEl, true)); - } - - return svg; - } catch (error) { - console.log( - `There was an error creating an SVG in a LegendView` + - `. Error details: ${error}`, - ); - } - }, - }, - ); - - return LegendView; -});