diff --git a/package-lock.json b/package-lock.json index a66e77eb..3ea1d82b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "@angular/cli": "^14.2.13", "@angular/compiler-cli": "^14.2.12", "@angular/language-service": "^14.2.12", - "@biesbjerg/ngx-translate-extract-marker": "1.0.0", + "@colsen1991/ngx-translate-extract-marker": "^2.0.8", "@gisaia/ngx-translate-extract": "8.2.0", "@types/jasmine": "~3.10.0", "@types/jasminewd2": "~2.0.2", @@ -37,6 +37,7 @@ "@typescript-eslint/eslint-plugin-tslint": "^5.6.0", "@typescript-eslint/parser": "5.3.0", "eslint": "^8.2.0", + "jasmine": "^5.1.0", "jasmine-core": "~3.10.0", "jasmine-spec-reporter": "~4.2.1", "karma": "^6.3.14", @@ -2525,6 +2526,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@biesbjerg/ngx-translate-extract-marker/-/ngx-translate-extract-marker-1.0.0.tgz", "integrity": "sha512-GlCBQKmFE+b+qfIO0aGvuRc4LJVSfK27K2QQFXZLP55/w28iiq/q2CnBS8ya+4l+hapm7U3QPtFoZu9lmbUuew==", + "peer": true, "dependencies": { "tslib": "^1.9.0" } @@ -2532,7 +2534,8 @@ "node_modules/@biesbjerg/ngx-translate-extract-marker/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "peer": true }, "node_modules/@braintree/sanitize-url": { "version": "6.0.4", @@ -2548,6 +2551,23 @@ "node": ">=0.1.90" } }, + "node_modules/@colsen1991/ngx-translate-extract-marker": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@colsen1991/ngx-translate-extract-marker/-/ngx-translate-extract-marker-2.0.8.tgz", + "integrity": "sha512-nYcjHifvXHtF3J4thQE6Lmu+AGNORgpCLYx55PY+XxzWRWfziuKIHhoIbNf9vwc9mMOZakJqfAjvpO+pN9AWdw==", + "dev": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=16", + "npm": ">=8" + }, + "peerDependencies": { + "@angular/common": ">=13.0.0", + "@angular/core": ">=13.0.0" + } + }, "node_modules/@cspotcode/source-map-consumer": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", @@ -3168,6 +3188,50 @@ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -3603,6 +3667,16 @@ "typescript": "^3 || ^4" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@rollup/plugin-json": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", @@ -10540,6 +10614,34 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -12242,15 +12344,29 @@ "integrity": "sha512-dPwFs3fHGJAwE9APaJB3UpEaxoTAG6N1wraKv48SjDvOP+CMzUmFbW+TWpyKVSpjRmpCsMdCoBFiLRAFsTkulA==", "license": "MIT" }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jasmine": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", - "integrity": "sha512-KbdGQTf5jbZgltoHs31XGiChAPumMSY64OZMWLNYnEnMfG5uwGBhffePwuskexjT+/Jea/gU3qAU8344hNohSw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-5.4.0.tgz", + "integrity": "sha512-E2u4ylX5tgGYvbynImU6EUBKKrSVB1L72FEPjGh4M55ov1VsxR26RA2JU91L9YSPFgcjo4mCLyKn/QXvEYGBkA==", "dev": true, "dependencies": { - "exit": "^0.1.2", - "glob": "^7.0.6", - "jasmine-core": "~2.8.0" + "glob": "^10.2.2", + "jasmine-core": "~5.4.0" }, "bin": { "jasmine": "bin/jasmine.js" @@ -12271,52 +12387,54 @@ "colors": "1.1.2" } }, - "node_modules/jasmine/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/jasmine/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/jasmine/node_modules/jasmine-core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", - "integrity": "sha512-SNkOkS+/jMZvLhuSx1fjhcNWUC/KG6oVyFUGkSBEr9n1axSNduWU8GlI7suaHXr4yxjet6KjrUZxUTE5WzzWwQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.4.0.tgz", + "integrity": "sha512-T4fio3W++llLd7LGSGsioriDHgWyhoL6YTu4k37uwJLF7DzOzspz7mNxRoM3cQdLWtL/ebazQpIf/yZGJx/gzg==", "dev": true }, "node_modules/jasmine/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jasmine/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" } }, "node_modules/jasminewd2": { @@ -15318,6 +15436,12 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, "node_modules/pacote": { "version": "13.6.2", "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.6.2.tgz", @@ -15762,6 +15886,37 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/path-to-regexp": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", @@ -16818,6 +16973,26 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/protractor/node_modules/jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha512-KbdGQTf5jbZgltoHs31XGiChAPumMSY64OZMWLNYnEnMfG5uwGBhffePwuskexjT+/Jea/gU3qAU8344hNohSw==", + "dev": true, + "dependencies": { + "exit": "^0.1.2", + "glob": "^7.0.6", + "jasmine-core": "~2.8.0" + }, + "bin": { + "jasmine": "bin/jasmine.js" + } + }, + "node_modules/protractor/node_modules/jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha512-SNkOkS+/jMZvLhuSx1fjhcNWUC/KG6oVyFUGkSBEr9n1axSNduWU8GlI7suaHXr4yxjet6KjrUZxUTE5WzzWwQ==", + "dev": true + }, "node_modules/protractor/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -18691,6 +18866,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -18727,6 +18923,28 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -20623,6 +20841,77 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -22536,6 +22825,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@biesbjerg/ngx-translate-extract-marker/-/ngx-translate-extract-marker-1.0.0.tgz", "integrity": "sha512-GlCBQKmFE+b+qfIO0aGvuRc4LJVSfK27K2QQFXZLP55/w28iiq/q2CnBS8ya+4l+hapm7U3QPtFoZu9lmbUuew==", + "peer": true, "requires": { "tslib": "^1.9.0" }, @@ -22543,7 +22833,8 @@ "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "peer": true } } }, @@ -22558,6 +22849,15 @@ "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true }, + "@colsen1991/ngx-translate-extract-marker": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@colsen1991/ngx-translate-extract-marker/-/ngx-translate-extract-marker-2.0.8.tgz", + "integrity": "sha512-nYcjHifvXHtF3J4thQE6Lmu+AGNORgpCLYx55PY+XxzWRWfziuKIHhoIbNf9vwc9mMOZakJqfAjvpO+pN9AWdw==", + "dev": true, + "requires": { + "tslib": "^2.3.0" + } + }, "@cspotcode/source-map-consumer": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", @@ -22936,6 +23236,37 @@ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -23266,6 +23597,13 @@ "esquery": "^1.0.1" } }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true + }, "@rollup/plugin-json": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", @@ -28412,6 +28750,24 @@ "is-callable": "^1.1.3" } }, + "foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + } + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -29657,55 +30013,60 @@ "resolved": "https://registry.npmjs.org/iv-viewer/-/iv-viewer-2.0.1.tgz", "integrity": "sha512-dPwFs3fHGJAwE9APaJB3UpEaxoTAG6N1wraKv48SjDvOP+CMzUmFbW+TWpyKVSpjRmpCsMdCoBFiLRAFsTkulA==" }, + "jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, "jasmine": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", - "integrity": "sha512-KbdGQTf5jbZgltoHs31XGiChAPumMSY64OZMWLNYnEnMfG5uwGBhffePwuskexjT+/Jea/gU3qAU8344hNohSw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-5.4.0.tgz", + "integrity": "sha512-E2u4ylX5tgGYvbynImU6EUBKKrSVB1L72FEPjGh4M55ov1VsxR26RA2JU91L9YSPFgcjo4mCLyKn/QXvEYGBkA==", "dev": true, "requires": { - "exit": "^0.1.2", - "glob": "^7.0.6", - "jasmine-core": "~2.8.0" + "glob": "^10.2.2", + "jasmine-core": "~5.4.0" }, "dependencies": { - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" } }, "jasmine-core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", - "integrity": "sha512-SNkOkS+/jMZvLhuSx1fjhcNWUC/KG6oVyFUGkSBEr9n1axSNduWU8GlI7suaHXr4yxjet6KjrUZxUTE5WzzWwQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.4.0.tgz", + "integrity": "sha512-T4fio3W++llLd7LGSGsioriDHgWyhoL6YTu4k37uwJLF7DzOzspz7mNxRoM3cQdLWtL/ebazQpIf/yZGJx/gzg==", "dev": true }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" } + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true } } }, @@ -31875,6 +32236,12 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, + "package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, "pacote": { "version": "13.6.2", "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.6.2.tgz", @@ -32224,6 +32591,30 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "requires": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true + } + } + }, "path-to-regexp": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", @@ -32888,6 +33279,23 @@ "path-is-absolute": "^1.0.0" } }, + "jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha512-KbdGQTf5jbZgltoHs31XGiChAPumMSY64OZMWLNYnEnMfG5uwGBhffePwuskexjT+/Jea/gU3qAU8344hNohSw==", + "dev": true, + "requires": { + "exit": "^0.1.2", + "glob": "^7.0.6", + "jasmine-core": "~2.8.0" + } + }, + "jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha512-SNkOkS+/jMZvLhuSx1fjhcNWUC/KG6oVyFUGkSBEr9n1axSNduWU8GlI7suaHXr4yxjet6KjrUZxUTE5WzzWwQ==", + "dev": true + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -34360,6 +34768,25 @@ } } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + } + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -34375,6 +34802,23 @@ } } }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + } + } + }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -35776,6 +36220,60 @@ } } }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 7acdadac..d45546bf 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@angular/cli": "^14.2.13", "@angular/compiler-cli": "^14.2.12", "@angular/language-service": "^14.2.12", - "@biesbjerg/ngx-translate-extract-marker": "1.0.0", + "@colsen1991/ngx-translate-extract-marker": "^2.0.8", "@gisaia/ngx-translate-extract": "8.2.0", "@types/jasmine": "~3.10.0", "@types/jasminewd2": "~2.0.2", @@ -58,6 +58,7 @@ "@typescript-eslint/eslint-plugin-tslint": "^5.6.0", "@typescript-eslint/parser": "5.3.0", "eslint": "^8.2.0", + "jasmine": "^5.1.0", "jasmine-core": "~3.10.0", "jasmine-spec-reporter": "~4.2.1", "karma": "^6.3.14", diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 9fb6ae76..1b2ed372 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -17,31 +17,104 @@ * under the License. */ -import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { Dialog, DIALOG_SCROLL_STRATEGY } from '@angular/cdk/dialog'; +import { HttpClientModule } from '@angular/common/http'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MAT_DIALOG_SCROLL_STRATEGY, MatDialog } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { RouterTestingModule } from '@angular/router/testing'; +import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { ArlasColorService, ColorGeneratorLoader } from 'arlas-web-components'; +import { ArlasCollaborativesearchService, ArlasCollectionService, + ArlasConfigService, ArlasSettingsService, ArlasStartupService } from 'arlas-wui-toolkit'; +import { of } from 'rxjs'; import { ArlasWuiComponent } from './app.component'; +import { ContributorService } from './services/contributors.service'; +import { MapService } from './services/map.service'; +import { ResultlistService } from './services/resultlist.service'; +import { VisualizeService } from './services/visualize.service'; describe('ArlasWuiComponent', () => { let component: ArlasWuiComponent; let fixture: ComponentFixture; - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ + beforeEach(async () => { + const mockArlasStartupService = jasmine.createSpyObj('ArlasStartupService', [], { + shouldRunApp: true, + emptyMode: false, + contributorRegistry: new Map(), + collectionsMap: new Map() + }); - ], - declarations: [ + const mockSettingsService = jasmine.createSpyObj('ArlasSettingsService', ['getHistogramMaxBucket']); + mockSettingsService.getHistogramMaxBucket.and.returnValue(); + + const mockContributorService = jasmine.createSpyObj('ContributorService', ['getMapContributors']); + mockContributorService.getMapContributors.and.returnValue([]); + + const mockColorGeneratorLoader = jasmine.createSpyObj('ColorGeneratorLoader', [], { + changekeysToColors$: of() + }); + + await TestBed.configureTestingModule({ + declarations: [ArlasWuiComponent], + imports: [ + TranslateModule.forRoot({ + loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } + }), + MatTooltipModule, + /** Needed for ResultlistService */ + RouterTestingModule, + HttpClientModule + /** End */ ], providers: [ + ResultlistService, + MapService, + ArlasColorService, + ArlasCollaborativesearchService, + ArlasConfigService, + { + provide: ArlasStartupService, + useValue: mockArlasStartupService + }, + { + provide: ArlasSettingsService, + useValue: mockSettingsService + }, + { + provide: ContributorService, + useValue: mockContributorService + }, + /** Needed for ResultlistService */ + MatSnackBar, + VisualizeService, + MatDialog, + { + provide: MAT_DIALOG_SCROLL_STRATEGY, + useValue: {} + }, + Dialog, + { + provide: DIALOG_SCROLL_STRATEGY, + useValue: {} + }, + /** End */ + { + provide: ColorGeneratorLoader, + useValue: mockColorGeneratorLoader + }, + ArlasCollectionService ] }).compileComponents(); + }); - })); - - beforeEach(async(() => { + beforeEach(() => { fixture = TestBed.createComponent(ArlasWuiComponent); component = fixture.componentInstance; fixture.detectChanges(); - })); + }); it('should create', () => { expect(component).toBeTruthy(); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 2a216e28..cbf51134 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -16,8 +16,15 @@ * specific language governing permissions and limitations * under the License. */ - -import { Component, OnInit } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; +import { CollectionReferenceParameters } from 'arlas-api'; +import { ArlasColorService } from 'arlas-web-components'; +import { ResultListContributor } from 'arlas-web-contributors'; +import { AnalyticsService, ArlasCollaborativesearchService, ArlasConfigService, ArlasStartupService } from 'arlas-wui-toolkit'; +import { Subject, takeUntil, zip } from 'rxjs'; +import { ContributorService } from './services/contributors.service'; +import { MapService } from './services/map.service'; +import { ResultlistService } from './services/resultlist.service'; @Component({ selector: 'arlas-root', @@ -26,13 +33,105 @@ import { Component, OnInit } from '@angular/core'; }) export class ArlasWuiComponent implements OnInit { + public collections = new Array(); + public collectionToDescription = new Map(); + public resultlistContributors: Array = new Array(); + /** + * @Input : Angular + * List of ResultList tabs to hide + */ + @Input() public hiddenResultlistTabs: string[] = []; + /** + * @Input : Angular + * List of Analytics tabs to hide + */ + @Input() public hiddenAnalyticsTabs: string[] = []; + + /** Destroy subscriptions */ + private _onDestroy$ = new Subject(); + + public constructor( + private arlasStartupService: ArlasStartupService, + private configService: ArlasConfigService, + private resultlistService: ResultlistService, + private contributorService: ContributorService, + private mapService: MapService, + private colorService: ArlasColorService, + private collaborativeService: ArlasCollaborativesearchService, + private analyticsService: AnalyticsService + ) { } + public ngOnInit(): void { const loadingGif = document.querySelector('.gif'); if (!!loadingGif) { loadingGif.remove(); } - } -} + // Initialize the contributors and app wide services + if (this.arlasStartupService.shouldRunApp && !this.arlasStartupService.emptyMode) { + this.collections = [...new Set(Array.from(this.collaborativeService.registry.values()).map(c => c.collection))]; + /** Map */ + // Set MapContributors + const mapContributors = []; + this.contributorService.getMapContributors().forEach(mapContrib => { + mapContrib.colorGenerator = this.colorService.colorGenerator; + if (!!this.resultlistContributors) { + const resultlistContrbutor: ResultListContributor = this.resultlistContributors + .find(resultlistContrib => resultlistContrib.collection === mapContrib.collection); + if (!!resultlistContrbutor) { + mapContrib.searchSize = resultlistContrbutor.pageSize; + mapContrib.searchSort = resultlistContrbutor.sort; + } else { + mapContrib.searchSize = 50; + } + } + mapContributors.push(mapContrib); + }); + this.mapService.setContributors(mapContributors); + /** Resultlist */ + /** Retrieve displayable resultlists */ + const hiddenListsTabsSet = new Set(this.hiddenResultlistTabs); + const allResultlists = this.configService.getValue('arlas.web.components.resultlists'); + const allContributors = this.configService.getValue('arlas.web.contributors'); + const resultListsConfig = !!allResultlists ? allResultlists.filter(a => { + const contId = a.contributorId; + const tab = allContributors.find(c => c.identifier === contId).name; + return !hiddenListsTabsSet.has(tab); + }) : []; + + const ids = new Set(resultListsConfig.map(c => c.contributorId)); + this.arlasStartupService.contributorRegistry.forEach((v, k) => { + if (v instanceof ResultListContributor) { + v.updateData = ids.has(v.identifier); + this.resultlistContributors.push(v); + } + }); + this.resultlistService.setContributors(this.resultlistContributors, resultListsConfig); + + zip(...this.collections.map(c => this.collaborativeService.describe(c))) + .pipe(takeUntil(this._onDestroy$)) + .subscribe(cdrs => { + cdrs.forEach(cdr => { + this.collectionToDescription.set(cdr.collection_name, cdr.params); + }); + this.resultlistService.setCollectionsDescription(this.collectionToDescription); + if (!!this.mapService.mapComponent) { + const bounds = (this.mapService.mapComponent.map).getBounds(); + if (!!bounds) { + (this.mapService.mapComponent.map).fitBounds(bounds, { duration: 0 }); + } + } + if (this.resultlistContributors.length > 0) { + this.resultlistContributors.forEach(c => c.sort = this.collectionToDescription.get(c.collection).id_path); + } + }); + + /** Analytics */ + const hiddenAnalyticsTabsSet = new Set(this.hiddenAnalyticsTabs); + const allAnalytics = this.arlasStartupService.analytics; + this.analyticsService.initializeGroups(!!allAnalytics ? allAnalytics.filter(a => !hiddenAnalyticsTabsSet.has(a.tab)) : []); + } + } +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 7be1dc18..a7248278 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -45,7 +45,7 @@ import { RouterModule } from '@angular/router'; import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; import { HistogramModule, MapglImportModule, MapglModule, MapglSettingsModule, - ResultsModule, FormatNumberModule, BboxGeneratorModule + ResultsModule, FormatNumberModule, BboxGeneratorModule, GetValueModule } from 'arlas-web-components'; import { ArlasCollectionService, @@ -69,15 +69,20 @@ import { ArlasWuiRootComponent } from './components/arlas-wui-root/arlas-wui-roo import { ConfigsListComponent } from './components/configs-list/configs-list.component'; import { GeocodingComponent } from './components/geocoding/geocoding.component'; import { LeftMenuComponent } from './components/left-menu/left-menu.component'; -import { AoiDimensionComponent } from './components/map/aoi-dimensions/aoi-dimensions.component'; -import { RoundKilometer, SquareKilometer } from './components/map/aoi-dimensions/aoi-dimensions.pipes'; +import { AoiDimensionComponent } from './components/arlas-map/aoi-dimensions/aoi-dimensions.component'; +import { RoundKilometer, SquareKilometer } from './components/arlas-map/aoi-dimensions/aoi-dimensions.pipes'; import { ContributorService } from './services/contributors.service'; -import { DynamicComponentService } from './services/dynamicComponent.service'; import { VisualizeService } from './services/visualize.service'; import { ArlasTranslateLoader, ArlasWalkthroughLoader } from './tools/customLoader'; import { LazyLoadImageHooks } from './tools/lazy-loader'; import { LAZYLOAD_IMAGE_HOOKS, LazyLoadImageModule } from 'ng-lazyload-image'; import { RastersManagerComponent } from './components/map/raster-layers-manager/rasters-manager.component'; +import { ArlasMapComponent } from './components/arlas-map/arlas-map.component'; +import { ArlasListComponent } from './components/arlas-list/arlas-list.component'; +import { GetResultlistConfigPipe } from './pipes/get-resultlist-config.pipe'; +import { MapService } from './services/map.service'; +import { ResultlistService } from './services/resultlist.service'; +import { ArlasAnalyticsComponent } from './components/arlas-analytics/arlas-analytics.component'; @NgModule({ @@ -90,7 +95,11 @@ import { RastersManagerComponent } from './components/map/raster-layers-manager/ RoundKilometer, SquareKilometer, GeocodingComponent, - RastersManagerComponent + RastersManagerComponent, + ArlasMapComponent, + ArlasListComponent, + GetResultlistConfigPipe, + ArlasAnalyticsComponent ], exports: [ AoiDimensionComponent, @@ -99,7 +108,11 @@ import { RastersManagerComponent } from './components/map/raster-layers-manager/ LeftMenuComponent, ConfigsListComponent, RoundKilometer, - SquareKilometer + SquareKilometer, + GeocodingComponent, + ArlasMapComponent, + ArlasListComponent, + GetResultlistConfigPipe ], imports: [ BrowserModule, @@ -154,12 +167,13 @@ import { RastersManagerComponent } from './components/map/raster-layers-manager/ }), ArlasTaggerModule, LoginModule, - LazyLoadImageModule + LazyLoadImageModule, + GetValueModule ], providers: [ - ContributorService, - DynamicComponentService, VisualizeService, + MapService, + ResultlistService, { provide: LAZYLOAD_IMAGE_HOOKS, useClass: LazyLoadImageHooks @@ -170,7 +184,8 @@ import { RastersManagerComponent } from './components/map/raster-layers-manager/ deps: [AuthentificationService, ArlasIamService, ArlasSettingsService], multi: true }, - ArlasCollectionService + ArlasCollectionService, + ContributorService ], bootstrap: [ArlasWuiComponent], entryComponents: [] diff --git a/src/app/components/arlas-analytics/arlas-analytics.component.html b/src/app/components/arlas-analytics/arlas-analytics.component.html new file mode 100644 index 00000000..b2237769 --- /dev/null +++ b/src/app/components/arlas-analytics/arlas-analytics.component.html @@ -0,0 +1,7 @@ +
+ + + +
\ No newline at end of file diff --git a/src/app/components/arlas-analytics/arlas-analytics.component.scss b/src/app/components/arlas-analytics/arlas-analytics.component.scss new file mode 100644 index 00000000..54e717cc --- /dev/null +++ b/src/app/components/arlas-analytics/arlas-analytics.component.scss @@ -0,0 +1,24 @@ +/* + * Licensed to Gisaïa under one or more contributor + * license agreements. See the NOTICE.txt file distributed with + * this work for additional information regarding copyright + * ownership. Gisaïa licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +.arlas-analytics--container { + height: 100%; + width: 100%; + overflow: auto; +} \ No newline at end of file diff --git a/src/app/components/arlas-analytics/arlas-analytics.component.spec.ts b/src/app/components/arlas-analytics/arlas-analytics.component.spec.ts new file mode 100644 index 00000000..142c511a --- /dev/null +++ b/src/app/components/arlas-analytics/arlas-analytics.component.spec.ts @@ -0,0 +1,65 @@ +import { Dialog, DIALOG_SCROLL_STRATEGY } from '@angular/cdk/dialog'; +import { HttpClientModule } from '@angular/common/http'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MAT_DIALOG_SCROLL_STRATEGY, MatDialog } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { RouterTestingModule } from '@angular/router/testing'; +import { ArlasCollaborativesearchService, ArlasCollectionService, ArlasStartupService } from 'arlas-wui-toolkit'; +import { VisualizeService } from '../../services/visualize.service'; +import { ArlasAnalyticsComponent } from './arlas-analytics.component'; +import { TranslateModule, TranslateLoader, TranslateFakeLoader } from '@ngx-translate/core'; +import { Overlay } from '@angular/cdk/overlay'; +import { ResultlistService } from '@services/resultlist.service'; + +describe('ArlasAnalyticsComponent', () => { + let component: ArlasAnalyticsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + const mockArlasStartupService = jasmine.createSpyObj('ArlasStartupService', [], { + shouldRunApp: true, + emptyMode: false, + contributorRegistry: new Map(), + collectionsMap: new Map() + }); + + await TestBed.configureTestingModule({ + declarations: [ ArlasAnalyticsComponent ], + imports: [ + RouterTestingModule, + HttpClientModule, + TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } }) + ], + providers: [ + ArlasCollaborativesearchService, + { + provide: ArlasStartupService, + useValue: mockArlasStartupService + }, + MatSnackBar, + VisualizeService, + MatDialog, + { + provide: MAT_DIALOG_SCROLL_STRATEGY, + useValue: {} + }, + Dialog, + { + provide: DIALOG_SCROLL_STRATEGY, + useValue: {} + }, + Overlay, + ArlasCollectionService + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ArlasAnalyticsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/arlas-analytics/arlas-analytics.component.ts b/src/app/components/arlas-analytics/arlas-analytics.component.ts new file mode 100644 index 00000000..67a70aac --- /dev/null +++ b/src/app/components/arlas-analytics/arlas-analytics.component.ts @@ -0,0 +1,66 @@ +/* + * Licensed to Gisaïa under one or more contributor + * license agreements. See the NOTICE.txt file distributed with + * this work for additional information regarding copyright + * ownership. Gisaïa licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Component, Input, OnInit } from '@angular/core'; +import { ResultlistService } from '@services/resultlist.service'; +import { AnalyticsContributor } from 'arlas-web-contributors'; +import { AnalyticsService, ArlasConfigService, ArlasStartupService } from 'arlas-wui-toolkit'; + +@Component({ + selector: 'arlas-analytics', + templateUrl: './arlas-analytics.component.html', + styleUrls: ['./arlas-analytics.component.scss'] +}) +export class ArlasAnalyticsComponent implements OnInit { + /** + * @Input : Angular + * Whether to show the analytics menu inside of this component. Useful for multi-windows views + */ + @Input() public showMenu = false; + + public analyticsContributor: AnalyticsContributor; + public spinner: { show: boolean; diameter: string; color: string; strokeWidth: number; }; + public showIndicators = false; + + public constructor( + protected analyticsService: AnalyticsService, + protected arlasStartupService: ArlasStartupService, + private configService: ArlasConfigService, + protected resultlistService: ResultlistService + ) { + } + + public ngOnInit(): void { + if (this.arlasStartupService.shouldRunApp && !this.arlasStartupService.emptyMode) { + this.setSpinner(); + if (this.configService.getValue('arlas.web.options.indicators')) { + this.showIndicators = true; + } + } + } + + private setSpinner() { + const spinnerConfiguration = this.configService.getValue('arlas.web.options.spinner'); + if (!!spinnerConfiguration) { + this.spinner = spinnerConfiguration; + } else { + this.spinner = { show: false, diameter: '60', color: 'accent', strokeWidth: 5 }; + } + } +} diff --git a/src/app/components/arlas-list/arlas-list.component.html b/src/app/components/arlas-list/arlas-list.component.html new file mode 100644 index 00000000..04084bd6 --- /dev/null +++ b/src/app/components/arlas-list/arlas-list.component.html @@ -0,0 +1,55 @@ + + + + + + {{(list | getResultlistConfig)?.options?.icon}} + + + {{list.name | translate}} + + +
+ + +
+
+
\ No newline at end of file diff --git a/src/app/components/arlas-list/arlas-list.component.scss b/src/app/components/arlas-list/arlas-list.component.scss new file mode 100644 index 00000000..1cea3109 --- /dev/null +++ b/src/app/components/arlas-list/arlas-list.component.scss @@ -0,0 +1,51 @@ +/* + * Licensed to Gisaïa under one or more contributor + * license agreements. See the NOTICE.txt file distributed with + * this work for additional information regarding copyright + * ownership. Gisaïa licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + @import "../../../styles/variables.scss"; + +.arlas-progression { + width: 100%; + position: absolute; + top: 0; +} + +.result-list-tab-group { + ::ng-deep .mat-tab-label { + min-width: 60px; + } + + ::ng-deep .mat-tab-label-content { + display: flex; + justify-content: space-between; + width: 100%; + gap: 0 10px; + } + + ::ng-deep .mat-tab-label-content .name { + flex: 5; + } + + .list__container { + // 50px is the height of the tab group + height: calc(100vh - 50px - $top-menu-height); + overflow: hidden; + } +} + + diff --git a/src/app/components/arlas-list/arlas-list.component.spec.ts b/src/app/components/arlas-list/arlas-list.component.spec.ts new file mode 100644 index 00000000..2f4eee3c --- /dev/null +++ b/src/app/components/arlas-list/arlas-list.component.spec.ts @@ -0,0 +1,35 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { VisualizeService } from 'app/services/visualize.service'; +import { ArlasToolKitModule, ArlasToolkitSharedModule } from 'arlas-wui-toolkit'; +import { ArlasListComponent } from './arlas-list.component'; + +describe('ArlasListComponent', () => { + let component: ArlasListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ArlasListComponent ], + imports: [ + ArlasToolKitModule, + TranslateModule.forRoot({ + loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } }), + ArlasToolkitSharedModule + ], + providers: [ + VisualizeService + ], + teardown: { destroyAfterEach: false } + }) + .compileComponents(); + + fixture = TestBed.createComponent(ArlasListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/arlas-list/arlas-list.component.ts b/src/app/components/arlas-list/arlas-list.component.ts new file mode 100644 index 00000000..ceb98757 --- /dev/null +++ b/src/app/components/arlas-list/arlas-list.component.ts @@ -0,0 +1,130 @@ +/* + * Licensed to Gisaïa under one or more contributor + * license agreements. See the NOTICE.txt file distributed with + * this work for additional information regarding copyright + * ownership. Gisaïa licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { AfterViewInit, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { MatTabGroup } from '@angular/material/tabs'; +import { ResultlistService } from '@services/resultlist.service'; +import { Action, Column, ElementIdentifier, Item, ModeEnum, PageQuery, ResultListComponent } from 'arlas-web-components'; +import { ResultListContributor } from 'arlas-web-contributors'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'arlas-list', + templateUrl: './arlas-list.component.html', + styleUrls: ['./arlas-list.component.scss'] +}) +export class ArlasListComponent implements OnInit, OnDestroy, AfterViewInit { + + /** + * @Input : Angular + * @description Width in pixels of the preview result list + */ + @Input() public previewListWidth = 125; + /** + * @Input : Angular + * @description Width in pixels of the result list + */ + @Input() public listWidth = 500; + /** + * @Input : Angular + * @description Number of columns in the grid result list + */ + @Input() public resultListGridColumns = 4; + + @ViewChild('resultList', { static: false }) public resultListComponent: ResultListComponent; + @ViewChild('tabsList', { static: false }) public tabsList: MatTabGroup; + + /** Destroy subscriptions */ + private _onDestroy$ = new Subject(); + + public constructor( + protected resultlistService: ResultlistService + ) { } + + public ngOnInit(): void { } + + public ngAfterViewInit(): void { + this.tabsList.selectedIndexChange?.pipe(takeUntil(this._onDestroy$)).subscribe(e => { + this.resultlistService.selectedListTabIndex = e; + }); + } + + public ngOnDestroy(): void { + this.resultlistService.unsetListComponent(); + this._onDestroy$.next(true); + this._onDestroy$.complete(); + } + + public onListLoaded(loaded: boolean) { + if (loaded) { + setTimeout(() => { + this.resultlistService.setListComponent(this.resultListComponent); + }, 0); + } + } + + public changeListResultMode(mode: ModeEnum, identifier: string) { + const config = this.resultlistService.resultlistConfigPerContId.get(identifier); + config.defautMode = mode; + this.resultlistService.resultlistConfigPerContId.set(identifier, config); + setTimeout(() => { + this.resultlistService.updateVisibleItems(); + }, 0); + } + + public sortColumn(listContributor: ResultListContributor, column: Column) { + this.resultlistService.getBoardEvents({ origin: listContributor.identifier, event: 'sortColumnEvent', data: column }); + } + + public geoAutoSort(listContributor: ResultListContributor, enabled: boolean) { + this.resultlistService.getBoardEvents({ origin: listContributor.identifier, event: 'geoAutoSortEvent', data: enabled }); + } + + public geoSort(listContributor: ResultListContributor, event: string) { + this.resultlistService.getBoardEvents({ origin: listContributor.identifier, event: 'geoSortEvent', data: event }); + } + + public applyActionOnItem(listContributor: ResultListContributor, action: { action: Action; elementidentifier: ElementIdentifier; }) { + this.resultlistService.getBoardEvents({ origin: listContributor.identifier, event: 'actionOnItemEvent', data: action }); + } + + public consultItem(listContributor: ResultListContributor, data: ElementIdentifier) { + this.resultlistService.getBoardEvents({ origin: listContributor.identifier, event: 'consultedItemEvent', data }); + } + + public selectItems(listContributor: ResultListContributor, data: string[]) { + this.resultlistService.getBoardEvents({ origin: listContributor.identifier, event: 'selectedItemsEvent', data }); + } + + public paginate(listContributor: ResultListContributor, pageQuery: PageQuery) { + this.resultlistService.getBoardEvents({ origin: listContributor.identifier, event: 'paginationEvent', data: pageQuery }); + } + + public applyGlobalAction(listContributor: ResultListContributor, action: Action) { + this.resultlistService.getBoardEvents({ origin: listContributor.identifier, event: 'globalActionEvent', data: action }); + } + + public updateMapStyleFromScroll(items: Item[], collection: string) { + this.resultlistService.updateMapStyleFromScroll(items, collection); + } + + public updateMapStyleFromChange(items: Map[], collection: string) { + this.resultlistService.updateMapStyleFromChange(items, collection); + } +} diff --git a/src/app/components/map/aoi-dimensions/aoi-dimensions.component.html b/src/app/components/arlas-map/aoi-dimensions/aoi-dimensions.component.html similarity index 100% rename from src/app/components/map/aoi-dimensions/aoi-dimensions.component.html rename to src/app/components/arlas-map/aoi-dimensions/aoi-dimensions.component.html diff --git a/src/app/components/map/aoi-dimensions/aoi-dimensions.component.scss b/src/app/components/arlas-map/aoi-dimensions/aoi-dimensions.component.scss similarity index 100% rename from src/app/components/map/aoi-dimensions/aoi-dimensions.component.scss rename to src/app/components/arlas-map/aoi-dimensions/aoi-dimensions.component.scss diff --git a/src/app/components/map/aoi-dimensions/aoi-dimensions.component.ts b/src/app/components/arlas-map/aoi-dimensions/aoi-dimensions.component.ts similarity index 81% rename from src/app/components/map/aoi-dimensions/aoi-dimensions.component.ts rename to src/app/components/arlas-map/aoi-dimensions/aoi-dimensions.component.ts index af4522d4..7ca86130 100644 --- a/src/app/components/map/aoi-dimensions/aoi-dimensions.component.ts +++ b/src/app/components/arlas-map/aoi-dimensions/aoi-dimensions.component.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { AoiEdition } from 'arlas-web-components'; @Component({ @@ -25,15 +25,14 @@ import { AoiEdition } from 'arlas-web-components'; templateUrl: './aoi-dimensions.component.html', styleUrls: ['./aoi-dimensions.component.scss'] }) -export class AoiDimensionComponent implements OnInit, OnChanges { +export class AoiDimensionComponent implements OnInit { + /** + * @Input : Angular + * Current dimensions of the AOI being edited + */ @Input() public aoiEdition: AoiEdition; public constructor() { } - public ngOnInit() { - } - - public ngOnChanges(changes: SimpleChanges): void { - - } + public ngOnInit() { } } diff --git a/src/app/components/map/aoi-dimensions/aoi-dimensions.pipes.ts b/src/app/components/arlas-map/aoi-dimensions/aoi-dimensions.pipes.ts similarity index 100% rename from src/app/components/map/aoi-dimensions/aoi-dimensions.pipes.ts rename to src/app/components/arlas-map/aoi-dimensions/aoi-dimensions.pipes.ts diff --git a/src/app/components/arlas-map/arlas-map.component.html b/src/app/components/arlas-map/arlas-map.component.html new file mode 100644 index 00000000..61342704 --- /dev/null +++ b/src/app/components/arlas-map/arlas-map.component.html @@ -0,0 +1,84 @@ +
+ + +
+
+
+ settings +
+
+ public +
+
+ travel_explore +
+
+ location_searching + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ edit +
+
+ +
+
+
° ' "
+
+
+
+ +
+ +
+ + + + + + +
+ +
+
\ No newline at end of file diff --git a/src/app/components/arlas-map/arlas-map.component.scss b/src/app/components/arlas-map/arlas-map.component.scss new file mode 100644 index 00000000..a65723e0 --- /dev/null +++ b/src/app/components/arlas-map/arlas-map.component.scss @@ -0,0 +1,121 @@ +/* + * Licensed to Gisaïa under one or more contributor + * license agreements. See the NOTICE.txt file distributed with + * this work for additional information regarding copyright + * ownership. Gisaïa licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +@import "../../../styles/variables.scss"; +@import "../../../styles/app/sizes.scss"; +@import "../../../styles/app/mixin.scss"; + +.arlas-map__container { + height: 100%; + + .arlas-map-settings { + position: absolute; + top: calc( $sm-spacing + $map-attributions-height + $sm-spacing + + $map-actions-width + $sm-spacing + + $map-actions-length + $sm-spacing); + right: $sm-spacing; + z-index: 2; + + .arlas-map-settings-container { + display: flex; + flex-direction: column; + @include box-border(); + background-color: white; + + .arlas-map-settings-items { + height: $map-actions-width; + width: $map-actions-width; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + border-top: 1px solid #ddd; + + &:hover { + background-color: rgba(0, 0, 0, 0.05); + } + + &:first-child { + border-top: none !important; + } + } + } + } + + .if-geocoding-button { + bottom: calc($map-actions-width + $xs-border) !important; + } + + .arlas-rasters-manager { + position: absolute; + top: calc( + $sm-spacing + $map-attributions-height + $sm-spacing + $map-actions-width + $sm-spacing + $map-actions-length + + $sm-spacing + $map-settings-height + $sm-spacing + ); + right: $sm-spacing; + z-index: 2; + + &--geocoding { + top: calc( + $sm-spacing + $map-attributions-height + $sm-spacing + $map-actions-width + $sm-spacing + $map-actions-length + + $sm-spacing + $map-settings-height + $map-actions-width + $sm-spacing + ); + } + } + + .arlas-map-action-container { + position: absolute; + right: calc($map-actions-width + $xs-border); + bottom: 0; + display: flex; + flex-direction: row-reverse; + background-color: white; + @include box-border(); + + .arlas-map-action-items { + height: $map-actions-width; + width: $map-actions-width; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + border-right: $xs-border solid #ddd; + color: black; + + &:hover { + background-color: rgba(0, 0, 0, 0.05); + } + + &:first-child { + border-right: unset; + } + } + } + + .aoi-dimensions { + position: absolute; + + right: calc($sm-spacing + $map-actions-width + $sm-spacing); + top: calc($sm-spacing + $map-attributions-height + + $sm-spacing + $map-actions-width + + $sm-spacing + $map-actions-length + + $sm-spacing + $map-actions-width); + z-index: 1; + } +} \ No newline at end of file diff --git a/src/app/components/arlas-map/arlas-map.component.spec.ts b/src/app/components/arlas-map/arlas-map.component.spec.ts new file mode 100644 index 00000000..6ab97c0f --- /dev/null +++ b/src/app/components/arlas-map/arlas-map.component.spec.ts @@ -0,0 +1,35 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { VisualizeService } from 'app/services/visualize.service'; +import { ArlasToolKitModule, ArlasToolkitSharedModule } from 'arlas-wui-toolkit'; +import { ArlasMapComponent } from './arlas-map.component'; + +describe('ArlasMapComponent', () => { + let component: ArlasMapComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ArlasMapComponent ], + imports: [ + ArlasToolKitModule, + TranslateModule.forRoot({ + loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } }), + ArlasToolkitSharedModule + ], + providers: [ + VisualizeService + ], + teardown: { destroyAfterEach: false } + }) + .compileComponents(); + + fixture = TestBed.createComponent(ArlasMapComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/arlas-map/arlas-map.component.ts b/src/app/components/arlas-map/arlas-map.component.ts new file mode 100644 index 00000000..03441181 --- /dev/null +++ b/src/app/components/arlas-map/arlas-map.component.ts @@ -0,0 +1,506 @@ +/* + * Licensed to Gisaïa under one or more contributor + * license agreements. See the NOTICE.txt file distributed with + * this work for additional information regarding copyright + * ownership. Gisaïa licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Component, OnInit, ViewChild } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { MatIconRegistry } from '@angular/material/icon'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { DomSanitizer } from '@angular/platform-browser'; +import { ActivatedRoute, Router } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { GeocodingResult } from '@services/geocoding.service'; +import { MapService } from '@services/map.service'; +import { ResultlistService } from '@services/resultlist.service'; +import { VisualizeService } from '@services/visualize.service'; +import { + AoiEdition, BasemapStyle, BboxGeneratorComponent, GeoQuery, MapglComponent, + MapglImportComponent, MapglSettingsComponent, SCROLLABLE_ARLAS_ID +} from 'arlas-web-components'; +import { ElementIdentifier, MapContributor } from 'arlas-web-contributors'; +import { LegendData } from 'arlas-web-contributors/contributors/MapContributor'; +import { + ArlasCollaborativesearchService, ArlasCollectionService, ArlasConfigService, ArlasMapService, + ArlasMapSettings, ArlasSettingsService, ArlasStartupService, getParamValue +} from 'arlas-wui-toolkit'; +import * as mapboxgl from 'mapbox-gl'; +import { BehaviorSubject, debounceTime, fromEvent, merge, mergeMap, Observable, of, Subject, takeUntil } from 'rxjs'; + +const DEFAULT_BASEMAP: BasemapStyle = { + styleFile: 'https://api.maptiler.com/maps/basic/style.json?key=xIhbu1RwgdbxfZNmoXn4', + name: 'Basic' +}; + +@Component({ + selector: 'arlas-map', + templateUrl: './arlas-map.component.html', + styleUrls: ['./arlas-map.component.scss'] +}) +export class ArlasMapComponent implements OnInit { + /** Map definition */ + public mapComponentConfig: any; + public mapId = 'mapgl'; + public defaultBasemap: BasemapStyle; + public mainMapContributor: MapContributor; + public mainCollection: string; + public mapAttributionPosition: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' = 'top-right'; + public mapLoaded = false; + + /** Map interactions */ + public featuresToSelect: Array = []; + + /** Map move */ + public fitbounds: Array> = []; + public recalculateExtent = true; + public zoomChanged = false; + public zoomStart: number; + private disableRecalculateExtent = false; + private cumulatedXMoveRatio = 0; + private cumulatedYMoveRatio = 0; + + /** Extent in url */ + private allowMapExtent: boolean; + private mapBounds: mapboxgl.LngLatBounds; + private mapEventListener = new Subject(); + private mapExtentTimer: number; + private MAP_EXTENT_PARAM = 'extend'; + + /** Map data */ + public mapDataSources; + public mapRedrawSources; + public mapLegendUpdater = new Subject>>(); + public mapVisibilityUpdater; + /** Visibility status of layers on the map */ + public layersVisibilityStatus: Map = new Map(); + + /** Geo-filters */ + public isMapMenuOpen = false; + public shouldCloseMapMenu = true; + public aoiEdition: AoiEdition; + public geojsondraw: { type: string; features: Array; } = { + 'type': 'FeatureCollection', + 'features': [] + }; + + /** Import geometries */ + public nbVerticesLimit = 50; + public maxFeatures = 100; + public maxFileSize = 5000000; // bytes + public maxLoadingTime = 30000; // s + + /** Geocoding */ + protected showGeocodingPopup = new BehaviorSubject(false); + protected enableGeocodingFeature = !!this.settingsService.getGeocodingSettings()?.enabled; + + /** Destroy subscriptions */ + private _onDestroy$ = new Subject(); + + @ViewChild('map', { static: false }) public mapglComponent: MapglComponent; + @ViewChild('import', { static: false }) public mapImportComponent: MapglImportComponent; + @ViewChild('mapSettings', { static: false }) public mapSettings: MapglSettingsComponent; + + public constructor( + protected mapService: MapService, + private toolkitMapService: ArlasMapService, + protected visualizeService: VisualizeService, + private configService: ArlasConfigService, + private collaborativeService: ArlasCollaborativesearchService, + protected arlasStartupService: ArlasStartupService, + private settingsService: ArlasSettingsService, + private generateAoiDialog: MatDialog, + private activatedRoute: ActivatedRoute, + private router: Router, + private mapSettingsService: ArlasMapSettings, + private resultlistService: ResultlistService, + private translate: TranslateService, + private snackbar: MatSnackBar, + private iconRegistry: MatIconRegistry, + private domSanitizer: DomSanitizer, + private collectionService: ArlasCollectionService + ) { + if (this.arlasStartupService.shouldRunApp && !this.arlasStartupService.emptyMode) { + /** resize the map */ + fromEvent(window, 'resize') + .pipe(debounceTime(100), takeUntil(this._onDestroy$)) + .subscribe((event: Event) => { + this.mapService.resize(); + }); + + this.mapComponentConfig = this.configService.getValue('arlas.web.components.mapgl.input'); + if (!!this.mapComponentConfig) { + this.mapService.setMapConfig(this.mapComponentConfig); + this.defaultBasemap = this.mapComponentConfig.defaultBasemapStyle ?? DEFAULT_BASEMAP; + this.mapExtentTimer = this.configService.getValue('arlas.web.components.mapgl.mapExtendTimer') ?? 4000; + this.allowMapExtent = this.configService.getValue('arlas.web.components.mapgl.allowMapExtend'); + this.nbVerticesLimit = this.configService.getValue('arlas.web.components.mapgl.nbVerticesLimit'); + + /** init from url */ + const queryParamVisibleVisualisations = getParamValue('vs'); + if (queryParamVisibleVisualisations) { + const visibleVisuSet = new Set(queryParamVisibleVisualisations.split(';').map(n => decodeURI(n))); + this.mapComponentConfig.visualisations_sets.forEach(v => v.enabled = visibleVisuSet.has(v.name)); + } + } else { + this.defaultBasemap = DEFAULT_BASEMAP; + } + } else { + this.defaultBasemap = DEFAULT_BASEMAP; + } + } + + public ngOnInit(): void { + if (this.arlasStartupService.shouldRunApp && !this.arlasStartupService.emptyMode) { + /** Prepare map data */ + this.mainCollection = this.configService.getValue('arlas.server.collection.name'); + this.mainMapContributor = this.mapService.mapContributors.filter(m => !!m.collection || m.collection === this.mainCollection)[0]; + this.mapDataSources = this.mapService.mapContributors.map(c => c.dataSources).length > 0 ? + this.mapService.mapContributors.map(c => c.dataSources).reduce((set1, set2) => new Set([...set1, ...set2])) : new Set(); + this.mapRedrawSources = merge(...this.mapService.mapContributors.map(c => c.redrawSource)); + + const legendUpdaters: Observable<{ collection: string; legendData: Map; }> = + merge(...this.mapService.mapContributors + .map(c => c.legendUpdater + .pipe(mergeMap(m => of({ collection: c.collection, legendData: m }))) + )); + const legendData = new Map>(); + legendUpdaters + .pipe(takeUntil(this._onDestroy$)) + .subscribe(lg => { + legendData.set(lg.collection, lg.legendData); + this.mapLegendUpdater.next(legendData); + }); + + this.mapVisibilityUpdater = merge(...this.mapService.mapContributors.map(c => c.visibilityUpdater)); + this.mapService.mapContributors.forEach(contrib => contrib.drawingsUpdate.subscribe(() => { + this.geojsondraw = { + 'type': 'FeatureCollection', + 'features': this.mapService.mapContributors.map(c => c.geojsondraw.features).reduce((a, b) => a.concat(b)) + .filter((v, i, a) => a.findIndex(t => (t.properties.arlas_id === v.properties.arlas_id)) === i) + }; + })); + + if (this.allowMapExtent) { + const extentValue = getParamValue(this.MAP_EXTENT_PARAM); + if (extentValue) { + const stringBounds = extentValue.split(','); + if (stringBounds.length === 4) { + this.mapBounds = new mapboxgl.LngLatBounds( + new mapboxgl.LngLat(+stringBounds[0], +stringBounds[1]), + new mapboxgl.LngLat(+stringBounds[2], +stringBounds[3]) + ); + } + } + } + + // eslint-disable-next-line max-len + this.iconRegistry.addSvgIconLiteral('bbox', this.domSanitizer.bypassSecurityTrustHtml('')); + + // eslint-disable-next-line max-len + this.iconRegistry.addSvgIconLiteral('draw_polygon', this.domSanitizer.bypassSecurityTrustHtml(' image/svg+xml ')); + + // eslint-disable-next-line max-len + this.iconRegistry.addSvgIconLiteral('draw_circle', this.domSanitizer.bypassSecurityTrustHtml('')); + + // eslint-disable-next-line max-len + this.iconRegistry.addSvgIconLiteral('remove_polygon', this.domSanitizer.bypassSecurityTrustHtml(' image/svg+xml ')); + + // eslint-disable-next-line max-len + this.iconRegistry.addSvgIconLiteral('import_polygon', this.domSanitizer.bypassSecurityTrustHtml('')); + + // eslint-disable-next-line max-len + this.iconRegistry.addSvgIconLiteral('map_settings', this.domSanitizer.bypassSecurityTrustHtml('')); + } + } + + public ngAfterViewInit() { + if (!this.arlasStartupService.emptyMode) { + this.mapService.adjustCoordinates(); + + this.mapEventListener + .pipe( + takeUntil(this._onDestroy$), + debounceTime(this.mapExtentTimer)) + .subscribe(() => { + /** Change map extent in the url */ + const bounds = (this.mapglComponent.map).getBounds(); + const extent = bounds.getWest() + ',' + bounds.getSouth() + ',' + bounds.getEast() + ',' + bounds.getNorth(); + const queryParams = Object.assign({}, this.activatedRoute.snapshot.queryParams); + queryParams[this.MAP_EXTENT_PARAM] = extent; + this.router.navigate([], { replaceUrl: true, queryParams: queryParams }); + }); + } + } + + /** + * Wait until the map component is loaded before fetching the data + * @param isLoaded Whether the map has loaded + */ + public onMapLoaded(isLoaded: boolean): void { + if (isLoaded && !this.arlasStartupService.emptyMode) { + this.mapLoaded = true; + this.mapService.setMapComponent(this.mapglComponent); + this.toolkitMapService.setMap(this.mapglComponent.map); + this.visualizeService.setMap(this.mapglComponent.map); + if (this.mapBounds && this.allowMapExtent) { + this.mapglComponent.map.fitBounds(this.mapBounds, { duration: 0 }); + this.mapBounds = null; + } + this.mapglComponent.map.on('movestart', (e) => { + this.zoomStart = this.mapglComponent.map.getZoom(); + }); + this.mapglComponent.map.on('moveend', (e) => { + if (Math.abs(this.mapglComponent.map.getZoom() - this.zoomStart) > 1) { + this.zoomChanged = true; + } + if (this.allowMapExtent) { + this.mapEventListener.next(null); + } + }); + this.adjustMapOffset(); + this.mapService.adjustCoordinates(); + this.mapService.mapContributors.forEach(mapglContributor => { + mapglContributor.updateData = true; + mapglContributor.fetchData(null); + mapglContributor.setSelection(null, this.collaborativeService.getCollaboration(mapglContributor.identifier)); + }); + + if (!!this.resultlistService.previewListContrib && this.resultlistService.previewListContrib.data.length > 0 && + this.mapComponentConfig.mapLayers.events.onHover.filter(l => this.mapglComponent.map.getLayer(l)).length > 0) { + this.resultlistService.updateVisibleItems(); + } + } + } + + public ngOnDestroy(): void { + this._onDestroy$.next(true); + this._onDestroy$.complete(); + } + + public openAoiGenerator() { + this.generateAoiDialog.open(BboxGeneratorComponent, { + data: { + initCorner: { + lat: this.mapComponentConfig.initCenter ? this.mapComponentConfig.initCenter[1] : 0, + lng: this.mapComponentConfig.initCenter ? this.mapComponentConfig.initCenter[0] : 0, + } + } + }); + } + + public downloadLayerSource(d) { + const mc = this.mapService.mapContributors.find(mc => mc.collection === d.collection); + if (mc) { + mc.downloadLayerSource(d.sourceName, d.layerName, d.downloadType, this.collectionService.displayFieldName); + } + } + + public openMapSettings(): void { + this.mapSettingsService.mapContributors = this.mapService.mapContributors; + this.mapSettings.openDialog(this.mapSettingsService); + } + + /** + * Applies the selected geo query + */ + public applySelectedGeoQuery(geoQueries: Map) { + const configDebounceTime = this.configService.getValue('arlas.server.debounceCollaborationTime'); + const debounceDuration = configDebounceTime !== undefined ? configDebounceTime : 750; + const changedMapContributors = this.mapService.mapContributors.filter(mc => !!geoQueries.has(mc.collection)); + for (let i = 0; i < changedMapContributors.length; i++) { + setTimeout(() => { + const collection = changedMapContributors[i].collection; + const geoQuery = geoQueries.get(collection); + changedMapContributors[i].setGeoQueryOperation(geoQuery.operation); + changedMapContributors[i].setGeoQueryField(geoQuery.geometry_path); + changedMapContributors[i].onChangeGeoQuery(); + this.snackbar.open(this.translate.instant('Updating Geo-query of ', + { collection: this.translate.instant(this.collectionService.getDisplayName(changedMapContributors[i].collection)) })); + if (i === changedMapContributors.length - 1) { + setTimeout(() => this.snackbar.dismiss(), 1000); + } + + }, (i) * (debounceDuration * 1.5)); + } + + } + + public setLayersVisibilityStatus(event) { + this.layersVisibilityStatus = event; + } + + public reloadMapImages() { + this.visualizeService.setMap(this.mapglComponent.map); + } + + public onAoiEdit(aoiEdit: AoiEdition) { + this.aoiEdition = aoiEdit; + } + + public onMove(event) { + // Update data only when the collections info are presents + if (this.resultlistService.collectionToDescription.size > 0) { + /** Change map extent in the url */ + const bounds = this.mapglComponent.map.getBounds(); + const extent = bounds.getWest() + ',' + bounds.getSouth() + ',' + bounds.getEast() + ',' + bounds.getNorth(); + const queryParams = Object.assign({}, this.activatedRoute.snapshot.queryParams); + const visibileVisus = this.mapglComponent.visualisationSetsConfig.filter(v => v.enabled).map(v => v.name).join(';'); + queryParams[this.MAP_EXTENT_PARAM] = extent; + queryParams['vs'] = visibileVisus; + this.router.navigate([], { replaceUrl: true, queryParams: queryParams }); + localStorage.setItem('currentExtent', JSON.stringify(bounds)); + + const ratioToAutoSort = 0.1; + this.mapService.centerLatLng.lat = event.centerWithOffset[1]; + this.mapService.centerLatLng.lng = event.centerWithOffset[0]; + this.cumulatedXMoveRatio += event.xMoveRatio; + this.cumulatedYMoveRatio += event.yMoveRatio; + if ((this.cumulatedXMoveRatio > ratioToAutoSort || this.cumulatedYMoveRatio > ratioToAutoSort || this.zoomChanged)) { + this.recalculateExtent = true; + this.cumulatedXMoveRatio = 0; + this.cumulatedYMoveRatio = 0; + } + const newMapExtent = event.extendWithOffset; + const newMapExtentRaw = event.rawExtendWithOffset; + const pwithin = newMapExtent[1] + ',' + newMapExtent[2] + ',' + newMapExtent[3] + ',' + newMapExtent[0]; + const pwithinRaw = newMapExtentRaw[1] + ',' + newMapExtentRaw[2] + ',' + newMapExtentRaw[3] + ',' + newMapExtentRaw[0]; + if (this.recalculateExtent && !this.disableRecalculateExtent) { + this.resultlistService.applyMapExtent(pwithinRaw, pwithin); + + this.mapService.mapContributors.forEach(c => { + if (!!this.resultlistService.resultlistContributors) { + const resultlistContrbutor = this.resultlistService.resultlistContributors.find(v => v.collection === c.collection); + if (!!resultlistContrbutor) { + if (this.resultlistService.isGeoSortActivated.get(c.identifier)) { + c.searchSort = resultlistContrbutor.geoOrderSort; + } else { + c.searchSort = resultlistContrbutor.sort; + } + this.collaborativeService.registry.set(c.identifier, c); + } + } + this.mapService.clearWindowData(c); + }); + this.zoomChanged = false; + } + event.extendForTest = newMapExtent; + event.rawExtendForTest = newMapExtentRaw; + this.mapService.mapContributors.forEach(contrib => contrib.onMove(event, this.recalculateExtent)); + this.recalculateExtent = false; + } + } + + public onChangeAoi(event) { + const configDebounceTime = this.configService.getValue('arlas.server.debounceCollaborationTime'); + const debounceDuration = configDebounceTime !== undefined ? configDebounceTime : 750; + for (let i = 0; i < this.mapService.mapContributors.length; i++) { + setTimeout(() => { + this.snackbar.open(this.translate.instant('Loading data of ', + { collection: this.translate.instant(this.collectionService.getDisplayName(this.mapService.mapContributors[i].collection)) })); + this.mapService.mapContributors[i].onChangeAoi(event); + if (i === this.mapService.mapContributors.length - 1) { + setTimeout(() => this.snackbar.dismiss(), 1000); + } + }, (i) * ((debounceDuration + 100) * 1.5)); + } + } + + public changeVisualisation(layers: Set) { + this.mapService.mapContributors.forEach(contrib => contrib.changeVisualisation(layers)); + const queryParams = Object.assign({}, this.activatedRoute.snapshot.queryParams); + const visibileVisus = this.mapglComponent.visualisationSetsConfig.filter(v => v.enabled).map(v => v.name).join(';'); + queryParams['vs'] = visibileVisus; + this.router.navigate([], { replaceUrl: true, queryParams: queryParams }); + } + + public emitFeaturesOnHover(event) { + if (event.features) { + this.mapService.setCursor('pointer'); + this.resultlistService.highlightItems(event.features); + } else { + this.mapService.setCursor(''); + this.resultlistService.clearHighlightedItems(); + } + } + + public emitFeaturesOnClick(event) { + if (event.features) { + const feature = event.features[0]; + const resultListContributor = this.resultlistService.resultlistContributors + .filter(c => feature.layer.metadata.collection === c.collection + && !feature.layer.id.includes(SCROLLABLE_ARLAS_ID))[0]; + if (!!resultListContributor) { + const idFieldName = this.resultlistService.collectionToDescription.get(resultListContributor.collection).id_path; + const id = feature.properties[idFieldName.replace(/\./g, '_')]; + // Open the list panel if it's closed + this.disableRecalculateExtent = true; + if (!this.resultlistService.listOpen) { + this.resultlistService.toggleList(); + } + + // Get index of list to display + const contributorIds = this.resultlistService.resultlistContributors.map(c => c.identifier); + const listIdx = contributorIds.findIndex(id => id === resultListContributor.identifier); + this.resultlistService.selectedListTabIndex = listIdx; + + // Select the good tab if we have several + // No tabs case + if (this.resultlistService.resultlistContributors.length === 1) { + this.resultlistService.waitForList(() => this.resultlistService.openDetail(id)); + this.disableRecalculateExtent = false; + } else { + this.resultlistService.waitForList(() => { + // retrieve list + const tab = document.querySelector('[aria-label="' + resultListContributor.identifier + '"]') as any; + tab.click(); + // Set Timeout to wait the new tab + setTimeout(() => this.resultlistService.openDetail(id), 250); + this.disableRecalculateExtent = false; + }); + } + } + } + } + + public closeMapMenu() { + setTimeout(() => { + if (this.shouldCloseMapMenu) { + this.isMapMenuOpen = false; + } + }, 100); + } + + public drawCircle() { + this.mapglComponent.switchToDrawMode('draw_radius_circle', { isFixedRadius: false, steps: 12 }); + } + + protected goToLocation(event: GeocodingResult) { + const bbox = this.visualizeService.getBbox(event.geojson); + this.visualizeService.handleGeojsonPreview(event.geojson); + if (event.geojson.type === 'Point') { + const zoom = this.settingsService.getGeocodingSettings().find_place_zoom_to; + this.mapglComponent.map.fitBounds(bbox, { maxZoom: zoom }); + } else { + this.mapglComponent.map.fitBounds(bbox); + } + } + + private adjustMapOffset() { + this.recalculateExtent = true; + this.mapglComponent.map.fitBounds(this.mapglComponent.map.getBounds()); + } +} diff --git a/src/app/components/arlas-wui-root/arlas-wui-root.component.html b/src/app/components/arlas-wui-root/arlas-wui-root.component.html index 71784467..50b9f614 100644 --- a/src/app/components/arlas-wui-root/arlas-wui-root.component.html +++ b/src/app/components/arlas-wui-root/arlas-wui-root.component.html @@ -6,7 +6,8 @@ [searchContributors]="searchContributors" [dialogPositionLeft]="42" [dialogPositionTop]="-2"> + [collectionToDescription]="resultlistService.collectionToDescription" (zoomEvent)="zoomToData($event)" + [availableSpace]="availableSpaceCounts" [spacing]="spacing"> @@ -19,70 +20,36 @@ - + - +
-
- - -
+ + + +
-
-
-
- -
-
- public -
-
- travel_explore -
-
- location_searching - -
-
-
-
- -
-
- -
-
- -
-
- -
-
- edit -
-
- -
-
-
° ' "
-
-
-
-
- -
+ [class.app-container-with-list-analytics]="resultlistService.rightListContributors.length > 0 + && !resultlistService.listOpen + && (resultlistService.previewListContrib | getResultlistConfig)?.hasGridMode + && analyticsService.activeTab !== undefined" + [class.app-container-analytics]="(resultlistService.rightListContributors.length === 0 + || (resultlistService.rightListContributors.length > 0 + && !(resultlistService.previewListContrib | getResultlistConfig)?.hasGridMode)) + && !resultlistService.listOpen && analyticsService.activeTab !== undefined" + [class.app-container-timeline-hidden]="!showTimeline" + [class.app-container-timeline-legend]="showTimeline && timelineComponent + && timelineComponent.timelineLegend && timelineComponent.timelineLegend.length > 1 && showTimelineLegend">
@@ -92,7 +59,7 @@
-
{{ isExtraShortcutsOpen ? 'See less' : 'See more' | translate }}
+
{{ isExtraShortcutsOpen ? ('See less' | translate) : ('See more' | translate) }}
{{ extraShortcutsFiltered }} @@ -109,153 +76,79 @@ [displayFilterFirstValue]="false">
+
-
-
+ +
keyboard_arrow_left
-
- +
+
- - -
+ + + +
{{ resultlistService.listOpen ? 'keyboard_arrow_right' : 'keyboard_arrow_left' }}
-
- + + (paginationEvent)="resultlistService.getBoardEvents({ origin: resultlistService.previewListContrib.identifier, event: 'paginationEvent', data: $event })" + (geoSortEvent)="resultlistService.getBoardEvents({ origin: resultlistService.previewListContrib.identifier, event: 'geoSortEvent', data: $event })" + (geoAutoSortEvent)="resultlistService.getBoardEvents({ origin: resultlistService.previewListContrib.identifier, event: 'geoAutoSortEvent', data: $event })" + (selectedItemsEvent)="resultlistService.getBoardEvents({ origin: resultlistService.previewListContrib.identifier, event: 'selectedItemsEvent', data: $event })" + (consultedItemEvent)="resultlistService.getBoardEvents({ origin: resultlistService.previewListContrib.identifier, event: 'consultedItemEvent', data: $event })" + (actionOnItemEvent)="resultlistService.getBoardEvents({ origin: resultlistService.previewListContrib.identifier, event: 'actionOnItemEvent', data: $event })" + (globalActionEvent)="resultlistService.getBoardEvents({ origin: resultlistService.previewListContrib.identifier, event: 'globalActionEvent', data: $event })">
-
- - - - - - {{resultListConfigPerContId.get(list.identifier)?.options?.icon}} - - - {{list.name | translate}} - - -
- - -
-
-
+
+
- - -
- -
diff --git a/src/app/components/arlas-wui-root/arlas-wui-root.component.scss b/src/app/components/arlas-wui-root/arlas-wui-root.component.scss index 1442b110..ec5859ea 100644 --- a/src/app/components/arlas-wui-root/arlas-wui-root.component.scss +++ b/src/app/components/arlas-wui-root/arlas-wui-root.component.scss @@ -26,39 +26,53 @@ height: 100%; overflow: hidden; + .arlas-progression { + width: 100%; + position: absolute; + top: 0; + } + ::ng-deep.arlas-time-shortcuts--container { left: $sm-spacing; padding: 0; } - ::ng-deep.current-coordinate, - ::ng-deep.current-coordinate-edition { - right: calc($sm-spacing + $map-scale-max-width + $default-spacing); - bottom: calc($timeline-height + $sm-spacing); - } + .arlas-map { + display: flex; + width: 100%; + height: 100%; - .map-container-tight-coordinates { ::ng-deep.current-coordinate, ::ng-deep.current-coordinate-edition { - right: $sm-spacing; - bottom: calc($timeline-height + $sm-spacing + $map-scale-height + $sm-spacing); + right: calc($sm-spacing + $map-scale-max-width + $default-spacing); + bottom: calc($timeline-height + $sm-spacing); } - } -} -.arlas-progression { - width: 100%; - position: absolute; - top: 0; -} + &--tight { + ::ng-deep.current-coordinate, + ::ng-deep.current-coordinate-edition { + right: $sm-spacing; + bottom: calc($timeline-height + $sm-spacing + $map-scale-height + $sm-spacing); + } + } -.arlas-map { - display: flex; - width: 100%; - height: 100%; + &--tight-reduce { + ::ng-deep.current-coordinate, + ::ng-deep.current-coordinate-edition { + right: calc($sm-spacing + $result-list-width); + } + } + + &--tight-with-list { + ::ng-deep.current-coordinate, + ::ng-deep.current-coordinate-edition { + right: calc($sm-spacing + $preview-result-list-width); + } + } + } } -.side-result-list-toggle { +.resultlist__toggle { z-index: 2; position: absolute; top: 50%; @@ -69,35 +83,31 @@ justify-content: center; flex-direction: column; @include toggle(); -} -.side-result-list-toggle-open { - // Have same spacing between toggle and list as with toggle and timeline - right: calc($result-list-width + ($timeline-tools-height - $toggle-height) / 2); -} + &--open { + // Have same spacing between toggle and list as with toggle and timeline + right: calc($result-list-width + ($timeline-tools-height - $toggle-height) / 2); + } -.side-result-list-toggle-open-no-grid { - right: 0px; + &--no-grid { + right: 0; + } } -.side-result-list-preview { +.resultlist__preview { width: $preview-result-list-width; height: 100%; position: absolute; right: 0; display: block; background-color: white; -} - -.side-result-list-preview-close { - display: none; -} -.side-result-list-preview .resultgrid__icon_check { - display: none; + &--closed { + display: none; + } } -.side-result-list { +.resultlist__wrapper { width: 0; height: calc(100vh - $top-menu-height); position: absolute; @@ -105,32 +115,28 @@ visibility: hidden; background-color: white; overflow: hidden; -} -.one-tab ::ng-deep.mat-tab-header { - display: none; + &--open { + display: block; + width: $result-list-width; + visibility: visible; + } } -.side-result-list-open { - display: block; - width: $result-list-width; - visibility: visible; +.one-tab { + ::ng-deep.list__container { + height: calc(100vh - $top-menu-height) !important; + } + + ::ng-deep.mat-tab-header { + display: none; + } } .rotate-icon { transform: rotate(180deg); } -.left_resultlist_wrapper { - height: calc(100vh - 50px - $top-menu-height); - overflow: hidden; -} - -.one-tab .left_resultlist_wrapper { - height: calc(100vh - $top-menu-height); - overflow: hidden; -} - .arlas-analytics-toggle { position: absolute; // Have same spacing between toggle and list as with toggle and timeline @@ -151,10 +157,9 @@ transform: rotate(180deg); } -.arlas-analytics--container { +.arlas-analytics--wrapper { width: $analytics-board-width; height: calc(100vh - $top-menu-height); - overflow: auto; border-right: $menu-border; box-sizing: border-box; } @@ -227,56 +232,59 @@ .app-container-reduce-analytics, .app-container-reduce { - .arlas-map-action, - .arlas-map-settings, - .arlas-rasters-manager - { - right: calc($result-list-width + $sm-spacing); - } - .aoi-dimensions { - right: calc($result-list-width + $sm-spacing + $map-actions-width + $sm-spacing); - } - ::ng-deep.basemap-container { - right: calc($result-list-width + $sm-spacing + $map-actions-width + $sm-spacing) !important; - } + .arlas-map { + ::ng-deep.arlas-map-action, + ::ng-deep.arlas-map-settings, + ::ng-deep.arlas-rasters-manager { + right: calc($result-list-width + $sm-spacing) !important; + } + ::ng-deep.aoi-dimensions { + right: calc($result-list-width + $sm-spacing + $map-actions-width + $sm-spacing); + } + ::ng-deep.basemap-container { + right: calc($result-list-width + $sm-spacing + $map-actions-width + $sm-spacing) !important; + } - ::ng-deep.current-coordinate, - ::ng-deep.current-coordinate-edition { - right: calc($result-list-width + $sm-spacing + $map-scale-max-width + $default-spacing); - } - .map-container-tight-coordinates { ::ng-deep.current-coordinate, ::ng-deep.current-coordinate-edition { - right: calc($result-list-width + $sm-spacing); - bottom: calc($timeline-height + $sm-spacing + $map-scale-height + $sm-spacing); + right: calc($result-list-width + $sm-spacing + $map-scale-max-width + $default-spacing); + } + &--tight { + ::ng-deep.current-coordinate, + ::ng-deep.current-coordinate-edition { + right: calc($result-list-width + $sm-spacing) !important; + bottom: calc($timeline-height + $sm-spacing + $map-scale-height + $sm-spacing); + } } } } .app-container-with-list-analytics, .app-container-with-list { - .arlas-map-action, - .arlas-map-settings, - .arlas-rasters-manager { - right: calc($preview-result-list-width + $sm-spacing); - } - .aoi-dimensions { - right: calc($preview-result-list-width + $sm-spacing + $map-actions-width + $sm-spacing); - } + .arlas-map { + ::ng-deep.arlas-map-action, + ::ng-deep.arlas-map-settings, + ::ng-deep.arlas-rasters-manager { + right: calc($preview-result-list-width + $sm-spacing) !important; + } + ::ng-deep.aoi-dimensions { + right: calc($preview-result-list-width + $sm-spacing + $map-actions-width + $sm-spacing); + } - ::ng-deep.basemap-container { - right: calc($preview-result-list-width + $sm-spacing + $map-actions-width + $sm-spacing) !important; - } + ::ng-deep.basemap-container { + right: calc($preview-result-list-width + $sm-spacing + $map-actions-width + $sm-spacing) !important; + } - ::ng-deep.current-coordinate, - ::ng-deep.current-coordinate-edition { - right: calc($preview-result-list-width + $sm-spacing + $map-scale-max-width + $default-spacing); - } - .map-container-tight-coordinates { ::ng-deep.current-coordinate, ::ng-deep.current-coordinate-edition { - right: calc($preview-result-list-width + $sm-spacing); - bottom: calc($timeline-height + $sm-spacing + $map-scale-height + $sm-spacing); + right: calc($preview-result-list-width + $sm-spacing + $map-scale-max-width + $default-spacing); + } + &--tight { + ::ng-deep.current-coordinate, + ::ng-deep.current-coordinate-edition { + right: calc($preview-result-list-width + $sm-spacing) !important; + bottom: calc($timeline-height + $sm-spacing + $map-scale-height + $sm-spacing); + } } } } @@ -301,20 +309,23 @@ } .app-container-timeline-hidden { - ::ng-deep.mapboxgl-ctrl-scale { - // Center regarding to timeline tools - bottom: calc(($timeline-tools-height - $map-scale-height) / 2 - $map-scale-border-width); - } + .arlas-map { + ::ng-deep.mapboxgl-ctrl-scale { + // Center regarding to timeline tools + bottom: calc(($timeline-tools-height - $map-scale-height) / 2 - $map-scale-border-width); + } - ::ng-deep.current-coordinate, - ::ng-deep.current-coordinate-edition { - // Center regarding to timeline tools - bottom: calc(($timeline-tools-height - $map-scale-height) / 2); - } - .map-container-tight-coordinates { ::ng-deep.current-coordinate, ::ng-deep.current-coordinate-edition { - bottom: calc(($timeline-tools-height - $map-scale-height) / 2 + $map-scale-height + $sm-spacing); + // Center regarding to timeline tools + bottom: calc(($timeline-tools-height - $map-scale-height) / 2); + } + + &--tight { + ::ng-deep.current-coordinate, + ::ng-deep.current-coordinate-edition { + bottom: calc(($timeline-tools-height - $map-scale-height) / 2 + $map-scale-height + $sm-spacing); + } } } @@ -331,27 +342,29 @@ } .app-container-timeline-legend { - ::ng-deep.mapboxgl-ctrl-scale { - // Center regarding to timeline tools - bottom: calc( - $timeline-with-legend-height + ($timeline-tools-height - $map-scale-height) / 2 - $map-scale-border-width - ); - } - - ::ng-deep.current-coordinate, - ::ng-deep.current-coordinate-edition { - // Center regarding to timeline tools - bottom: calc($timeline-with-legend-height + ($timeline-tools-height - $map-scale-height) / 2); - } + .arlas-map { + ::ng-deep.mapboxgl-ctrl-scale { + // Center regarding to timeline tools + bottom: calc( + $timeline-with-legend-height + ($timeline-tools-height - $map-scale-height) / 2 - $map-scale-border-width + ); + } - .map-container-tight-coordinates { ::ng-deep.current-coordinate, ::ng-deep.current-coordinate-edition { // Center regarding to timeline tools - bottom: calc( - $timeline-with-legend-height + ($timeline-tools-height - $map-scale-height) / 2 + $map-scale-height + - $sm-spacing - ); + bottom: calc($timeline-with-legend-height + ($timeline-tools-height - $map-scale-height) / 2); + } + + &--tight { + ::ng-deep.current-coordinate, + ::ng-deep.current-coordinate-edition { + // Center regarding to timeline tools + bottom: calc( + $timeline-with-legend-height + ($timeline-tools-height - $map-scale-height) / 2 + $map-scale-height + + $sm-spacing + ); + } } } @@ -367,89 +380,6 @@ } } -.arlas-map-action-container { - position: absolute; - right: 30px; - bottom: 0; - display: flex; - flex-direction: row-reverse; - background-color: white; - @include box-border(); - - .arlas-map-action-items { - height: $map-actions-width; - width: $map-actions-width; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - border-right: $xs-border solid #ddd; - color: black; - - &:hover { - background-color: rgba(0, 0, 0, 0.05); - } - - &:first-child { - border-right: unset; - } - } -} - -.if-geocoding-button { - bottom: 30px; -} -.arlas-rasters-manager { - position: absolute; - top: calc( - $sm-spacing + $map-attributions-height + $sm-spacing + $map-actions-width + $sm-spacing + $map-actions-length + - $sm-spacing + $map-settings-height + $sm-spacing - ); - right: $sm-spacing; - z-index: 2; -} -.arlas-rasters-manager--geocoding { - top: calc( - $sm-spacing + $map-attributions-height + $sm-spacing + $map-actions-width + $sm-spacing + $map-actions-length + - $sm-spacing + $map-settings-height + $map-actions-width + $sm-spacing - ); -} - -.arlas-map-settings { - position: absolute; - top: calc( - $sm-spacing + $map-attributions-height + $sm-spacing + $map-actions-width + $sm-spacing + $map-actions-length + - $sm-spacing - ); - right: $sm-spacing; - z-index: 2; - - .arlas-map-settings-container { - display: flex; - flex-direction: column; - @include box-border(); - background-color: white; - - .arlas-map-settings-items { - height: $map-actions-width; - width: $map-actions-width; - cursor: pointer; - display: flex; - justify-content: center; - align-items: center; - border-top: 1px solid #ddd; - - &:hover { - background-color: rgba(0, 0, 0, 0.05); - } - - &:first-child { - border-top: none !important; - } - } - } -} - .arlas-map ::ng-deep.basemap-container { top: calc( $sm-spacing + $map-attributions-height + $sm-spacing + $map-actions-width + $sm-spacing + $map-actions-length + @@ -459,22 +389,6 @@ z-index: 1; } -.arlas-zoom-to-data { - margin: 0 4px; - background-color: #ff4081; - color: white; - min-width: 40px; - border-radius: 4px; - line-height: 28px; - cursor: pointer; -} - -.arlas-zoom-to-data-icon { - vertical-align: middle; - padding-left: 7px; - margin: 3px 0; -} - .sidenav-content { margin-left: $left-menu-width; } @@ -488,35 +402,6 @@ height: calc(100vh - $top-menu-height); } -.aoi-dimensions { - position: absolute; - - right: calc($sm-spacing + $map-actions-width + $sm-spacing); - top: calc( - $sm-spacing + $map-attributions-height + $sm-spacing + $map-actions-width + $sm-spacing + $map-actions-length + - $sm-spacing + $map-actions-width - ); - z-index: 3; -} -::ng-deep.bookmark-shortcut { - top: 30px !important; -} - -.result-list-tab-group ::ng-deep .mat-tab-label { - min-width: 60px; -} - -.result-list-tab-group ::ng-deep .mat-tab-label-content { - display: flex; - justify-content: space-between; - width: 100%; - gap: 0 10px; -} - -.result-list-tab-group ::ng-deep .mat-tab-label-content .name { - flex: 5; -} - .top-left-menu { display: flex; align-items: center; @@ -731,6 +616,13 @@ } } } + +.timeline__legend--hidden { + ::ng-deep .arlas-timeline--legend { + display: none; + } +} + .empty_mode { display: flex; flex-direction: column; diff --git a/src/app/components/arlas-wui-root/arlas-wui-root.component.spec.ts b/src/app/components/arlas-wui-root/arlas-wui-root.component.spec.ts index f34e5295..e2d1d028 100644 --- a/src/app/components/arlas-wui-root/arlas-wui-root.component.spec.ts +++ b/src/app/components/arlas-wui-root/arlas-wui-root.component.spec.ts @@ -1,5 +1,24 @@ +/* + * Licensed to Gisaïa under one or more contributor + * license agreements. See the NOTICE.txt file distributed with + * this work for additional information regarding copyright + * ownership. Gisaïa licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + import { APP_BASE_HREF } from '@angular/common'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatChipsModule } from '@angular/material/chips'; @@ -12,54 +31,76 @@ import { MatSelectModule } from '@angular/material/select'; import { MatTooltipModule } from '@angular/material/tooltip'; import { RouterModule } from '@angular/router'; import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { GetResultlistConfigPipe } from 'app/pipes/get-resultlist-config.pipe'; +import { ContributorService } from 'app/services/contributors.service'; +import { ResultlistService } from 'app/services/resultlist.service'; +import { VisualizeService } from 'app/services/visualize.service'; +import { HistogramModule } from 'arlas-web-components'; import { - ArlasCollaborativesearchService, ArlasConfigService, ArlasStartupService, ArlasTaggerModule, ArlasToolKitModule, ArlasToolkitSharedModule + ArlasCollaborativesearchService, ArlasCollectionService, ArlasConfigService, ArlasSettingsService, + ArlasStartupService, ArlasTaggerModule, ArlasToolKitModule, ArlasToolkitSharedModule } from 'arlas-wui-toolkit'; -import { LeftMenuComponent } from '../left-menu/left-menu.component'; - -import { HistogramModule, MapglImportModule, MapglModule, MapglSettingsModule } from 'arlas-web-components'; import { ArlasWuiRootComponent } from './arlas-wui-root.component'; -import { ContributorService } from 'app/services/contributors.service'; -import { ResultlistService } from 'app/services/resultlist.service'; describe('ArlasWuiRootComponent', () => { let component: ArlasWuiRootComponent; let fixture: ComponentFixture; - let arlasStartupService: ArlasStartupService; - beforeEach(() => { - TestBed.configureTestingModule({ + beforeEach(async () => { + const mockSettingsService = jasmine.createSpyObj('ArlasSettingsService', + ['settings', 'getAuthentSettings', 'getPersistenceSettings', 'getPermissionSettings', 'getSettings', 'getArlasHubUrl', 'setSettings', + 'getLinksSettings', 'getTicketingKey']); + mockSettingsService.settings = { tab_name: 'Test' }; + mockSettingsService.getAuthentSettings.and.returnValue(); + mockSettingsService.getPersistenceSettings.and.returnValue(); + mockSettingsService.getPermissionSettings.and.returnValue(); + mockSettingsService.getSettings.and.returnValue(); + mockSettingsService.getArlasHubUrl.and.returnValue(); + mockSettingsService.setSettings.and.returnValue(); + mockSettingsService.getLinksSettings.and.returnValue(); + mockSettingsService.getTicketingKey.and.returnValue(); + + const mockContributorService = jasmine.createSpyObj('ContributorService', ['getSearchContributors']); + mockContributorService.getSearchContributors.and.returnValue(); + + await TestBed.configureTestingModule({ imports: [ MatIconModule, MatAutocompleteModule, MatInputModule, ReactiveFormsModule, ArlasToolKitModule, FormsModule, MatChipsModule, MatTooltipModule, RouterModule, HistogramModule, MatSelectModule, MatMenuModule, MatProgressBarModule, MatRadioModule, TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } }), - MapglModule, - ArlasTaggerModule, MapglImportModule, MapglSettingsModule, ArlasToolkitSharedModule, + ArlasTaggerModule, ArlasToolkitSharedModule, ], declarations: [ - ArlasWuiRootComponent, LeftMenuComponent + ArlasWuiRootComponent, + GetResultlistConfigPipe ], providers: [ ArlasCollaborativesearchService, ArlasConfigService, - ContributorService, + { + provide: ContributorService, + useValue: mockContributorService + }, ArlasStartupService, { provide: APP_BASE_HREF, useValue: '/' }, - ResultlistService - ] + ResultlistService, + VisualizeService, + { + provide: ArlasSettingsService, + useValue: mockSettingsService + }, + ArlasCollectionService + ], + teardown: { destroyAfterEach: false } }).compileComponents(); + fixture = TestBed.createComponent(ArlasWuiRootComponent); + component = fixture.componentInstance; + fixture.detectChanges(); }); - beforeEach(() => { - arlasStartupService = TestBed.get(ArlasStartupService); - arlasStartupService.arlasIsUp.subscribe(isUp => { - if (isUp) { - fixture = TestBed.createComponent(ArlasWuiRootComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - } - }); + it('should create', () => { + expect(component).toBeTruthy(); }); }); diff --git a/src/app/components/arlas-wui-root/arlas-wui-root.component.ts b/src/app/components/arlas-wui-root/arlas-wui-root.component.ts index e10bc5da..7f13bfba 100644 --- a/src/app/components/arlas-wui-root/arlas-wui-root.component.ts +++ b/src/app/components/arlas-wui-root/arlas-wui-root.component.ts @@ -17,82 +17,27 @@ * under the License. */ -import { - AfterViewInit, - ChangeDetectorRef, - Component, - Input, - OnDestroy, - OnInit, - Output, - ViewChild -} from '@angular/core'; -import { MatDialog, MatDialogRef } from '@angular/material/dialog'; -import { MatIconRegistry } from '@angular/material/icon'; -import { MatSnackBar } from '@angular/material/snack-bar'; -import { MatTabGroup } from '@angular/material/tabs'; -import { DomSanitizer, Title } from '@angular/platform-browser'; +import { AfterViewInit, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { Title } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; -import { marker } from '@biesbjerg/ngx-translate-extract-marker'; -import { TranslateService } from '@ngx-translate/core'; -import { ResultlistService } from 'app/services/resultlist.service'; -import { CollectionReferenceParameters } from 'arlas-api'; -import { - AoiEdition, - ArlasAnyLayer, - ArlasColorService, - BboxGeneratorComponent, - CellBackgroundStyleEnum, - ChartType, - Column, - DataType, - GeoQuery, - Item, - MapglComponent, - MapglImportComponent, - MapglSettingsComponent, - ModeEnum, - PageQuery, - Position, - ResultListComponent, - SCROLLABLE_ARLAS_ID, - SortEnum -} from 'arlas-web-components'; -import { - AnalyticsContributor, - ElementIdentifier, - FeatureRenderMode, - MapContributor, - ResultListContributor, - SearchContributor -} from 'arlas-web-contributors'; -import { LegendData } from 'arlas-web-contributors/contributors/MapContributor'; +import { ArlasListComponent } from '@components/arlas-list/arlas-list.component'; +import { ArlasMapComponent } from '@components/arlas-map/arlas-map.component'; +import { MenuState } from '@components/left-menu/left-menu.component'; +import { ContributorService } from '@services/contributors.service'; +import { MapService } from '@services/map.service'; +import { ResultlistService } from '@services/resultlist.service'; +import { VisualizeService } from '@services/visualize.service'; +import { Item, ModeEnum } from 'arlas-web-components'; +import { SearchContributor } from 'arlas-web-contributors'; import { - AiasDownloadComponent, - AnalyticsService, - ArlasCollaborativesearchService, - ArlasCollectionService, - ArlasConfigService, - ArlasExportCsvService, - ArlasMapService, - ArlasMapSettings, - ArlasSettingsService, - ArlasStartupService, - FilterShortcutConfiguration, - NOT_CONFIGURED, - ProcessService, - TimelineComponent, - ZoomToDataStrategy + AnalyticsService, ArlasCollaborativesearchService, ArlasCollectionService, ArlasConfigService, ArlasExportCsvService, ArlasMapService, + ArlasMapSettings, ArlasSettingsService, ArlasStartupService, FilterShortcutConfiguration, + getParamValue, NOT_CONFIGURED, ProcessService, TimelineComponent, ZoomToDataStrategy } from 'arlas-wui-toolkit'; -import * as mapboxgl from 'mapbox-gl'; -import { BehaviorSubject, fromEvent, merge, Observable, of, Subject, zip } from 'rxjs'; -import { debounceTime, finalize, mergeMap, takeUntil } from 'rxjs/operators'; +import { fromEvent, Subject } from 'rxjs'; +import { debounceTime, takeUntil } from 'rxjs/operators'; import { environment } from '../../../environments/environment'; -import { ContributorService } from '../../services/contributors.service'; -import { DynamicComponentService } from '../../services/dynamicComponent.service'; -import { GeocodingResult } from '../../services/geocoding.service'; -import { VisualizeService } from '../../services/visualize.service'; -import { MenuState } from '../left-menu/left-menu.component'; @Component({ selector: 'arlas-wui-root', @@ -100,94 +45,36 @@ import { MenuState } from '../left-menu/left-menu.component'; styleUrls: ['./arlas-wui-root.component.scss'], }) export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { + /** + * @Input : Angular + * Current version of ARLAS WUI + */ @Input() public version: string; - @Output() public actionOnPopup = new Subject<{ - action: { - id: string; - label: string; - collection: string; - cssClass?: string | string[]; - tooltip?: string; - }; - elementidentifier: ElementIdentifier; - }>(); - @Output() public actionOnList = new Subject<{ origin: string; event: string; data?: any; }>(); - public coordinatesHaveSpace = true; - public modeEnum = ModeEnum; - public mapglContributors: Array = new Array(); public searchContributors: SearchContributor[]; - public resultlistContributors: Array = new Array(); - public analyticsContributor: AnalyticsContributor; - - public sortOutput = new Map(); - - public analytics: Array; - public dataType = DataType; - public chartType = ChartType; - public position = Position; public appName: string; - // component config - public mapComponentConfig: any; + // Component config public timelineComponentConfig: any; public detailedTimelineComponentConfig: any; - /** - * Whether the legend of the timeline is displayed. If both the analytics and the list are open, then the legend is hidden - */ - public isTimelineLegend = true; - public resultListsConfig = []; - public resultListConfigPerContId = new Map(); - public resultlistIsExporting = false; - - public fitbounds: Array> = []; - public featureToHightLight: { - isleaving: boolean; - elementidentifier: ElementIdentifier; + public menuState: MenuState = { + configs: false }; - public featuresToSelect: Array = []; - public nbVerticesLimit = 50; - public isMapMenuOpen = false; - public shouldCloseMapMenu = true; - public menuState: MenuState; - public searchOpen = true; - public mapId = 'mapgl'; - public centerLatLng: { lat: number; lng: number; } = { lat: 0, lng: 0 }; - public previewListContrib: ResultListContributor = null; - public rightListContributors: Array = new Array(); + /* Options */ public spinner: { show: boolean; diameter: string; color: string; strokeWidth: number; } = { show: false, diameter: '60', color: 'accent', strokeWidth: 5 }; + public showIndicators = false; + public showTimeline = true; + /** + * Whether the legend of the timeline is displayed. If both the analytics and the list are open, then the legend is hidden + */ + public showTimelineLegend = true; public zoomToStrategy = ZoomToDataStrategy.NONE; public showZoomToData = false; - public showIndicators = false; - public onSideNavChange: boolean; - public defaultBaseMap; - public mapDataSources; - public mapRedrawSources; - public mapLegendUpdater = new Subject>>(); - public mapVisibilityUpdater; - /** Visibility status of layers on the map */ - public layersVisibilityStatus: Map = new Map(); - public mainMapContributor: MapContributor; - public mainCollection: string; - public geojsondraw: { type: string; features: Array; } = { - 'type': 'FeatureCollection', - 'features': [] - }; - public isTimelineOpen = true; - public recalculateExtend = true; - public zoomChanged = false; - public zoomStart: number; - public aoiEdition: AoiEdition; - - private disableRecalculateExtend = false; - private currentClickedFeatureId: string = undefined; - @Input() public hiddenAnalyticsTabs: string[] = []; - @Input() public hiddenResultlistTabs: string[] = []; /** * @Input : Angular * @description Width in pixels of the preview result list @@ -203,15 +90,11 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { * @description Number of columns in the grid result list */ @Input() public resultListGridColumns = 4; - public isGeoSortActivated = new Map(); - public collectionToDescription = new Map(); public collections: string[]; - @ViewChild('map', { static: false }) public mapglComponent: MapglComponent; - @ViewChild('import', { static: false }) public mapImportComponent: MapglImportComponent; - @ViewChild('mapSettings', { static: false }) public mapSettings: MapglSettingsComponent; - @ViewChild('tabsList', { static: false }) public tabsList: MatTabGroup; + @ViewChild('timeline', { static: false }) public timelineComponent: TimelineComponent; - @ViewChild('resultsidenav', { static: false }) public resultListComponent: ResultListComponent; + @ViewChild('arlasMap', { static: false }) public arlasMapComponent: ArlasMapComponent; + @ViewChild('arlasList', { static: false }) public arlasListComponent: ArlasListComponent; /** Shortcuts */ public shortcuts = new Array(); @@ -236,20 +119,6 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { */ public spacing = 5; - public mapAttributionPosition: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' = 'top-right'; - private allowMapExtend: boolean; - private mapBounds: mapboxgl.LngLatBounds; - private mapEventListener = new Subject(); - private mapExtendTimer: number; - private MAP_EXTEND_PARAM = 'extend'; - - /** Geocoding */ - protected showGeocodingPopup = new BehaviorSubject(false); - protected enableGeocodingFeature = !!this.arlasSettingsService.getGeocodingSettings()?.enabled; - - /* Process */ - private downloadDialogRef: MatDialogRef; - /** Destroy subscriptions */ private _onDestroy$ = new Subject(); @@ -258,37 +127,27 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { protected settingsService: ArlasSettingsService, public collaborativeService: ArlasCollaborativesearchService, private contributorService: ContributorService, - public arlasStartUpService: ArlasStartupService, + public arlasStartupService: ArlasStartupService, private mapSettingsService: ArlasMapSettings, - private iconRegistry: MatIconRegistry, - private domSanitizer: DomSanitizer, private cdr: ChangeDetectorRef, - private mapService: ArlasMapService, - private colorService: ArlasColorService, + private toolkitMapService: ArlasMapService, private titleService: Title, - private arlasSettingsService: ArlasSettingsService, - private dynamicComponentService: DynamicComponentService, public visualizeService: VisualizeService, - private translate: TranslateService, - private snackbar: MatSnackBar, private activatedRoute: ActivatedRoute, private router: Router, public analyticsService: AnalyticsService, private dialog: MatDialog, private generateAoiDialog: MatDialog, private processService: ProcessService, - public resultlistService: ResultlistService, private exportService: ArlasExportCsvService, - private collectionService: ArlasCollectionService + private collectionService: ArlasCollectionService, + protected resultlistService: ResultlistService, + protected mapService: MapService ) { - this.menuState = { - configs: false - }; - if (this.arlasStartUpService.shouldRunApp && !this.arlasStartUpService.emptyMode) { + if (this.arlasStartupService.shouldRunApp && !this.arlasStartupService.emptyMode) { /** resize the map */ fromEvent(window, 'resize').pipe(debounceTime(100)) .subscribe((event: Event) => { - this.mapglComponent.map.resize(); this.resizeCollectionCounts(); this.adjustVisibleShortcuts(); this.adjustComponentsSize(); @@ -296,23 +155,10 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { this.appName = this.configService.appName ?? (this.configService.getValue('arlas-wui.web.app.name') ?? 'ARLAS'); - this.analyticsContributor = this.arlasStartUpService.contributorRegistry.get('analytics') as AnalyticsContributor; - this.mapComponentConfig = this.configService.getValue('arlas.web.components.mapgl.input'); - const mapExtendTimer = this.configService.getValue('arlas.web.components.mapgl.mapExtendTimer'); - this.mapExtendTimer = (mapExtendTimer !== undefined) ? mapExtendTimer : 4000; - this.allowMapExtend = this.configService.getValue('arlas.web.components.mapgl.allowMapExtend'); - this.nbVerticesLimit = this.configService.getValue('arlas.web.components.mapgl.nbVerticesLimit'); this.timelineComponentConfig = this.configService.getValue('arlas.web.components.timeline'); this.detailedTimelineComponentConfig = this.configService.getValue('arlas.web.components.detailedTimeline'); this.zoomToStrategy = this.configService.getValue('arlas.web.options.zoom_to_strategy'); - this.mainCollection = this.configService.getValue('arlas.server.collection.name'); - this.defaultBaseMap = !!this.mapComponentConfig.defaultBasemapStyle ? this.mapComponentConfig.defaultBasemapStyle : - { - styleFile: 'http://demo.arlas.io:82/styles/positron/style.json', - name: 'Positron' - }; - if (this.configService.getValue('arlas.web.options.spinner')) { this.spinner = Object.assign(this.spinner, this.configService.getValue('arlas.web.options.spinner')); } @@ -325,42 +171,19 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { if (this.configService.getValue('arlas.web.options.indicators')) { this.showIndicators = true; } - - /** init from url */ - const queryParamVisibleVisualisations = this.getParamValue('vs'); - if (queryParamVisibleVisualisations) { - const visibleVisuSet = new Set(queryParamVisibleVisualisations.split(';').map(n => decodeURI(n))); - this.mapComponentConfig.visualisations_sets.forEach(v => v.enabled = visibleVisuSet.has(v.name)); - } - - this.isTimelineOpen = this.getParamValue('to') === 'true'; - - let wasTabSelected = this.getParamValue('at') !== null; - this.analyticsService.tabChange.subscribe(tab => { - // If there is a change in the state of the analytics (open/close), resize - if (wasTabSelected !== (tab !== undefined)) { - this.adjustComponentsSize(); - wasTabSelected = (tab !== undefined); - } - this.updateTimelineLegendVisibility(); - }); - } else { - // Update default basemap style ? - this.defaultBaseMap = { - styleFile: 'http://demo.arlas.io:82/styles/positron/style.json', - name: 'Positron' - }; } - } - public openAoiGenerator() { - this.generateAoiDialog.open(BboxGeneratorComponent, { - data: { - initCorner: { - lat: this.mapComponentConfig.initCenter ? this.mapComponentConfig.initCenter[1] : 0, - lng: this.mapComponentConfig.initCenter ? this.mapComponentConfig.initCenter[0] : 0, - } + /** init from url */ + this.showTimeline = getParamValue('to') === 'true'; + + let wasTabSelected = getParamValue('at') !== null; + this.analyticsService.tabChange.subscribe(tab => { + // If there is a change in the state of the analytics (open/close), resize + if (wasTabSelected !== (tab !== undefined)) { + this.adjustComponentsSize(); + wasTabSelected = (tab !== undefined); } + this.updateTimelineLegendVisibility(); }); } @@ -369,208 +192,28 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { this._onDestroy$.complete(); } - public downloadLayerSource(d) { - const mc = this.mapglContributors.find(mc => mc.collection === d.collection); - if (mc) { - mc.downloadLayerSource(d.sourceName, d.layerName, d.downloadType, this.collectionService.displayFieldName); - } - } - public ngOnInit() { - if (!this.version) { this.version = environment.VERSION; } this.setAppTitle(); - if (this.arlasStartUpService.shouldRunApp && !this.arlasStartUpService.emptyMode) { - /** Retrieve displayable analytics */ - const hiddenAnalyticsTabsSet = new Set(this.hiddenAnalyticsTabs); - const allAnalytics = this.arlasStartUpService.analytics; - this.analyticsService.initializeGroups(!!allAnalytics ? allAnalytics.filter(a => !hiddenAnalyticsTabsSet.has(a.tab)) : []); - /** Retrieve displayable resultlists */ - const hiddenListsTabsSet = new Set(this.hiddenResultlistTabs); - const allResultlists = this.configService.getValue('arlas.web.components.resultlists'); - const allContributors = this.configService.getValue('arlas.web.contributors'); - this.resultListsConfig = !!allResultlists ? allResultlists.filter(a => { - const contId = a.contributorId; - const tab = allContributors.find(c => c.identifier === contId).name; - return !hiddenListsTabsSet.has(tab); - }) : []; - /** Prepare map data */ - this.mapglContributors = this.contributorService.getMapContributors(); - this.mainMapContributor = this.mapglContributors.filter(m => !!m.collection || m.collection === this.mainCollection)[0]; - this.mapDataSources = this.mapglContributors.map(c => c.dataSources).length > 0 ? - this.mapglContributors.map(c => c.dataSources).reduce((set1, set2) => new Set([...set1, ...set2])) : new Set(); - this.mapRedrawSources = merge(...this.mapglContributors.map(c => c.redrawSource)); - - const legendUpdaters: Observable<{ collection: string; legendData: Map; }> = - merge(...this.mapglContributors - .map(c => c.legendUpdater - .pipe(mergeMap(m => of({ collection: c.collection, legendData: m }))) - )); - const legendData = new Map>(); - legendUpdaters - .pipe(takeUntil(this._onDestroy$)) - .subscribe(lg => { - legendData.set(lg.collection, lg.legendData); - this.mapLegendUpdater.next(legendData); - }); - - this.mapVisibilityUpdater = merge(...this.mapglContributors.map(c => c.visibilityUpdater)); - this.mapglContributors.forEach(contrib => contrib.drawingsUpdate.subscribe(() => { - this.geojsondraw = { - 'type': 'FeatureCollection', - 'features': this.mapglContributors.map(c => c.geojsondraw.features).reduce((a, b) => a.concat(b)) - .filter((v, i, a) => a.findIndex(t => (t.properties.arlas_id === v.properties.arlas_id)) === i) - }; - })); + if (this.arlasStartupService.shouldRunApp && !this.arlasStartupService.emptyMode) { this.searchContributors = this.contributorService.getSearchContributors(); - const ids = new Set(this.resultListsConfig.map(c => c.contributorId)); - this.arlasStartUpService.contributorRegistry.forEach((v, k) => { - if (v instanceof ResultListContributor) { - v.updateData = ids.has(v.identifier); - this.resultlistContributors.push(v); - } - }); - this.resultlistService.setContributors(this.resultlistContributors, this.resultListsConfig); - - if (this.resultlistContributors.length > 0) { - this.rightListContributors = this.resultlistContributors - .filter(c => this.resultListsConfig.some((rc) => c.identifier === rc.contributorId)) - .map(rlcontrib => { - (rlcontrib as any).name = rlcontrib.getName(); - const sortColumn = rlcontrib.fieldsList.find(c => !!(c as any).sort && (c as any).sort !== ''); - if (!!sortColumn) { - this.sortOutput.set(rlcontrib.identifier, { - columnName: sortColumn.columnName, - fieldName: sortColumn.fieldName, - sortDirection: (sortColumn as any).sort === 'asc' ? SortEnum.asc : SortEnum.desc - }); - } - return rlcontrib; - }); - - this.resultListsConfig.forEach(rlConf => { - rlConf.input.cellBackgroundStyle = !!rlConf.input.cellBackgroundStyle ? - CellBackgroundStyleEnum[rlConf.input.cellBackgroundStyle] : undefined; - this.resultListConfigPerContId.set(rlConf.contributorId, rlConf.input); - }); - - this.resultlistContributors.forEach(c => { - const listActionsId = c.actionToTriggerOnClick.map(a => a.id); - const mapcontributor = this.mapglContributors.find(mc => mc.collection === c.collection); - if (!!this.resultListConfigPerContId.get(c.identifier)) { - if (!!this.resultListConfigPerContId.get(c.identifier).visualisationLink && !listActionsId.includes('visualize')) { - c.addAction({ - id: 'visualize', label: marker('Visualize'), icon: 'visibility', cssClass: '', tooltip: marker('Visualize on the map'), - reverseAction: { - id: 'remove', label: marker('Remove from map'), cssClass: '', tooltip: marker('Remove from map'), icon: 'visibility_off' - }, - fields: this.visualizeService.getVisuFields(this.resultListConfigPerContId.get(c.identifier).visualisationLink), - hide: true - } as any); - } - if (!!this.resultListConfigPerContId.get(c.identifier).downloadLink && !listActionsId.includes('download')) { - c.addAction({ id: 'download', label: 'Download', cssClass: '', tooltip: 'Download' }); - } - } - if (!!mapcontributor && !listActionsId.includes('zoomToFeature')) { - c.addAction({ id: 'zoomToFeature', label: marker('Zoom to'), cssClass: '', tooltip: marker('Zoom to product') }); - } - }); - this.declareResultlistExportCsv(); - // Check if the user can access process endpoint - const processSettings = this.arlasSettingsService.getProcessSettings(); - const externalNode = this.configService.getValue('arlas.web.externalNode'); - if ( - !!processSettings && !!processSettings.url - && !!externalNode && !!externalNode.download && externalNode.download === true - ) { - this.processService.check() - .pipe(takeUntil(this._onDestroy$)) - .subscribe({ - next: () => { - this.resultlistContributors.forEach(c => { - const listActionsId = c.actionToTriggerOnClick.map(a => a.id); - if (!listActionsId.includes('production')) { - c.addAction({ id: 'production', label: 'Download', cssClass: '', tooltip: 'Download' }); - const resultConfig = this.resultListConfigPerContId.get(c.identifier); - if (resultConfig) { - if (!resultConfig.globalActionsList) { - resultConfig.globalActionsList = []; - } - resultConfig.globalActionsList.push({ 'id': 'production', 'label': 'Download' }); - } - } - - }); - } - }); - } - - const selectedResultlistTab = this.getParamValue('rt'); - const previewListContrib = this.rightListContributors.find(r => r.getName() === decodeURI(selectedResultlistTab)); - if (previewListContrib) { - this.previewListContrib = previewListContrib; - } else { - this.previewListContrib = this.rightListContributors[0]; - } - } + this.collections = [...new Set(Array.from(this.collaborativeService.registry.values()).map(c => c.collection))]; - this.actionOnPopup - .pipe(takeUntil(this._onDestroy$)) - .subscribe(data => { - const collection = data.action.collection; - const mapContributor = this.mapglContributors.filter(m => m.collection === collection)[0]; - const listContributor = this.resultlistContributors.filter(m => m.collection === collection)[0]; - this.actionOnItemEvent(data, mapContributor, listContributor, collection); - }); + this.shortcuts = this.arlasStartupService.filtersShortcuts; - if (this.allowMapExtend) { - const extendValue = this.getParamValue(this.MAP_EXTEND_PARAM); - if (extendValue) { - const stringBounds = extendValue.split(','); - if (stringBounds.length === 4) { - this.mapBounds = new mapboxgl.LngLatBounds( - new mapboxgl.LngLat(+stringBounds[0], +stringBounds[1]), - new mapboxgl.LngLat(+stringBounds[2], +stringBounds[3]) - ); - } - } - } - this.collections = [...new Set(Array.from(this.collaborativeService.registry.values()).map(c => c.collection))]; - zip(...this.collections.map(c => this.collaborativeService.describe(c))) + this.resultlistService.listOpenChange .pipe(takeUntil(this._onDestroy$)) - .subscribe(cdrs => { - cdrs.forEach(cdr => { - this.collectionToDescription.set(cdr.collection_name, cdr.params); - }); - const bounds = (this.mapglComponent.map)?.getBounds(); - if (!!bounds) { - (this.mapglComponent.map).fitBounds(bounds, { duration: 0 }); - } - if (this.resultlistContributors.length > 0) { - this.resultlistContributors.forEach(c => c.sort = this.collectionToDescription.get(c.collection).id_path); - } - this.mapglContributors.forEach(mapContrib => { - mapContrib.colorGenerator = this.colorService.colorGenerator; - if (!!this.resultlistContributors) { - const resultlistContrbutor: ResultListContributor = this.resultlistContributors - .find(resultlistContrib => resultlistContrib.collection === mapContrib.collection); - if (!!resultlistContrbutor) { - mapContrib.searchSize = resultlistContrbutor.pageSize; - mapContrib.searchSort = resultlistContrbutor.sort; - } else { - mapContrib.searchSize = 50; - } - } - }); + .subscribe(o => { + this.updateTimelineLegendVisibility(); + this.adjustGrids(); + this.adjustComponentsSize(); + this.adjustVisibleShortcuts(); }); - this.shortcuts = this.arlasStartUpService.filtersShortcuts; - this.collaborativeService.ongoingSubscribe .pipe(takeUntil(this._onDestroy$)) .subscribe(() => { @@ -590,645 +233,92 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { } }); } - - // eslint-disable-next-line max-len - this.iconRegistry.addSvgIconLiteral('bbox', this.domSanitizer.bypassSecurityTrustHtml('')); - - // eslint-disable-next-line max-len - this.iconRegistry.addSvgIconLiteral('draw_polygon', this.domSanitizer.bypassSecurityTrustHtml(' image/svg+xml ')); - - // eslint-disable-next-line max-len - this.iconRegistry.addSvgIconLiteral('draw_circle', this.domSanitizer.bypassSecurityTrustHtml('')); - - // eslint-disable-next-line max-len - this.iconRegistry.addSvgIconLiteral('remove_polygon', this.domSanitizer.bypassSecurityTrustHtml(' image/svg+xml ')); - - // eslint-disable-next-line max-len - this.iconRegistry.addSvgIconLiteral('import_polygon', this.domSanitizer.bypassSecurityTrustHtml('')); - - // eslint-disable-next-line max-len - this.iconRegistry.addSvgIconLiteral('map_settings', this.domSanitizer.bypassSecurityTrustHtml('')); - } - - public isElementInViewport(el) { - if (el) { - const rect = el.getBoundingClientRect(); - return (rect.top >= 0 && - rect.left >= 0 && - rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && - rect.right <= (window.innerWidth || document.documentElement.clientWidth)); - } else { - return false; - } - } - - public declareResultlistExportCsv() { - if (this.settingsService.isResultListExportEnabled()) { - this.resultlistContributors.forEach(c => { - const resultConfig = this.resultListConfigPerContId.get(c.identifier); - if (resultConfig) { - if (!resultConfig.globalActionsList) { - resultConfig.globalActionsList = []; - } - resultConfig.globalActionsList.push({ 'id': 'export_csv', 'label': 'Export csv', 'alwaysEnabled': true }); - } - }); - } } public ngAfterViewInit(): void { - if (!this.arlasStartUpService.emptyMode) { + if (!this.arlasStartupService.emptyMode) { + const isListOpen = getParamValue('ro') === 'true'; + if (isListOpen) { + this.resultlistService.toggleList(); + } this.resizeCollectionCounts(); this.adjustVisibleShortcuts(); this.adjustComponentsSize(); + // Keep the last displayed list as preview when closing the right panel - if (!!this.tabsList) { - this.tabsList.selectedIndexChange + if (!!this.arlasListComponent && !!this.arlasListComponent.tabsList) { + this.arlasListComponent.tabsList.selectedIndexChange .pipe(takeUntil(this._onDestroy$)) .subscribe(index => { - this.resultlistService.selectedListTabIndex = index; - this.previewListContrib = this.resultlistContributors[index]; + this.resultlistService.selectList(index); const queryParams = Object.assign({}, this.activatedRoute.snapshot.queryParams); - queryParams['rt'] = this.previewListContrib.getName(); + queryParams['rt'] = this.resultlistService.previewListContrib.getName(); this.router.navigate([], { replaceUrl: true, queryParams: queryParams }); this.adjustGrids(); this.adjustComponentsSize(); }); } - - this.mapEventListener - .pipe( - takeUntil(this._onDestroy$), - debounceTime(this.mapExtendTimer)) - .subscribe(() => { - /** Change map extend in the url */ - const bounds = (this.mapglComponent.map).getBounds(); - const extend = bounds.getWest() + ',' + bounds.getSouth() + ',' + bounds.getEast() + ',' + bounds.getNorth(); - const queryParams = Object.assign({}, this.activatedRoute.snapshot.queryParams); - queryParams[this.MAP_EXTEND_PARAM] = extend; - this.router.navigate([], { replaceUrl: true, queryParams: queryParams }); - - }); - - this.cdr.detectChanges(); - } - } - - public onMapLoaded(isLoaded: boolean): void { - /** wait until the map component loading is finished before fetching the data */ - if (isLoaded && !this.arlasStartUpService.emptyMode) { - - this.mapService.setMap(this.mapglComponent.map); - this.visualizeService.setMap(this.mapglComponent.map); - if (this.mapBounds && this.allowMapExtend) { - (this.mapglComponent.map).fitBounds(this.mapBounds, { duration: 0 }); - this.mapBounds = null; - } - this.mapglComponent.map.on('movestart', (e) => { - this.zoomStart = this.mapglComponent.map.getZoom(); - }); - this.mapglComponent.map.on('moveend', (e) => { - if (Math.abs(this.mapglComponent.map.getZoom() - this.zoomStart) > 1) { - this.zoomChanged = true; - } - if (this.allowMapExtend) { - this.mapEventListener.next(null); - } - }); - this.adjustMapOffset(); - this.adjustCoordinates(); - this.mapglContributors.forEach(mapglContributor => { - mapglContributor.updateData = true; - mapglContributor.fetchData(null); - mapglContributor.setSelection(null, this.collaborativeService.getCollaboration(mapglContributor.identifier)); - }); - - // If there is a list displayed, sync window layers' data - if (!!this.previewListContrib && this.previewListContrib.data.length > 0 && - this.mapComponentConfig.mapLayers.events.onHover.filter(l => this.mapglComponent.map.getLayer(l)).length > 0) { - this.updateVisibleItems(); - } } } public setAppTitle() { - const prefixTitle = (!!this.arlasSettingsService.settings.tab_name && this.arlasSettingsService.settings.tab_name !== NOT_CONFIGURED) ? - this.arlasSettingsService.settings.tab_name : ''; + const prefixTitle = (!!this.settingsService.settings.tab_name && this.settingsService.settings.tab_name !== NOT_CONFIGURED) ? + this.settingsService.settings.tab_name : ''; this.titleService.setTitle(prefixTitle === '' ? this.appName : prefixTitle.concat(' - ').concat(this.appName)); } - /** - * Update which elements from the list are visible on the map - */ - public updateVisibleItems() { - if (this.previewListContrib && !!this.collectionToDescription.get(this.previewListContrib.collection)) { - const idFieldName = this.collectionToDescription.get(this.previewListContrib.collection).id_path; - const visibleItems = this.previewListContrib.data.map(i => (i.get(idFieldName) as number | string)) - .filter(i => i !== undefined && this.isElementInViewport(document.getElementById(i.toString()))); - this.updateMapStyle(visibleItems, this.previewListContrib.collection); - } - } - - public updateMapStyle(ids: Array, collection: string) { - // use always this.previewListContrib because it's the current resultlist contributor - if (!!this.mapComponentConfig.mapLayers.events.onHover) { - this.mapComponentConfig.mapLayers.events.onHover.forEach(l => { - const layer = this.mapglComponent.map.getLayer(l) as ArlasAnyLayer; - if (!!layer && typeof (layer.source) === 'string' && layer.source.indexOf(collection) >= 0) { - if (ids && ids.length > 0) { - // Tests value in camel and kebab case due to an unknown issue on other projects - if (layer.metadata.isScrollableLayer || layer.metadata['is-scrollable-layer']) { - this.mapglComponent.map.setFilter(l, this.getVisibleElementLayerFilter(l, ids)); - const strokeLayerId = l.replace('_id:', '-fill_stroke-'); - const strokeLayer = this.mapglComponent.map.getLayer(strokeLayerId); - if (!!strokeLayer) { - this.mapglComponent.map.setFilter(strokeLayerId, this.getVisibleElementLayerFilter(strokeLayerId, ids)); - } - } - } else { - this.mapglComponent.map.setFilter(l, this.mapglComponent.layersMap.get(l).filter); - const strokeLayerId = l.replace('_id:', '-fill_stroke-'); - const strokeLayer = this.mapglComponent.map.getLayer(strokeLayerId); - if (!!strokeLayer) { - this.mapglComponent.map.setFilter(strokeLayerId, - this.mapglComponent.layersMap.get(strokeLayerId).filter); - } - } - } - }); - } - } - - public updateMapStyleFromScroll(items: Array, collection: string) { - this.updateMapStyle(items.map(i => i.identifier), collection); - } - - /** - * Updates features style on map after repopulating the resultlist with data - * @param items List of items constituting the resultlist - */ - public updateMapStyleFromChange(items: Array>, collection: string) { - if (this.collectionToDescription.size > 0) { - const idFieldName = this.collectionToDescription.get(collection).id_path; - setTimeout(() => { - const visibleItems = items.map(item => item.get(idFieldName)) - .filter(id => id !== undefined && this.isElementInViewport(document.getElementById(id.toString()))); - this.updateMapStyle(visibleItems, collection); - }, 200); - } - } - public consumeMenuEvents(states: MenuState) { this.menuState = states; } - public openMapSettings(): void { - this.mapSettingsService.mapContributors = this.mapglContributors; - this.mapSettings.openDialog(this.mapSettingsService); - } - - /** - * Applies the selected geo query - */ - public applySelectedGeoQuery(geoQueries: Map) { - const configDebounceTime = this.configService.getValue('arlas.server.debounceCollaborationTime'); - const debounceDuration = configDebounceTime !== undefined ? configDebounceTime : 750; - const changedMapContributors = this.mapglContributors.filter(mc => !!geoQueries.has(mc.collection)); - for (let i = 0; i < changedMapContributors.length; i++) { - setTimeout(() => { - const collection = changedMapContributors[i].collection; - const geoQuery = geoQueries.get(collection); - changedMapContributors[i].setGeoQueryOperation(geoQuery.operation); - changedMapContributors[i].setGeoQueryField(geoQuery.geometry_path); - changedMapContributors[i].onChangeGeoQuery(); - this.snackbar.open(this.translate.instant('Updating Geo-query of ', - { collection: this.translate.instant(this.collectionService.getDisplayName(changedMapContributors[i].collection)) })); - if (i === changedMapContributors.length - 1) { - setTimeout(() => this.snackbar.dismiss(), 1000); - } - - }, (i) * (debounceDuration * 1.5)); - } - - } - - public setLyersVisibilityStatus(event) { - this.layersVisibilityStatus = event; - } - public zoomToData(collection: string): void { if (!this.mapSettingsService.mapContributors || this.mapSettingsService.mapContributors.length === 0) { - this.mapSettingsService.mapContributors = this.mapglContributors; + this.mapSettingsService.mapContributors = this.mapService.mapContributors; } let fieldPath: string; if ( this.zoomToStrategy === ZoomToDataStrategy.CENTROID || this.configService.getValue('arlas.web.options.zoom_to_data') // for backward compatibility ) { - fieldPath = this.collectionToDescription.get(collection).centroid_path; + fieldPath = this.resultlistService.collectionToDescription.get(collection).centroid_path; } else if (this.zoomToStrategy === ZoomToDataStrategy.GEOMETRY) { - fieldPath = this.collectionToDescription.get(collection).geometry_path; + fieldPath = this.resultlistService.collectionToDescription.get(collection).geometry_path; } - this.mapService.zoomToData(collection, fieldPath, this.mapglComponent.map, 0.2); - } - - - /** This method sorts the list on the given column. The features are also sorted if the `Simple mode` is activated in mapContributor */ - public sortColumnEvent(contributorId: string, sortOutput: Column) { - const resultlistContributor = (this.collaborativeService.registry.get(contributorId) as ResultListContributor); - this.isGeoSortActivated.set(contributorId, false); - /** Save the sorted column */ - this.sortOutput.set(contributorId, sortOutput); - /** Sort the list by the selected column and the id field name */ - resultlistContributor.sortColumn(sortOutput, true); - /** set mapcontritbutor sort */ - let sortOrder = null; - if (sortOutput.sortDirection.toString() === '0') { - sortOrder = ''; - } else if (sortOutput.sortDirection.toString() === '1') { - sortOrder = '-'; - } - let sort = ''; - if (sortOrder !== null) { - sort = sortOrder + sortOutput.fieldName; - } - - this.mapglContributors - .filter(c => c.collection === resultlistContributor.collection) - .forEach(c => { - // Could have some problems if we put 2 lists with the same collection and different sort ? - c.searchSort = resultlistContributor.sort; - c.searchSize = resultlistContributor.getConfigValue('search_size'); - /** Redraw features with setted sort in case of window mode */ - /** Remove old features */ - this.clearWindowData(c); - /** Set new features */ - c.drawGeoSearch(0, true); - }); - } - - /** - * Called at the end of scrolling the list - * @param contributor ResultlistContributor instance that fetches the data - * @param eventPaginate Which page is queried - */ - public paginate(contributor, eventPaginate: PageQuery): void { - contributor.getPage(eventPaginate.reference, eventPaginate.whichPage); - const sort = this.isGeoSortActivated.get(contributor.identifier) ? contributor.geoOrderSort : contributor.sort; - this.mapglContributors - .filter(c => c.collection === contributor.collection) - .forEach(c => c.getPage(eventPaginate.reference, sort, eventPaginate.whichPage, contributor.maxPages)); + this.toolkitMapService.zoomToData(collection, fieldPath, this.arlasMapComponent.mapglComponent.map, 0.2); } public clickOnTile(item: Item) { - this.tabsList.realignInkBar(); - const config = this.resultListConfigPerContId.get(this.previewListContrib.identifier); - config.defautMode = this.modeEnum.grid; + this.arlasListComponent.tabsList.realignInkBar(); + const config = this.resultlistService.resultlistConfigPerContId.get(this.resultlistService.previewListContrib.identifier); + config.defautMode = ModeEnum.grid; config.selectedGridItem = item; config.isDetailledGridOpen = true; - this.resultListConfigPerContId.set(this.previewListContrib.identifier, config); - this.resultlistService.toggleList(); - setTimeout(() => { - if (!!this.timelineComponent.timelineHistogramComponent) { - this.timelineComponent.timelineHistogramComponent.resizeHistogram(); - } - }, 100); - } - - public changeListResultMode(mode: ModeEnum, identifier: string) { - const config = this.resultListConfigPerContId.get(identifier); - config.defautMode = mode; - this.resultListConfigPerContId.set(identifier, config); - setTimeout(() => { - this.updateVisibleItems(); - }, 0); - } - - public reloadMapImages() { - this.visualizeService.setMap(this.mapglComponent.map); - } - - public getBoardEvents(event: { origin: string; event: string; data?: any; }) { - const resultListContributor = this.collaborativeService.registry.get(event.origin) as ResultListContributor; - const currentCollection = resultListContributor.collection; - const mapContributor: MapContributor = this.mapglContributors.filter(c => c.collection === currentCollection)[0]; - switch (event.event) { - case 'paginationEvent': - this.paginate(resultListContributor, event.data); - break; - case 'sortColumnEvent': - this.sortColumnEvent(event.origin, event.data); - break; - case 'consultedItemEvent': - if (!!mapContributor) { - const f = mapContributor.getFeatureToHightLight(event.data); - if (mapContributor) { - f.elementidentifier.idFieldName = f.elementidentifier.idFieldName.replace(/\./g, '_'); - } - this.featureToHightLight = f; - } - break; - case 'selectedItemsEvent': - /** TODO : manage features to select when we have multiple collections */ - if (event.data.length > 0 && this.mapComponentConfig && mapContributor) { - this.featuresToSelect = event.data.map(id => { - let idFieldName = this.collectionToDescription.get(currentCollection).id_path; - if (mapContributor.isFlat) { - idFieldName = idFieldName.replace(/\./g, '_'); - } - return { - idFieldName: idFieldName, - idValue: id - }; - }); - this.mapglComponent.selectFeaturesByCollection(this.featuresToSelect, currentCollection); - } else { - if (!!this.mapglComponent) { - this.mapglComponent.selectFeaturesByCollection([], currentCollection); - } - } - break; - case 'actionOnItemEvent': - this.actionOnItemEvent(event.data, mapContributor, resultListContributor, currentCollection); - break; - case 'globalActionEvent': - if (event.data.id === 'production') { - const idsItemSelected: ElementIdentifier[] = this.featuresToSelect; - this.process(idsItemSelected.map(i => i.idValue), currentCollection); - } else if (event.data.id === 'export_csv') { - this.resultlistIsExporting = true; - this.exportService.fetchResultlistData$(resultListContributor, undefined) - .pipe(finalize(() => this.resultlistIsExporting = false)) - .subscribe({ - next: (h) => this.exportService.exportResultlist(resultListContributor, h), - error: (e) => this.snackbar.open(marker('An error occured exporting the list')) - }); - } - break; - case 'geoSortEvent': - break; - case 'geoAutoSortEvent': - this.onActiveOnGeosort(event.data, resultListContributor, mapContributor, this.centerLatLng.lat, this.centerLatLng.lng); - break; - } - this.actionOnList.next(event); - } - - public onActiveOnGeosort(data, resultListContributor: ResultListContributor, mapContributor: MapContributor, lat, lng): void { - this.isGeoSortActivated.set(resultListContributor.identifier, data); - if (data) { - /** Apply geosort in list */ - resultListContributor.geoSort(lat, lng, true); - this.sortOutput.delete(resultListContributor.identifier); - // this.resultListComponent.columns.filter(c => !c.isIdField).forEach(c => c.sortDirection = SortEnum.none); - /** Apply geosort in map (for simple mode) */ - this.clearWindowData(mapContributor); - mapContributor.searchSort = resultListContributor.geoOrderSort; - mapContributor.searchSize = resultListContributor.pageSize; - mapContributor.drawGeoSearch(0, true); - } else { - const idFieldName = resultListContributor.getConfigValue('fieldsConfiguration')['idFieldName']; - this.sortOutput.set(resultListContributor.identifier, - { fieldName: idFieldName, sortDirection: SortEnum.none }); - /** Sort the list by the selected column and the id field name */ - resultListContributor.sortColumn({ fieldName: idFieldName, sortDirection: SortEnum.none }, true); - mapContributor.searchSort = resultListContributor.sort; - mapContributor.searchSize = resultListContributor.pageSize; - this.clearWindowData(mapContributor); - mapContributor.drawGeoSearch(0, true); - } - } - - public onChangeAoi(event) { - const configDebounceTime = this.configService.getValue('arlas.server.debounceCollaborationTime'); - const debounceDuration = configDebounceTime !== undefined ? configDebounceTime : 750; - for (let i = 0; i < this.mapglContributors.length; i++) { - setTimeout(() => { - this.snackbar.open(this.translate.instant('Loading data of ', - { collection: this.translate.instant(this.collectionService.getDisplayName(this.mapglContributors[i].collection)) })); - this.mapglContributors[i].onChangeAoi(event); - if (i === this.mapglContributors.length - 1) { - setTimeout(() => this.snackbar.dismiss(), 1000); - } - }, (i) * ((debounceDuration + 100) * 1.5)); - } - } - - public onAoiEdit(aoiEdit: AoiEdition) { - this.aoiEdition = aoiEdit; - } - - public onMove(event) { - // Update data only when the collections info are presents - if (this.collectionToDescription.size > 0) { - /** Change map extend in the url */ - const bounds = (this.mapglComponent.map).getBounds(); - const extend = bounds.getWest() + ',' + bounds.getSouth() + ',' + bounds.getEast() + ',' + bounds.getNorth(); - const queryParams = Object.assign({}, this.activatedRoute.snapshot.queryParams); - const visibileVisus = this.mapglComponent.visualisationSetsConfig.filter(v => v.enabled).map(v => v.name).join(';'); - queryParams[this.MAP_EXTEND_PARAM] = extend; - queryParams['vs'] = visibileVisus; - this.router.navigate([], { replaceUrl: true, queryParams: queryParams }); - localStorage.setItem('currentExtent', JSON.stringify(bounds)); - const ratioToAutoSort = 0.1; - this.centerLatLng['lat'] = event.centerWithOffset[1]; - this.centerLatLng['lng'] = event.centerWithOffset[0]; - if ((event.xMoveRatio > ratioToAutoSort || event.yMoveRatio > ratioToAutoSort || this.zoomChanged)) { - this.recalculateExtend = true; - } - const newMapExtent = event.extendWithOffset; - const newMapExtentRaw = event.rawExtendWithOffset; - const pwithin = newMapExtent[1] + ',' + newMapExtent[2] + ',' + newMapExtent[3] + ',' + newMapExtent[0]; - const pwithinRaw = newMapExtentRaw[1] + ',' + newMapExtentRaw[2] + ',' + newMapExtentRaw[3] + ',' + newMapExtentRaw[0]; - if (this.recalculateExtend && !this.disableRecalculateExtend) { - this.resultlistContributors - .forEach(c => { - const centroidPath = this.collectionToDescription.get(c.collection).centroid_path; - const mapContrib = this.mapglContributors.find(mc => mc.collection === c.collection); - if (!!mapContrib) { - c.filter = mapContrib.getFilterForCount(pwithinRaw, pwithin, centroidPath, true); - } else { - MapContributor.getFilterFromExtent(pwithinRaw, pwithin, centroidPath); - } - this.collaborativeService.registry.set(c.identifier, c); - }); - this.resultlistContributors.forEach(c => { - if (this.isGeoSortActivated.get(c.identifier)) { - c.geoSort(this.centerLatLng.lat, this.centerLatLng.lng, true); - } else { - c.sortColumn(this.sortOutput.get(c.identifier), true); - } - }); - this.mapglContributors.forEach(c => { - if (!!this.resultlistContributors) { - const resultlistContrbutor: ResultListContributor = this.resultlistContributors.find(v => v.collection === c.collection); - if (!!resultlistContrbutor) { - if (this.isGeoSortActivated.get(c.identifier)) { - c.searchSort = resultlistContrbutor.geoOrderSort; - } else { - c.searchSort = resultlistContrbutor.sort; - } - this.collaborativeService.registry.set(c.identifier, c); - } - } - this.clearWindowData(c); - }); - this.zoomChanged = false; - } - event.extendForTest = newMapExtent; - event.rawExtendForTest = newMapExtentRaw; - this.mapglContributors.forEach(contrib => contrib.onMove(event, this.recalculateExtend)); - this.recalculateExtend = false; - - } - } - - public changeVisualisation(event) { - this.mapglContributors.forEach(contrib => contrib.changeVisualisation(event)); - const queryParams = Object.assign({}, this.activatedRoute.snapshot.queryParams); - const visibileVisus = this.mapglComponent.visualisationSetsConfig.filter(v => v.enabled).map(v => v.name).join(';'); - queryParams['vs'] = visibileVisus; - this.router.navigate([], { replaceUrl: true, queryParams: queryParams }); - } - - - public emitFeaturesOnOver(event) { - if (event.features) { - this.mapglComponent.map.getCanvas().style.cursor = 'pointer'; - // Get feature by collection - this.resultlistContributors.forEach(c => { - const idFieldName = this.collectionToDescription.get(c.collection).id_path; - const highLightItems = event.features - .filter(f => f.layer.metadata.collection === c.collection) - .map(f => f.properties[idFieldName.replace(/\./g, '_')]) - .filter(id => id !== undefined) - .map(id => id.toString()); - c.setHighlightItems(highLightItems); - }); - } else { - this.mapglComponent.map.getCanvas().style.cursor = ''; - this.resultlistContributors.forEach(c => { - c.setHighlightItems([]); - }); - } - } - - public emitFeaturesOnClic(event) { - if (event.features) { - const feature = event.features[0]; - const resultListContributor = this.resultlistContributors - .filter(c => feature.layer.metadata.collection === c.collection && !feature.layer.id.includes(SCROLLABLE_ARLAS_ID))[0]; - if (!!resultListContributor) { - const idFieldName = this.collectionToDescription.get(resultListContributor.collection).id_path; - const id = feature.properties[idFieldName.replace(/\./g, '_')]; - // Open the list panel if it's closed - this.disableRecalculateExtend = true; - if (!this.resultlistService.listOpen) { - this.toggleList(); - } - // Select the good tab if we have several - // No tabs case - if (this.resultlistContributors.length === 1) { - this.waitFor(this.resultListComponent, () => this.openDetail(id)); - this.disableRecalculateExtend = false; - } else { - this.waitFor(this.resultListComponent, () => { - // retrieve list - const tab = document.querySelector('[aria-label="' + resultListContributor.identifier + '"]') as any; - tab.click(); - // Set Timeout to wait the new tab - setTimeout(() => this.openDetail(id), 250); - this.disableRecalculateExtend = false; - }); - } - } - } - } - - private openDetail(id: any) { - const isListMode = this.resultListComponent.resultMode === this.modeEnum.list; - if (isListMode) { - const detailListButton = document.getElementById('open-detail-' + id); - if (!!detailListButton) { - // close previous if exists - if (this.currentClickedFeatureId) { - const closeButtonElement = document.getElementById('close-detail-' + this.currentClickedFeatureId); - if (closeButtonElement) { - closeButtonElement.click(); - } - } - detailListButton.click(); - this.currentClickedFeatureId = id; - this.disableRecalculateExtend = false; - } - } else { - const productTile = document.getElementById('grid-tile-' + id); - const isDetailledGridOpen = this.resultListComponent?.isDetailledGridOpen; - if (!!productTile) { - productTile.click(); - if (!isDetailledGridOpen) { - setTimeout(() => { - const detailGridButton = document.getElementById('show_details_gridmode_btn'); - if (!!detailGridButton) { - detailGridButton.click(); - } - this.disableRecalculateExtend = false; - - }, 250); - } else { - // If image is displayed switch to detail data - const gridDivs = document.getElementsByClassName('resultgrid__img'); - if (gridDivs.length > 0) { - const imgDiv = gridDivs[0].parentElement; - if (window.getComputedStyle(imgDiv).display === 'block') { - setTimeout(() => { - const detailGridButton = document.getElementById('show_details_gridmode_btn'); - if (!!detailGridButton) { - detailGridButton.click(); - } - this.disableRecalculateExtend = false; - }, 1); - } - } - } - } - } + this.resultlistService.resultlistConfigPerContId.set(this.resultlistService.previewListContrib.identifier, config); + this.toggleList(); } public toggleList() { - this.tabsList.realignInkBar(); + this.arlasListComponent.tabsList.realignInkBar(); this.resultlistService.toggleList(); - this.updateTimelineLegendVisibility(); - this.adjustGrids(); - this.adjustComponentsSize(); - this.adjustVisibleShortcuts(); } public toggleTimeline() { - this.isTimelineOpen = !this.isTimelineOpen; + this.showTimeline = !this.showTimeline; const queryParams = Object.assign({}, this.activatedRoute.snapshot.queryParams); - queryParams['to'] = this.isTimelineOpen + ''; + queryParams['to'] = this.showTimeline + ''; this.router.navigate([], { replaceUrl: true, queryParams: queryParams }); } public updateTimelineLegendVisibility() { - this.isTimelineLegend = !(this.resultlistService.listOpen && this.analyticsService.activeTab !== undefined); + this.showTimelineLegend = !(this.resultlistService.listOpen && this.analyticsService.activeTab !== undefined); } public closeAnalytics() { this.analyticsService.selectTab(undefined); } - public closeMapMenu() { - setTimeout(() => { - if (this.shouldCloseMapMenu) { - this.isMapMenuOpen = false; - } - }, 100); - } - public onOpenShortcut(state: boolean, shortcutIdx: number) { if (state) { this.shortcutOpen = shortcutIdx; @@ -1251,27 +341,12 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { } public goToArlasHub() { - const hubUrl = this.arlasSettingsService.getArlasHubUrl(); + const hubUrl = this.settingsService.getArlasHubUrl(); if (!!hubUrl) { window.open(hubUrl); } } - public drawCircle() { - this.mapglComponent.switchToDrawMode('draw_radius_circle', { isFixedRadius: false, steps: 12 }); - } - - protected goToLocation(event: GeocodingResult) { - const bbox = this.visualizeService.getBbox(event.geojson); - this.visualizeService.handleGeojsonPreview(event.geojson); - if (event.geojson.type === 'Point') { - const zoom = this.settingsService.getGeocodingSettings().find_place_zoom_to; - this.mapglComponent.map.fitBounds(bbox, { maxZoom: zoom }); - } else { - this.mapglComponent.map.fitBounds(bbox); - } - } - /** * Compute the space available between the divider after the search and the title of the application */ @@ -1292,10 +367,11 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { private adjustGrids() { if (!this.resultlistService.listOpen) { - const config = this.resultListConfigPerContId.get(this.previewListContrib.identifier); + const config = this.resultlistService.resultlistConfigPerContId.get(this.resultlistService.previewListContrib.identifier); config.isDetailledGridOpen = false; } else { - this.resultlistService.selectedListTabIndex = this.rightListContributors.indexOf(this.previewListContrib); + this.resultlistService.selectedListTabIndex = + this.resultlistService.rightListContributors.indexOf(this.resultlistService.previewListContrib); } } @@ -1309,34 +385,19 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { */ private adjustComponentsSize() { setTimeout(() => { - if (this.timelineComponent.timelineHistogramComponent) { + if (!!this.timelineComponent && !!this.timelineComponent.timelineHistogramComponent) { this.timelineComponent.timelineHistogramComponent.resizeHistogram(); if (!!this.timelineComponent.detailedTimelineHistogramComponent) { this.timelineComponent.detailedTimelineHistogramComponent.resizeHistogram(); } } - this.mapglComponent.map?.resize(); - this.adjustCoordinates(); + this.mapService.mapComponent?.map?.resize(); + this.mapService.adjustCoordinates(); - this.updateVisibleItems(); + this.resultlistService.updateVisibleItems(); }, 100); } - private adjustCoordinates(): void { - const timelineToolsMaxWidth = 420; - const scaleMaxWidth = 100; - const toggleButtonWidth = 24; - const smMargin = 5; - const mapCanvas = document.getElementsByClassName('mapboxgl-canvas'); - if (mapCanvas && mapCanvas.length > 0) { - const bbox = mapCanvas[0].getBoundingClientRect(); - if (bbox) { - const width = bbox.width; - this.coordinatesHaveSpace = (width - timelineToolsMaxWidth - scaleMaxWidth - toggleButtonWidth - 3 * smMargin) > 230; - } - } - } - /** * Transfer shortcuts from the visible ones to the extra ones based on the space available in the window */ @@ -1354,8 +415,8 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { // The threshold is based on the window inner size and the available size for the shortcuts // The shortcuts are spaced on the left from the menu, and must not overflow on the legend on the right, // with a minimum spacing equal to the one on the left. - const previewListOpen = !!this.previewListContrib && !this.resultlistService.listOpen - && this.resultListConfigPerContId.get(this.previewListContrib.identifier)?.hasGridMode; + const previewListOpen = !!this.resultlistService.previewListContrib && !this.resultlistService.listOpen + && this.resultlistService.resultlistConfigPerContId.get(this.resultlistService.previewListContrib.identifier)?.hasGridMode; const mapActionsAndLegendWidth = 270; const leftMenuWidth = 48; this.showMoreShortcutsWidth = document.getElementById('extra-shortcuts-title').getBoundingClientRect().width; @@ -1388,141 +449,4 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { this.showShortcuts = false; } - - private adjustMapOffset() { - this.recalculateExtend = true; - this.mapglComponent.map.fitBounds(this.mapglComponent.map.getBounds()); - } - - private getVisibleElementLayerFilter(l, ids) { - const lFilter = this.mapglComponent.layersMap.get(l).filter; - const filters = []; - if (lFilter) { - lFilter.forEach(f => { - filters.push(f); - }); - } - if (filters.length === 0) { - filters.push('all'); - } - filters.push([ - 'match', - ['get', 'id'], - Array.from(new Set(ids)), - true, - false - ]); - return filters; - } - - private clearWindowData(contributor: MapContributor) { - contributor.getConfigValue('layers_sources') - .filter(ls => ls.source.startsWith('feature-') && ls.render_mode === FeatureRenderMode.window) - .map(ls => ls.source) - .forEach(source => contributor.clearData(source)); - } - - private getParamValue(param: string): string { - let paramValue = null; - const url = window.location.href; - const regex = new RegExp('[?&]' + param + '(=([^&#]*)|&|#|$)'); - const results = regex.exec(url); - if (results && results[2]) { - paramValue = results[2]; - } - return paramValue; - } - - private actionOnItemEvent(data, mapContributor: MapContributor, listContributor: ResultListContributor, collection: string) { - switch (data.action.id) { - case 'zoomToFeature': - if (!!mapContributor) { - mapContributor - .getBoundsToFit(data.elementidentifier, collection) - .subscribe(bounds => { - this.visualizeService.fitbounds = bounds; - }); - } - break; - case 'visualize': - if (!!this.resultListConfigPerContId.get(listContributor.identifier)) { - const urlVisualisationTemplate = this.resultListConfigPerContId.get(listContributor.identifier).visualisationLink; - if (!data.action.activated) { - this.visualizeService.getVisuInfo(data.elementidentifier, collection, urlVisualisationTemplate).subscribe(url => { - this.visualizeService.displayDataOnMap(url, - data.elementidentifier, this.collectionToDescription.get(collection).geometry_path, - this.collectionToDescription.get(collection).centroid_path, collection); - }); - this.resultlistService.addAction(listContributor.identifier, data.elementidentifier.idValue, data.action); - } else { - this.visualizeService.removeRasters(data.elementidentifier.idValue); - this.resultlistService.removeAction(listContributor.identifier, data.elementidentifier.idValue, data.action.id); - } - } - break; - case 'download': - if (!!this.resultListConfigPerContId.get(listContributor.identifier)) { - const urlDownloadTemplate = this.resultListConfigPerContId.get(listContributor.identifier).downloadLink; - if (urlDownloadTemplate) { - this.visualizeService.getVisuInfo(data.elementidentifier, collection, urlDownloadTemplate).subscribe(url => { - const win = window.open(url, '_blank'); - win.focus(); - }); - } - } - break; - case 'production': - this.process([data.elementidentifier.idValue], collection); - break; - } - } - - private process(ids: string[], collection: string) { - const maxItems = this.arlasSettingsService.getProcessSettings().max_items; - if (ids.length <= maxItems) { - this.processService.load().subscribe({ - next: () => { - this.processService.getItemsDetail( - this.collectionToDescription.get(collection).id_path, - ids, - collection - ).subscribe({ - next: (item: any) => { - this.downloadDialogRef = this.dialog - .open(AiasDownloadComponent, { - minWidth: '520px', - maxWidth: '60vw', - data: { - ids, - collection, - nbProducts: ids.length, - itemDetail: item, - wktAoi: this.mapglComponent.getAllPolygon('wkt') - } - }); - } - }); - } - }); - } else { - this.snackbar.open( - this.translate.instant('You have exceeded the number of products authorised for a single download') + ' (' + maxItems + ')', 'X', - { - horizontalPosition: 'center', - verticalPosition: 'top', - duration: 5000 - } - ); - } - } - - private waitFor(variable, callback) { - const interval = setInterval(() => { - if (variable !== undefined) { - clearInterval(interval); - callback(); - } - }, 100); - } } - diff --git a/src/app/components/configs-list/configs-list.component.html b/src/app/components/configs-list/configs-list.component.html index aff1889e..525ed5f7 100644 --- a/src/app/components/configs-list/configs-list.component.html +++ b/src/app/components/configs-list/configs-list.component.html @@ -1,6 +1,3 @@ -
- -

{{'Choose a dashboard to load' | translate}}

@@ -12,13 +9,18 @@

{{'Choose a dashboard to load' | translate}}

- {{'Loading dashboards...' | translate}} - {{'Error while retrieving dashboards' | - translate}} + + {{'Loading dashboards...' | translate}} + + + {{'Error while retrieving dashboards' | translate}} + {{conf.name}} - No dashboard in this organisation + + {{ 'No dashboard in this organisation' | translate }} +
diff --git a/src/app/components/configs-list/configs-list.component.scss b/src/app/components/configs-list/configs-list.component.scss index 602c6661..676d8982 100644 --- a/src/app/components/configs-list/configs-list.component.scss +++ b/src/app/components/configs-list/configs-list.component.scss @@ -17,14 +17,6 @@ * under the License. */ -.configs-list_all_app { - position: absolute; - height: 100%; - width: 100%; - background-color: rgba(0, 0, 0, 0.5); - z-index: 1001; -} - .configs-list_wrapper { max-width: 400px; min-width: 130px; @@ -100,4 +92,4 @@ } } -} \ No newline at end of file +} diff --git a/src/app/components/configs-list/configs-list.component.ts b/src/app/components/configs-list/configs-list.component.ts index d0272f1b..d72910e0 100644 --- a/src/app/components/configs-list/configs-list.component.ts +++ b/src/app/components/configs-list/configs-list.component.ts @@ -20,12 +20,9 @@ import { Component, OnInit, Output } from '@angular/core'; import { UserOrgData } from 'arlas-iam-api'; import { DataResource, DataWithLinks } from 'arlas-persistence-api'; -import { - ArlasIamService, ArlasSettingsService, ArlasStartupService, - AuthentificationService, PersistenceService -} from 'arlas-wui-toolkit'; -import { Subject } from 'rxjs'; import { ArlasColorService } from 'arlas-web-components'; +import { ArlasIamService, ArlasSettingsService, ArlasStartupService, AuthentificationService, PersistenceService } from 'arlas-wui-toolkit'; +import { Subject } from 'rxjs'; export const ZONE_WUI_BUILDER = 'config.json'; @@ -42,15 +39,19 @@ export interface Configuration { }) export class ConfigsListComponent implements OnInit { public configurations: Array = new Array(); - public hubUrl; + public hubUrl: string; public listResolved = false; public retrieveData = true; - public isAuthentActivated; + public isAuthentActivated: boolean; public authentMode = 'false'; public orgs: UserOrgData[] = []; public currentOrg: string; + /** + * @Output : Angular + * Emits an event when the hub needs to be opened + */ @Output() public openHubEventEmitter: Subject = new Subject(); public constructor( @@ -73,7 +74,6 @@ export class ConfigsListComponent implements OnInit { } public ngOnInit() { - if (this.authentMode === 'iam') { this.arlasIamService.tokenRefreshed$.subscribe({ next: (userSubject) => { @@ -105,7 +105,7 @@ export class ConfigsListComponent implements OnInit { this.openHubEventEmitter.next(true); } - public switchConf(confId) { + public switchConf(confId: string) { let url = '?config_id=' + confId; const currentOrg = this.arlasIamService.getOrganisation(); if (!!currentOrg) { diff --git a/src/app/components/geocoding/geocoding.component.ts b/src/app/components/geocoding/geocoding.component.ts index 4ba7bec8..6c56e9c3 100644 --- a/src/app/components/geocoding/geocoding.component.ts +++ b/src/app/components/geocoding/geocoding.component.ts @@ -29,7 +29,7 @@ import { import { FormControl } from '@angular/forms'; import { MatTableDataSource } from '@angular/material/table'; import { TranslateService } from '@ngx-translate/core'; -import { GeocodingQueryParams, GeocodingResult, GeocodingService } from '../../services/geocoding.service'; +import { GeocodingQueryParams, GeocodingResult, GeocodingService } from '@services/geocoding.service'; @Component({ selector: 'arlas-geocoding', @@ -37,8 +37,16 @@ import { GeocodingQueryParams, GeocodingResult, GeocodingService } from '../../s styleUrls: ['./geocoding.component.scss'] }) export class GeocodingComponent implements AfterViewInit { - @Output() private close = new EventEmitter(); - @Output() private zoomToAddress = new EventEmitter(); + /** + * @Output : Angular + * Emits an event when the geocoding popup needs to be closed + */ + @Output() private close = new EventEmitter(); + /** + * @Output : Angular + * Emits an event when the map needs to zoom on the given location + */ + @Output() private zoomToAddress = new EventEmitter(); @ViewChild('searchInput') private searchInput: ElementRef; protected displayedColumns: string[] = ['address']; diff --git a/src/app/components/left-menu/left-menu.component.html b/src/app/components/left-menu/left-menu.component.html index b90e1f16..84ef8416 100644 --- a/src/app/components/left-menu/left-menu.component.html +++ b/src/app/components/left-menu/left-menu.component.html @@ -1,5 +1,5 @@ -