diff --git a/.vscode/moodle.code-snippets b/.vscode/moodle.code-snippets index 17126a06d..232f9578b 100644 --- a/.vscode/moodle.code-snippets +++ b/.vscode/moodle.code-snippets @@ -3,10 +3,12 @@ "scope": "typescript", "prefix": "massingleton", "body": [ - "import { Injectable } from '@angular/core';", "import { makeSingleton } from '@singletons';", "", + "/**", + " * $2.", + " */", "@Injectable({ providedIn: 'root' })", "export class ${1:${TM_FILENAME_BASE}}Service {", "", @@ -18,5 +20,21 @@ "" ], "description": "[Moodle] Create a Service Singleton" + }, + "[Moodle] Singleton": { + "scope": "typescript", + "prefix": "masingleton", + "body": [ + "/**", + " * Singleton$2.", + " */", + "export class ${1:${TM_FILENAME_BASE}} {", + "", + " $0", + "", + "}", + "" + ], + "description": "[Moodle] Create a Pure Singleton" } } diff --git a/package-lock.json b/package-lock.json index 44be797ac..ca7772a0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2438,6 +2438,328 @@ } } }, + "@ionic/cli": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/@ionic/cli/-/cli-6.12.3.tgz", + "integrity": "sha512-o4gm4ZoKrlzL8Gs1XZR+kVaMYghvKX8t/XJrQKr4EN/DQAM03KNMxsfm+RV3wFTnM8dgFywHzCuiw+5hY1oxEg==", + "dev": true, + "requires": { + "@ionic/cli-framework": "5.1.0", + "@ionic/cli-framework-output": "2.2.2", + "@ionic/cli-framework-prompts": "2.1.8", + "@ionic/utils-array": "2.1.5", + "@ionic/utils-fs": "3.1.5", + "@ionic/utils-network": "2.1.5", + "@ionic/utils-process": "2.1.8", + "@ionic/utils-stream": "3.1.5", + "@ionic/utils-subprocess": "2.1.8", + "@ionic/utils-terminal": "2.3.1", + "chalk": "^4.0.0", + "debug": "^4.0.0", + "diff": "^4.0.1", + "elementtree": "^0.1.7", + "leek": "0.0.24", + "lodash": "^4.17.5", + "open": "^7.0.4", + "os-name": "^4.0.0", + "semver": "^7.1.1", + "split2": "^3.0.0", + "ssh-config": "^1.1.1", + "stream-combiner2": "^1.1.1", + "superagent": "^5.2.1", + "superagent-proxy": "^2.0.0", + "tar": "^6.0.1", + "tslib": "^2.0.1" + }, + "dependencies": { + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "os-name": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-4.0.0.tgz", + "integrity": "sha512-caABzDdJMbtykt7GmSogEat3faTKQhmZf0BS5l/pZGmP0vPWQjXWqOhbLyK+b6j2/DQPmEvYdzLXJXXLJNVDNg==", + "dev": true, + "requires": { + "macos-release": "^2.2.0", + "windows-release": "^4.0.0" + } + }, + "windows-release": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-4.0.0.tgz", + "integrity": "sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg==", + "dev": true, + "requires": { + "execa": "^4.0.2" + } + } + } + }, + "@ionic/cli-framework": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@ionic/cli-framework/-/cli-framework-5.1.0.tgz", + "integrity": "sha512-Hb/P2zuHB3zQZN0qG7Lxda8IlP2mHisfb0KR+wc9cw2BSiH+rtXRd/A4JxndPznjWs00PHbWiEm0Ehas2pA/nw==", + "dev": true, + "requires": { + "@ionic/cli-framework-output": "2.2.2", + "@ionic/utils-array": "2.1.5", + "@ionic/utils-fs": "3.1.5", + "@ionic/utils-object": "2.1.5", + "@ionic/utils-process": "2.1.8", + "@ionic/utils-stream": "3.1.5", + "@ionic/utils-subprocess": "2.1.8", + "@ionic/utils-terminal": "2.3.1", + "chalk": "^4.0.0", + "debug": "^4.0.0", + "lodash": "^4.17.5", + "minimist": "^1.2.0", + "rimraf": "^3.0.0", + "tslib": "^2.0.1", + "write-file-atomic": "^3.0.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@ionic/cli-framework-output": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@ionic/cli-framework-output/-/cli-framework-output-2.2.2.tgz", + "integrity": "sha512-eQYkqIW1/tCwSC6Bd0gjse96U11lDX/ikf3jvsjX7a8z/zwSmGzCHRizb7xogV65Ey+1/zyAZR71cpDRQuFLBQ==", + "dev": true, + "requires": { + "@ionic/utils-terminal": "2.3.1", + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@ionic/cli-framework-prompts": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@ionic/cli-framework-prompts/-/cli-framework-prompts-2.1.8.tgz", + "integrity": "sha512-DjO4lQsmvficsZbPmpGqSSx+F1BfgSTQBwRqL5bl9Dkh9rIZ/ckcJcKqCciVOw9kIY7WTeNFOTwj2vWrkFn7+Q==", + "dev": true, + "requires": { + "@ionic/utils-terminal": "2.3.1", + "debug": "^4.0.0", + "inquirer": "^7.0.0", + "tslib": "^2.0.1" + }, + "dependencies": { + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "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 + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "rxjs": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", + "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } + } + }, "@ionic/core": { "version": "5.5.2", "resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.5.2.tgz", @@ -2454,6 +2776,305 @@ } } }, + "@ionic/utils-array": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@ionic/utils-array/-/utils-array-2.1.5.tgz", + "integrity": "sha512-HD72a71IQVBmQckDwmA8RxNVMTbxnaLbgFOl+dO5tbvW9CkkSFCv41h6fUuNsSEVgngfkn0i98HDuZC8mk+lTA==", + "dev": true, + "requires": { + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@ionic/utils-fs": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@ionic/utils-fs/-/utils-fs-3.1.5.tgz", + "integrity": "sha512-a41bY2dHqWSEQQ/80CpbXSs8McyiCFf2DnIWWLukrhYWf46h4qi6M/8dxcMKrofRiqI/3F+cL3S2mOm9Zz/o2Q==", + "dev": true, + "requires": { + "debug": "^4.0.0", + "fs-extra": "^9.0.0", + "tslib": "^2.0.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@ionic/utils-network": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@ionic/utils-network/-/utils-network-2.1.5.tgz", + "integrity": "sha512-HUQ1Ec4Mh2MXzzKdbbbDS6xYKwpFJ2XRY7SYXbaZT8+jiNahfHbsOfe62/p8bk41Yil7E9EagzGC2JvIFJh01w==", + "dev": true, + "requires": { + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@ionic/utils-object": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@ionic/utils-object/-/utils-object-2.1.5.tgz", + "integrity": "sha512-XnYNSwfewUqxq+yjER1hxTKggftpNjFLJH0s37jcrNDwbzmbpFTQTVAp4ikNK4rd9DOebX/jbeZb8jfD86IYxw==", + "dev": true, + "requires": { + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@ionic/utils-process": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@ionic/utils-process/-/utils-process-2.1.8.tgz", + "integrity": "sha512-VBBoyTzi+m6tgKAItl+jiTQneGwTOsctcrTG4CsEgmVOVOEhUYkPhddXqzD+oC54hPDU9ROsd3I014P5CWEuhQ==", + "dev": true, + "requires": { + "@ionic/utils-object": "2.1.5", + "@ionic/utils-terminal": "2.3.1", + "debug": "^4.0.0", + "signal-exit": "^3.0.3", + "tree-kill": "^1.2.2", + "tslib": "^2.0.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@ionic/utils-stream": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@ionic/utils-stream/-/utils-stream-3.1.5.tgz", + "integrity": "sha512-hkm46uHvEC05X/8PHgdJi4l4zv9VQDELZTM+Kz69odtO9zZYfnt8DkfXHJqJ+PxmtiE5mk/ehJWLnn/XAczTUw==", + "dev": true, + "requires": { + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@ionic/utils-subprocess": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@ionic/utils-subprocess/-/utils-subprocess-2.1.8.tgz", + "integrity": "sha512-pkmtf1LtXcEMPn6/cctREL2aZtZoy0+0Sl+nT0NIkOHIoBUcqrcfMWdctCSM4Mp6+2/hLWtgpHE3TOIibkWfIg==", + "dev": true, + "requires": { + "@ionic/utils-array": "2.1.5", + "@ionic/utils-fs": "3.1.5", + "@ionic/utils-process": "2.1.8", + "@ionic/utils-stream": "3.1.5", + "@ionic/utils-terminal": "2.3.1", + "cross-spawn": "^7.0.0", + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@ionic/utils-terminal": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@ionic/utils-terminal/-/utils-terminal-2.3.1.tgz", + "integrity": "sha512-cglsSd2AckI3Ldtdfczeq64vIIDjtPspV5QJtky8f8uIdxkeOIGeRV7bCj1+BEf1hyo+ZuggQxLviHnbMZhiRw==", + "dev": true, + "requires": { + "debug": "^4.0.0", + "signal-exit": "^3.0.3", + "slice-ansi": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "tslib": "^2.0.1", + "untildify": "^4.0.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "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 + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "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, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, "@ionic/v4-migration-tslint": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/@ionic/v4-migration-tslint/-/v4-migration-tslint-1.7.1.tgz", @@ -3099,6 +3720,12 @@ "defer-to-connect": "^1.0.1" } }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true + }, "@types/babel__core": { "version": "7.1.10", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.10.tgz", @@ -4307,6 +4934,15 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, + "ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "requires": { + "tslib": "^2.0.1" + } + }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", @@ -6036,6 +6672,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true + }, "copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", @@ -7164,6 +7806,12 @@ "assert-plus": "^1.0.0" } }, + "data-uri-to-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", + "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==", + "dev": true + }, "data-urls": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", @@ -7452,6 +8100,17 @@ } } }, + "degenerator": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-2.2.0.tgz", + "integrity": "sha512-aiQcQowF01RxFI4ZLFMpzyotbQonhNpBao6dkI8JPk5a+hmSjR5ErHp2CQySmQe8os3VBqLCIh87nDBgZXvsmg==", + "dev": true, + "requires": { + "ast-types": "^0.13.2", + "escodegen": "^1.8.1", + "esprima": "^4.0.0" + } + }, "del": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", @@ -7751,6 +8410,15 @@ "is-obj": "^2.0.0" } }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -8850,6 +9518,12 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", + "dev": true + }, "fastparse": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", @@ -9233,6 +9907,12 @@ "mime-types": "^2.1.12" } }, + "formidable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", + "dev": true + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -9322,6 +10002,42 @@ "dev": true, "optional": true }, + "ftp": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", + "integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=", + "dev": true, + "requires": { + "readable-stream": "1.1.x", + "xregexp": "2.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -9425,6 +10141,69 @@ "pump": "^3.0.0" } }, + "get-uri": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-3.0.2.tgz", + "integrity": "sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg==", + "dev": true, + "requires": { + "@tootallnate/once": "1", + "data-uri-to-buffer": "3", + "debug": "4", + "file-uri-to-path": "2", + "fs-extra": "^8.1.0", + "ftp": "^0.3.10" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "file-uri-to-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz", + "integrity": "sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==", + "dev": true + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + } + } + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -12970,6 +13749,25 @@ "flush-write-stream": "^1.0.2" } }, + "leek": { + "version": "0.0.24", + "resolved": "https://registry.npmjs.org/leek/-/leek-0.0.24.tgz", + "integrity": "sha1-5ADlfw5g2O8r1NBo3EKKVDRdvNo=", + "dev": true, + "requires": { + "debug": "^2.1.0", + "lodash.assign": "^3.2.0", + "rsvp": "^3.0.21" + }, + "dependencies": { + "rsvp": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", + "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==", + "dev": true + } + } + }, "less": { "version": "3.12.2", "resolved": "https://registry.npmjs.org/less/-/less-3.12.2.tgz", @@ -13162,6 +13960,62 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", + "dev": true + }, + "lodash._createassigner": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", + "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", + "dev": true, + "requires": { + "lodash._bindcallback": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash.restparam": "^3.0.0" + } + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.assign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", + "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=", + "dev": true, + "requires": { + "lodash._baseassign": "^3.0.0", + "lodash._createassigner": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -13173,12 +14027,41 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", "dev": true }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "dev": true + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -14048,6 +14931,12 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "netmask": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz", + "integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=", + "dev": true + }, "next-tick": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", @@ -14910,6 +15799,92 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, + "pac-proxy-agent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-4.1.0.tgz", + "integrity": "sha512-ejNgYm2HTXSIYX9eFlkvqFp8hyJ374uDf0Zq5YUAifiSh1D6fo+iBivQZirGvVv8dCYUsLhmLBRhlAYvBKI5+Q==", + "dev": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4", + "get-uri": "3", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "5", + "pac-resolver": "^4.1.0", + "raw-body": "^2.2.0", + "socks-proxy-agent": "5" + }, + "dependencies": { + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "socks-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz", + "integrity": "sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4", + "socks": "^2.3.3" + } + } + } + }, + "pac-resolver": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-4.1.0.tgz", + "integrity": "sha512-d6lf2IrZJJ7ooVHr7BfwSjRO1yKSJMaiiWYSHcrxSIUtZrCa4KKGwcztdkZ/E9LFleJfjoi1yl+XLR7AX24nbQ==", + "dev": true, + "requires": { + "degenerator": "^2.2.0", + "ip": "^1.1.5", + "netmask": "^1.0.6" + } + }, "package-json": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", @@ -16418,6 +17393,101 @@ "ipaddr.js": "1.9.1" } }, + "proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-4.0.1.tgz", + "integrity": "sha512-ODnQnW2jc/FUVwHHuaZEfN5otg/fMbvMxz9nMSUQfJ9JU7q2SZvSULSsjLloVgJOiv9yhc8GlNMKc4GkFmcVEA==", + "dev": true, + "requires": { + "agent-base": "^6.0.0", + "debug": "4", + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "lru-cache": "^5.1.1", + "pac-proxy-agent": "^4.1.0", + "proxy-from-env": "^1.0.0", + "socks-proxy-agent": "^5.0.0" + }, + "dependencies": { + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "socks-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz", + "integrity": "sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4", + "socks": "^2.3.3" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -18575,12 +19645,40 @@ "extend-shallow": "^3.0.0" } }, + "split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dev": true, + "requires": { + "readable-stream": "^3.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "ssh-config": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/ssh-config/-/ssh-config-1.1.6.tgz", + "integrity": "sha512-ZPO9rECxzs5JIQ6G/2EfL1I9ho/BVZkx9HRKn8+0af7QgwAmumQ7XBFP1ggMyPMo+/tUbmv0HFdv4qifdO/9JA==", + "dev": true + }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -18682,6 +19780,16 @@ "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", "integrity": "sha1-kdX1Ew0c75bc+n9yaUUYh0HQnuQ=" }, + "stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", + "dev": true, + "requires": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, "stream-each": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", @@ -18990,6 +20098,103 @@ } } }, + "superagent": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-5.3.1.tgz", + "integrity": "sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ==", + "dev": true, + "requires": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.2", + "debug": "^4.1.1", + "fast-safe-stringify": "^2.0.7", + "form-data": "^3.0.0", + "formidable": "^1.2.2", + "methods": "^1.1.2", + "mime": "^2.4.6", + "qs": "^6.9.4", + "readable-stream": "^3.6.0", + "semver": "^7.3.2" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "form-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "mime": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.7.tgz", + "integrity": "sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "qs": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "superagent-proxy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/superagent-proxy/-/superagent-proxy-2.1.0.tgz", + "integrity": "sha512-DnarpKN6Xn8e3pYlFV4Yvsj9yxLY4q5FIsUe5JvN7vjzP+YCfzXv03dTkZSD2yzrSadsNYHf0IgOUJwKjX457A==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "proxy-agent": "^4.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -20033,6 +21238,12 @@ } } }, + "untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true + }, "upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", @@ -21732,6 +22943,12 @@ "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz", "integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==" }, + "xregexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", + "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=", + "dev": true + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 0309ed784..2fac4efb2 100644 --- a/package.json +++ b/package.json @@ -19,17 +19,17 @@ ], "scripts": { "ng": "ng", - "start": "ng serve", - "build": "ng build", - "build:prod": "ng build --prod", + "start": "ionic serve", + "build": "ionic build", + "build:prod": "ionic build --prod", "test": "jest --verbose", "test:ci": "jest -ci --runInBand --verbose", "test:watch": "jest --watch", "test:coverage": "jest --coverage", "lint": "ng lint", - "ionic:serve:before": "npx gulp", - "ionic:serve": "npx gulp watch & ng serve", - "ionic:build:before": "npx gulp" + "ionic:serve:before": "gulp", + "ionic:serve": "gulp watch & ng serve", + "ionic:build:before": "gulp" }, "dependencies": { "@angular/animations": "^11.0.1", @@ -130,6 +130,7 @@ "@angular/compiler-cli": "~10.0.0", "@angular/language-service": "~10.0.0", "@ionic/angular-toolkit": "^2.3.0", + "@ionic/cli": "^6.12.3", "@ionic/v4-migration-tslint": "^1.7.1", "@types/faker": "^5.1.3", "@types/node": "^12.12.64", diff --git a/src/addons/badges/pages/user-badges/user-badges.page.ts b/src/addons/badges/pages/user-badges/user-badges.page.ts index d1ea67af6..23b54a499 100644 --- a/src/addons/badges/pages/user-badges/user-badges.page.ts +++ b/src/addons/badges/pages/user-badges/user-badges.page.ts @@ -19,7 +19,7 @@ import { CoreTimeUtils } from '@services/utils/time'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreSites } from '@services/sites'; import { CoreUtils } from '@services/utils/utils'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; import { ActivatedRoute } from '@angular/router'; // @todo import { CoreSplitViewComponent } from '@components/split-view/split-view'; @@ -107,7 +107,7 @@ export class AddonBadgesUserBadgesPage implements OnInit { const params = { courseId: this.courseId, userId: this.userId, badgeHash: badgeHash }; // @todo use splitview. // this.splitviewCtrl.push('AddonBadgesIssuedBadgePage', params); - CoreNavHelper.instance.goInSite('/badges/issue', params); + CoreNavigator.instance.navigateToSitePath('/badges/issue', { params }); } } diff --git a/src/addons/badges/services/handlers/badge-link.ts b/src/addons/badges/services/handlers/badge-link.ts index 13d54b376..7b09881ef 100644 --- a/src/addons/badges/services/handlers/badge-link.ts +++ b/src/addons/badges/services/handlers/badge-link.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { Params } from '@angular/router'; import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; import { makeSingleton } from '@singletons'; import { AddonBadges } from '../badges'; @@ -43,10 +43,12 @@ export class AddonBadgesBadgeLinkHandlerService extends CoreContentLinksHandlerB return [{ action: (siteId: string): void => { - CoreNavHelper.instance.goInSite( + CoreNavigator.instance.navigateToSitePath( '/badges/issue', - { courseId: 0, badgeHash: params.hash }, - siteId, + { + siteId, + params: { courseId: 0, badgeHash: params.hash }, + }, ); }, }]; diff --git a/src/addons/badges/services/handlers/mybadges-link.ts b/src/addons/badges/services/handlers/mybadges-link.ts index 9e0c873c9..ee7b19db5 100644 --- a/src/addons/badges/services/handlers/mybadges-link.ts +++ b/src/addons/badges/services/handlers/mybadges-link.ts @@ -15,7 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; import { makeSingleton } from '@singletons'; import { AddonBadges } from '../badges'; @@ -37,7 +37,7 @@ export class AddonBadgesMyBadgesLinkHandlerService extends CoreContentLinksHandl getActions(): CoreContentLinksAction[] { return [{ action: (siteId: string): void => { - CoreNavHelper.instance.goInSite('/badges/user', {}, siteId); + CoreNavigator.instance.navigateToSitePath('/badges/user', { siteId }); }, }]; } diff --git a/src/addons/badges/services/handlers/push-click.ts b/src/addons/badges/services/handlers/push-click.ts index e31f48531..e3a834f3c 100644 --- a/src/addons/badges/services/handlers/push-click.ts +++ b/src/addons/badges/services/handlers/push-click.ts @@ -19,7 +19,7 @@ import { CorePushNotificationsClickHandler } from '@features/pushnotifications/s import { AddonBadges } from '../badges'; import { makeSingleton } from '@singletons'; import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; /** * Handler for badges push notifications clicks. @@ -59,7 +59,12 @@ export class AddonBadgesPushClickHandlerService implements CorePushNotifications if (data.hash) { // We have the hash, open the badge directly. - return CoreNavHelper.instance.goInSite('/badges/issue', { courseId: 0, badgeHash: data.hash }, notification.site); + await CoreNavigator.instance.navigateToSitePath('/badges/issue', { + siteId: notification.site, + params: { courseId: 0, badgeHash: data.hash }, + }); + + return; } // No hash, open the list of user badges. @@ -71,7 +76,7 @@ export class AddonBadgesPushClickHandlerService implements CorePushNotifications ), ); - await CoreNavHelper.instance.goInSite('/badges/user', {}, notification.site); + await CoreNavigator.instance.navigateToSitePath('/badges/user', { siteId: notification.site }); } } diff --git a/src/addons/badges/services/handlers/user.ts b/src/addons/badges/services/handlers/user.ts index aad3c5ac2..0aed8ef9c 100644 --- a/src/addons/badges/services/handlers/user.ts +++ b/src/addons/badges/services/handlers/user.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses'; import { CoreUserProfile } from '@features/user/services/user'; import { CoreUserDelegateService, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@features/user/services/user-delegate'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; import { makeSingleton } from '@singletons'; import { AddonBadges } from '../badges'; @@ -72,7 +72,10 @@ export class AddonBadgesUserHandlerService implements CoreUserProfileHandler { action: (event, user, courseId): void => { event.preventDefault(); event.stopPropagation(); - CoreNavHelper.instance.goInSite('/badges/user', { courseId: courseId || 0, userId: user.id }); + CoreNavigator.instance.navigateToSitePath( + '/badges/user', + { params: { courseId: courseId || 0, userId: user.id } }, + ); }, }; } diff --git a/src/core/components/tabs/tabs.ts b/src/core/components/tabs/tabs.ts index 8746fb366..760e93b54 100644 --- a/src/core/components/tabs/tabs.ts +++ b/src/core/components/tabs/tabs.ts @@ -588,7 +588,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe } const ok = await this.navCtrl.navigateForward(selectedTab.page, pageParams); - if (ok) { + if (ok !== false) { this.selectHistory.push(tabId); this.selected = tabId; this.selectedIndex = index; diff --git a/src/core/components/user-avatar/user-avatar.ts b/src/core/components/user-avatar/user-avatar.ts index dbbd25b31..2e8348a76 100644 --- a/src/core/components/user-avatar/user-avatar.ts +++ b/src/core/components/user-avatar/user-avatar.ts @@ -145,7 +145,7 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy { // @todo Decide which navCtrl to use. If this component is inside a split view, use the split view's master nav. this.navCtrl.navigateForward(['user'], { relativeTo: this.route, - queryParams: CoreObject.removeUndefined({ + queryParams: CoreObject.withoutEmpty({ userId: this.userId, courseId: this.courseId, }), diff --git a/src/core/directives/user-link.ts b/src/core/directives/user-link.ts index 3099ae77e..b8b2cf2c1 100644 --- a/src/core/directives/user-link.ts +++ b/src/core/directives/user-link.ts @@ -15,7 +15,7 @@ import { Directive, Input, OnInit, ElementRef } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { NavController } from '@ionic/angular'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; import { CoreObject } from '@singletons/object'; @@ -54,10 +54,12 @@ export class CoreUserLinkDirective implements OnInit { event.stopPropagation(); // @todo If this directive is inside a split view, use the split view's master nav. - CoreNavHelper.instance.goInCurrentMainMenuTab('user', CoreObject.removeUndefined({ - userId: this.userId, - courseId: this.courseId, - })); + CoreNavigator.instance.navigateToSitePath('user', { + params: CoreObject.withoutEmpty({ + userId: this.userId, + courseId: this.courseId, + }), + }); }); } diff --git a/src/core/features/block/components/only-title-block/only-title-block.ts b/src/core/features/block/components/only-title-block/only-title-block.ts index 3e2ec6d20..35f7b7d73 100644 --- a/src/core/features/block/components/only-title-block/only-title-block.ts +++ b/src/core/features/block/components/only-title-block/only-title-block.ts @@ -14,7 +14,7 @@ import { OnInit, Component } from '@angular/core'; import { CoreBlockBaseComponent } from '../../classes/base-block-component'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; /** * Component to render blocks with only a title and link. @@ -24,7 +24,7 @@ import { CoreNavHelper } from '@services/nav-helper'; templateUrl: 'core-block-only-title.html', styleUrls: ['only-title-block.scss'], }) -export class CoreBlockOnlyTitleComponent extends CoreBlockBaseComponent implements OnInit { +export class CoreBlockOnlyTitleComponent extends CoreBlockBaseComponent implements OnInit { constructor() { super('CoreBlockOnlyTitleComponent'); @@ -43,7 +43,8 @@ export class CoreBlockOnlyTitleComponent extends CoreBlockBaseComponent impleme * Go to the block page. */ gotoBlock(): void { - CoreNavHelper.instance.goInSite(this.link!, this.linkParams!, undefined, true); + // @todo test that this is working properly. + CoreNavigator.instance.navigateToSitePath(this.link!, { params: this.linkParams }); } } diff --git a/src/core/features/contentlinks/classes/module-list-handler.ts b/src/core/features/contentlinks/classes/module-list-handler.ts index e0359ae50..f1c5c376a 100644 --- a/src/core/features/contentlinks/classes/module-list-handler.ts +++ b/src/core/features/contentlinks/classes/module-list-handler.ts @@ -16,7 +16,6 @@ import { CoreContentLinksHandlerBase } from './base-handler'; import { Translate } from '@singletons'; import { Params } from '@angular/router'; import { CoreContentLinksAction } from '../services/contentlinks-delegate'; -import { CoreNavHelper } from '@services/nav-helper'; /** * Handler to handle URLs pointing to a list of a certain type of modules. @@ -56,14 +55,16 @@ export class CoreContentLinksModuleListHandler extends CoreContentLinksHandlerBa getActions(siteIds: string[], url: string, params: Params): CoreContentLinksAction[] | Promise { return [{ + // eslint-disable-next-line @typescript-eslint/no-unused-vars action: (siteId): void => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const stateParams = { courseId: params.id, modName: this.modName, title: this.title || Translate.instance.instant('addon.mod_' + this.modName + '.modulenameplural'), }; - CoreNavHelper.instance.goInSite('CoreCourseListModTypePage @todo', stateParams, siteId); + // @todo CoreNavigator.instance.goInSite('CoreCourseListModTypePage', stateParams, siteId); }, }]; } diff --git a/src/core/features/contentlinks/pages/choose-site/choose-site.ts b/src/core/features/contentlinks/pages/choose-site/choose-site.ts index 376de7f04..2a95d6e56 100644 --- a/src/core/features/contentlinks/pages/choose-site/choose-site.ts +++ b/src/core/features/contentlinks/pages/choose-site/choose-site.ts @@ -21,7 +21,7 @@ import { CoreContentLinksAction } from '../../services/contentlinks-delegate'; import { CoreContentLinksHelper } from '../../services/contentlinks-helper'; import { ActivatedRoute } from '@angular/router'; import { CoreError } from '@classes/errors/error'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; /** * Page to display the list of sites to choose one to perform a content link action. @@ -102,7 +102,7 @@ export class CoreContentLinksChooseSitePage implements OnInit { */ siteClicked(siteId: string): void { if (this.isRootURL) { - CoreNavHelper.instance.openInSiteMainMenu('', {}, siteId); + CoreNavigator.instance.navigateToSiteHome({ siteId }); } else if (this.action) { this.action.action(siteId); } diff --git a/src/core/features/contentlinks/services/contentlinks-helper.ts b/src/core/features/contentlinks/services/contentlinks-helper.ts index 75631efce..f789767d7 100644 --- a/src/core/features/contentlinks/services/contentlinks-helper.ts +++ b/src/core/features/contentlinks/services/contentlinks-helper.ts @@ -19,8 +19,7 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreContentLinksDelegate, CoreContentLinksAction } from './contentlinks-delegate'; import { CoreSite } from '@classes/site'; import { makeSingleton, Translate } from '@singletons'; -import { Params } from '@angular/router'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; /** * Service that provides some features regarding content links. @@ -92,10 +91,11 @@ export class CoreContentLinksHelperProvider { * @param siteId Site ID. If not defined, current site. * @param checkMenu If true, check if the root page of a main menu tab. Only the page name will be checked. * @return Promise resolved when done. - * @deprecated since 3.9.5. Use CoreNavHelperService.goInSite instead. + * @deprecated since 3.9.5. Use CoreNavigator.navigateToSitePath instead. */ - goInSite(pageName: string, pageParams: Params, siteId?: string, checkMenu?: boolean): Promise { - return CoreNavHelper.instance.goInSite(pageName, pageParams, siteId, checkMenu); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async goInSite(navCtrl: NavController, pageName: string, pageParams: any, siteId?: string): Promise { + await CoreNavigator.instance.navigateToSitePath(pageName, { params: pageParams, siteId }); } /** @@ -192,7 +192,7 @@ export class CoreContentLinksHelperProvider { } } else { // Login in the site. - return CoreNavHelper.instance.openInSiteMainMenu('', {}, site.getId()); + await CoreNavigator.instance.navigateToSiteHome({ siteId: site.getId() }); } } diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index 0df7ad379..01dbf096a 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -34,7 +34,6 @@ import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses import { CoreArray } from '@singletons/array'; import { CoreIonLoadingElement } from '@classes/ion-loading'; import { CoreCourseOffline } from './course-offline'; -import { CoreNavHelper, CoreNavHelperService } from '@services/nav-helper'; import { CoreCourseOptionsDelegate, CoreCourseOptionsHandlerToDisplay, @@ -979,7 +978,7 @@ export class CoreCourseHelperProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when done. */ - openCourse(course: CoreEnrolledCourseBasicData | { id: number }, params?: Params, siteId?: string): Promise { + async openCourse(course: CoreEnrolledCourseBasicData | { id: number }, params?: Params, siteId?: string): Promise { if (!siteId || siteId == CoreSites.instance.getCurrentSiteId()) { // Current site, we can open the course. return CoreCourse.instance.openCourse(course, params); @@ -988,7 +987,9 @@ export class CoreCourseHelperProvider { params = params || {}; Object.assign(params, { course: course }); - return CoreNavHelper.instance.openInSiteMainMenu(CoreNavHelperService.OPEN_COURSE, params, siteId); + // @todo implement open course. + // await CoreNavigator.instance.navigateToSitePath('/course/.../...', { siteId, queryParams: params }); + // return CoreNavigator.instance.openInSiteMainMenu(CoreNavigatorService.OPEN_COURSE, params, siteId); } } diff --git a/src/core/features/courses/pages/dashboard/dashboard.ts b/src/core/features/courses/pages/dashboard/dashboard.ts index cc93164a6..f32c3b666 100644 --- a/src/core/features/courses/pages/dashboard/dashboard.ts +++ b/src/core/features/courses/pages/dashboard/dashboard.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; -import { IonRefresher, NavController } from '@ionic/angular'; +import { IonRefresher } from '@ionic/angular'; import { CoreCourses, CoreCoursesProvider } from '../../services/courses'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; @@ -22,6 +22,7 @@ import { CoreCoursesDashboard } from '@features/courses/services/dashboard'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreCourseBlock } from '@features/course/services/course'; import { CoreBlockComponent } from '@features/block/components/block/block'; +import { CoreNavigator } from '@services/navigator'; /** * Page that displays the dashboard page. @@ -35,7 +36,6 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { @ViewChildren(CoreBlockComponent) blocksComponents?: QueryList; - searchEnabled = false; downloadEnabled = false; downloadCourseEnabled = false; @@ -47,10 +47,6 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { protected updateSiteObserver?: CoreEventObserver; - constructor( - protected navCtrl: NavController, - ) { } - /** * Initialize the component. */ @@ -171,8 +167,8 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { /** * Go to search courses. */ - openSearch(): void { - this.navCtrl.navigateForward(['/main/home/courses/search']); + async openSearch(): Promise { + CoreNavigator.instance.navigateToSitePath('/courses/search'); } /** diff --git a/src/core/features/login/pages/change-password/change-password.ts b/src/core/features/login/pages/change-password/change-password.ts index 9872eb67a..6cb928503 100644 --- a/src/core/features/login/pages/change-password/change-password.ts +++ b/src/core/features/login/pages/change-password/change-password.ts @@ -18,7 +18,7 @@ import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreLoginHelper } from '@features/login/services/login-helper'; import { Translate } from '@singletons'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; /** * Page that shows instructions to change the password. @@ -63,7 +63,7 @@ export class CoreLoginChangePasswordPage { * Login the user. */ login(): void { - CoreNavHelper.instance.goToSiteInitialPage(); + CoreNavigator.instance.navigateToSiteHome(); this.changingPassword = false; } diff --git a/src/core/features/login/pages/credentials/credentials.ts b/src/core/features/login/pages/credentials/credentials.ts index 118360ee3..34cb74c81 100644 --- a/src/core/features/login/pages/credentials/credentials.ts +++ b/src/core/features/login/pages/credentials/credentials.ts @@ -26,7 +26,7 @@ import { CoreConstants } from '@/core/constants'; import { Translate } from '@singletons'; import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/site'; import { CoreEvents } from '@singletons/events'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; /** * Page to enter the user credentials. @@ -245,7 +245,8 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { this.siteId = id; - await CoreNavHelper.instance.goToSiteInitialPage({ urlToOpen: this.urlToOpen }); + // @todo test that this is working properly. + await CoreNavigator.instance.navigateToSiteHome({ params: { urlToOpen: this.urlToOpen } }); } catch (error) { CoreLoginHelper.instance.treatUserTokenError(siteUrl, error, username, password); diff --git a/src/core/features/login/pages/init/init.ts b/src/core/features/login/pages/init/init.ts index 97f89a85a..096dda7a0 100644 --- a/src/core/features/login/pages/init/init.ts +++ b/src/core/features/login/pages/init/init.ts @@ -20,7 +20,7 @@ import { ApplicationInit, SplashScreen } from '@singletons'; import { CoreConstants } from '@/core/constants'; import { CoreSites } from '@services/sites'; import { CoreLoginHelper } from '@features/login/services/login-helper'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; /** * Page that displays a "splash screen" while the app is being initialized. @@ -32,6 +32,8 @@ import { CoreNavHelper } from '@services/nav-helper'; }) export class CoreLoginInitPage implements OnInit { + // @todo this page should be removed in favor of native splash + // or a splash component rendered in the root app component constructor(protected navCtrl: NavController) {} /** @@ -80,17 +82,21 @@ export class CoreLoginInitPage implements OnInit { return; } - return CoreNavHelper.instance.goToSiteInitialPage({ - redirectPage: redirectData.page, - redirectParams: redirectData.params, + await CoreNavigator.instance.navigateToSiteHome({ + params: { + redirectPath: redirectData.page, + redirectParams: redirectData.params, + }, }); + + return; } catch (error) { // Site doesn't exist. return this.loadPage(); } } else if (redirectData.page) { // No site to load, open the page. - return CoreNavHelper.instance.goToNoSitePage(redirectData.page, redirectData.params); + // @todo return CoreNavigator.instance.goToNoSitePage(redirectData.page, redirectData.params); } } @@ -110,7 +116,9 @@ export class CoreLoginInitPage implements OnInit { return this.loadPage(); } - return CoreNavHelper.instance.goToSiteInitialPage(); + await CoreNavigator.instance.navigateToSiteHome(); + + return; } await this.navCtrl.navigateRoot('/login/sites'); diff --git a/src/core/features/login/pages/reconnect/reconnect.ts b/src/core/features/login/pages/reconnect/reconnect.ts index 729bcd50b..24204a371 100644 --- a/src/core/features/login/pages/reconnect/reconnect.ts +++ b/src/core/features/login/pages/reconnect/reconnect.ts @@ -25,7 +25,7 @@ import { CoreLoginHelper } from '@features/login/services/login-helper'; import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/site'; import { CoreEvents } from '@singletons/events'; import { CoreError } from '@classes/errors/error'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; /** * Page to enter the user password to reconnect to a site. @@ -207,9 +207,12 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy { this.credForm.controls['password'].reset(); // Go to the site initial page. - await CoreNavHelper.instance.goToSiteInitialPage({ - redirectPage: this.page, - redirectParams: this.pageParams, + // @todo test that this is working properly (could we use navigateToSitePath instead?). + await CoreNavigator.instance.navigateToSiteHome({ + params: { + redirectPath: this.page, + redirectParams: this.pageParams, + }, }); } catch (error) { CoreLoginHelper.instance.treatUserTokenError(this.siteUrl, error, this.username, password); diff --git a/src/core/features/login/pages/site-policy/site-policy.ts b/src/core/features/login/pages/site-policy/site-policy.ts index b38591b98..15af5cce8 100644 --- a/src/core/features/login/pages/site-policy/site-policy.ts +++ b/src/core/features/login/pages/site-policy/site-policy.ts @@ -22,7 +22,7 @@ import { CoreUtils } from '@services/utils/utils'; import { CoreMimetypeUtils } from '@services/utils/mimetype'; import { CoreLoginHelper } from '@features/login/services/login-helper'; import { CoreSite } from '@classes/site'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; /** * Page to accept a site policy. @@ -129,7 +129,7 @@ export class CoreLoginSitePolicyPage implements OnInit { // Invalidate cache since some WS don't return error if site policy is not accepted. await CoreUtils.instance.ignoreErrors(this.currentSite!.invalidateWsCache()); - await CoreNavHelper.instance.goToSiteInitialPage(); + await CoreNavigator.instance.navigateToSiteHome(); } catch (error) { CoreDomUtils.instance.showErrorModalDefault(error, 'Error accepting site policy.'); } finally { diff --git a/src/core/features/login/pages/site/site.ts b/src/core/features/login/pages/site/site.ts index 7d60e9871..326b6f648 100644 --- a/src/core/features/login/pages/site/site.ts +++ b/src/core/features/login/pages/site/site.ts @@ -31,7 +31,7 @@ import { CoreUrl } from '@singletons/url'; import { CoreUrlUtils } from '@services/utils/url'; import { CoreLoginSiteHelpComponent } from '@features/login/components/site-help/site-help'; import { CoreLoginSiteOnboardingComponent } from '@features/login/components/site-onboarding/site-onboarding'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; /** * Page that displays a "splash screen" while the app is being initialized. @@ -329,7 +329,9 @@ export class CoreLoginSitePage implements OnInit { CoreDomUtils.instance.triggerFormSubmittedEvent(this.formElement, true); - return CoreNavHelper.instance.goToSiteInitialPage(); + await CoreNavigator.instance.navigateToSiteHome(); + + return; } catch (error) { CoreLoginHelper.instance.treatUserTokenError(siteData.url, error, siteData.username, siteData.password); diff --git a/src/core/features/login/pages/sites/sites.ts b/src/core/features/login/pages/sites/sites.ts index 276bb6a94..429682697 100644 --- a/src/core/features/login/pages/sites/sites.ts +++ b/src/core/features/login/pages/sites/sites.ts @@ -19,7 +19,7 @@ import { Component, OnInit } from '@angular/core'; import { CoreSiteBasicInfo, CoreSites } from '@services/sites'; import { CoreLogger } from '@singletons/logger'; import { CoreLoginHelper } from '@features/login/services/login-helper'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; /** * Page that displays a "splash screen" while the app is being initialized. @@ -125,7 +125,9 @@ export class CoreLoginSitesPage implements OnInit { const loggedIn = await CoreSites.instance.loadSite(siteId); if (loggedIn) { - return CoreNavHelper.instance.goToSiteInitialPage(); + await CoreNavigator.instance.navigateToSiteHome(); + + return; } } catch (error) { this.logger.error('Error loading site ' + siteId, error); diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index 1b50a1c7e..dade42d2e 100644 --- a/src/core/features/login/services/login-helper.ts +++ b/src/core/features/login/services/login-helper.ts @@ -34,7 +34,7 @@ import { makeSingleton, Translate } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { CoreUrl } from '@singletons/url'; import { CoreObject } from '@singletons/object'; -import { CoreNavHelper, CoreNavHelperOpenMainMenuOptions, CoreNavHelperService } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; /** * Helper provider that provides some common features regarding authentication. @@ -42,7 +42,11 @@ import { CoreNavHelper, CoreNavHelperOpenMainMenuOptions, CoreNavHelperService } @Injectable({ providedIn: 'root' }) export class CoreLoginHelperProvider { - static readonly OPEN_COURSE = CoreNavHelperService.OPEN_COURSE; // @deprecated since 3.9.5. + /** + * @deprecated since 3.9.5. + */ + static readonly OPEN_COURSE = 'open_course'; + static readonly ONBOARDING_DONE = 'onboarding_done'; static readonly FAQ_URL_IMAGE_HTML = ''; static readonly FAQ_QRCODE_IMAGE_HTML = ''; @@ -111,9 +115,12 @@ export class CoreLoginHelperProvider { */ checkLogout(): void { const currentSite = CoreSites.instance.getCurrentSite(); - const currentPage = CoreNavHelper.instance.getCurrentPage(); - if (!CoreApp.instance.isSSOAuthenticationOngoing() && currentSite?.isLoggedOut() && currentPage == '/login/reconnect') { + if ( + !CoreApp.instance.isSSOAuthenticationOngoing() && + currentSite?.isLoggedOut() && + CoreNavigator.instance.isCurrent('/login/reconnect') + ) { // User must reauthenticate but he closed the InAppBrowser without doing so, logout him. CoreSites.instance.logout(); } @@ -436,21 +443,33 @@ export class CoreLoginHelperProvider { * @param page Page to open. * @param params Params of the page. * @return Promise resolved when done. - * @deprecated since 3.9.5. Use CoreNavHelperService.goToNoSitePage instead. + * @deprecated since 3.9.5. Use CoreNavigator.navigateToLoginCredentials instead. */ - goToNoSitePage(page: string, params?: Params): Promise { - return CoreNavHelper.instance.goToNoSitePage(page, params); + async goToNoSitePage(page: string, params?: Params): Promise { + await CoreNavigator.instance.navigateToLoginCredentials(params); } /** * Go to the initial page of a site depending on 'userhomepage' setting. * - * @param options Options. + * @param navCtrl NavController to use. Defaults to app root NavController. + * @param page Name of the page to load after loading the main page. + * @param params Params to pass to the page. + * @param options Navigation options. + * @param url URL to open once the main menu is loaded. * @return Promise resolved when done. - * @deprecated since 3.9.5. Use CoreNavHelperService.goToSiteInitialPage instead. + * @deprecated since 3.9.5. Use CoreNavigator.navigateToSiteHome or CoreNavigator.navigateToSitePath instead. */ - goToSiteInitialPage(options?: CoreNavHelperOpenMainMenuOptions): Promise { - return CoreNavHelper.instance.goToSiteInitialPage(options); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async goToSiteInitialPage(navCtrl?: NavController, page?: string, params?: any, options?: any, url?: string): Promise { + await CoreNavigator.instance.navigateToSiteHome({ + ...options, + params: { + redirectPath: page, + redirectParams: params, + urlToOpen: url, + }, + }); } /** @@ -606,10 +625,10 @@ export class CoreLoginHelperProvider { * * @param page Name of the page to load. * @param params Params to pass to the page. - * @deprecated since 3.9.5. Use CoreNavHelperService.loadPageInMainMenu instead. + * @deprecated since 3.9.5. Use CoreNavigator.navigateToSitepath instead. */ loadPageInMainMenu(page: string, params?: Params): void { - CoreNavHelper.instance.loadPageInMainMenu(page, params); + CoreNavigator.instance.navigateToSitePath(page, { params }); } /** @@ -767,10 +786,8 @@ export class CoreLoginHelperProvider { return; // Site that triggered the event is not current site. } - const currentPage = CoreNavHelper.instance.getCurrentPage(); - // If current page is already change password, stop. - if (currentPage == '/login/changepassword') { + if (CoreNavigator.instance.isCurrent('/login/changepassword')) { return; } @@ -826,14 +843,14 @@ export class CoreLoginHelperProvider { /** * Redirect to a new page, setting it as the root page and loading the right site if needed. * - * @param page Name of the page to load. Special cases: CoreNavHelperService.OPEN_COURSE (to open course page). + * @param page Name of the page to load. * @param params Params to pass to the page. * @param siteId Site to load. If not defined, current site. * @return Promise resolved when done. - * @deprecated since 3.9.5. Use CoreNavHelperService.openInSiteMainMenu instead. + * @deprecated since 3.9.5. Use CoreNavigator.navigateToSitePath instead. */ - redirect(page: string, params?: Params, siteId?: string): Promise { - return CoreNavHelper.instance.openInSiteMainMenu(page, params, siteId); + async redirect(page: string, params?: Params, siteId?: string): Promise { + await CoreNavigator.instance.navigateToSitePath(page, { params, siteId }); } /** @@ -959,14 +976,14 @@ export class CoreLoginHelperProvider { const info = currentSite.getInfo(); if (typeof info != 'undefined' && typeof info.username != 'undefined' && !this.isOpeningReconnect) { // If current page is already reconnect, stop. - if (CoreNavHelper.instance.getCurrentPage() == '/login/reconnect') { + if (CoreNavigator.instance.isCurrent('/login/reconnect')) { return; } this.isOpeningReconnect = true; await CoreUtils.instance.ignoreErrors(this.navCtrl.navigateRoot('/login/reconnect', { - queryParams: CoreObject.removeUndefined({ + queryParams: CoreObject.withoutEmpty({ siteId, pageName: data.pageName, pageParams: data.params, @@ -1127,7 +1144,7 @@ export class CoreLoginHelperProvider { } // If current page is already site policy, stop. - if (CoreNavHelper.instance.getCurrentPage() == '/login/sitepolicy') { + if (CoreNavigator.instance.isCurrent('/login/sitepolicy')) { return; } diff --git a/src/core/features/mainmenu/pages/menu/menu.html b/src/core/features/mainmenu/pages/menu/menu.html index 148c37584..c86ab2ea3 100644 --- a/src/core/features/mainmenu/pages/menu/menu.html +++ b/src/core/features/mainmenu/pages/menu/menu.html @@ -2,9 +2,6 @@ - - - diff --git a/src/core/features/mainmenu/pages/menu/menu.ts b/src/core/features/mainmenu/pages/menu/menu.ts index 9e99e239e..45dbddbe4 100644 --- a/src/core/features/mainmenu/pages/menu/menu.ts +++ b/src/core/features/mainmenu/pages/menu/menu.ts @@ -13,19 +13,19 @@ // limitations under the License. import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef } from '@angular/core'; -import { ActivatedRoute, Params, Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { NavController, IonTabs } from '@ionic/angular'; import { Subscription } from 'rxjs'; import { CoreApp } from '@services/app'; import { CoreSites } from '@services/sites'; import { CoreTextUtils } from '@services/utils/text'; -import { CoreEvents, CoreEventObserver, CoreEventLoadPageMainMenuData } from '@singletons/events'; +import { CoreEvents, CoreEventObserver } from '@singletons/events'; import { CoreMainMenu } from '../../services/mainmenu'; import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../services/mainmenu-delegate'; import { CoreDomUtils } from '@services/utils/dom'; import { Translate } from '@singletons'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreRedirectPayload } from '@services/navigator'; /** * Page that displays the main menu of the app. @@ -40,17 +40,14 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { tabs: CoreMainMenuHandlerToDisplay[] = []; allHandlers?: CoreMainMenuHandlerToDisplay[]; loaded = false; - redirectPage?: string; - redirectParams?: Params; showTabs = false; tabsPlacement = 'bottom'; hidden = false; protected subscription?: Subscription; protected redirectObs?: CoreEventObserver; - protected pendingRedirect?: CoreEventLoadPageMainMenuData; + protected pendingRedirect?: CoreRedirectPayload; protected urlToOpen?: string; - protected mainMenuId: number; protected keyboardObserver?: CoreEventObserver; @ViewChild('mainTabs') mainTabs?: IonTabs; @@ -60,44 +57,32 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { protected navCtrl: NavController, protected changeDetector: ChangeDetectorRef, protected router: Router, - ) { - this.mainMenuId = CoreNavHelper.instance.getMainMenuId(); - } + ) {} /** * Initialize the component. */ ngOnInit(): void { + // @TODO this should be handled by route guards and can be removed if (!CoreSites.instance.isLoggedIn()) { this.navCtrl.navigateRoot('/login/init'); return; } - this.route.queryParams.subscribe(params => { - const redirectPage = params['redirectPage']; - if (redirectPage) { + this.route.queryParams.subscribe((params: Partial & { urlToOpen?: string }) => { + if (params.redirectPath) { this.pendingRedirect = { - redirectPage: redirectPage, - redirectParams: params['redirectParams'], + redirectPath: params.redirectPath, + redirectParams: params.redirectParams, }; } - this.urlToOpen = params['urlToOpen']; + this.urlToOpen = params.urlToOpen; }); this.showTabs = true; - this.redirectObs = CoreEvents.on(CoreEvents.LOAD_PAGE_MAIN_MENU, (data: CoreEventLoadPageMainMenuData) => { - if (!this.loaded) { - // View isn't ready yet, wait for it to be ready. - this.pendingRedirect = data; - } else { - delete this.pendingRedirect; - this.handleRedirect(data); - } - }); - this.subscription = CoreMainMenuDelegate.instance.getHandlersObservable().subscribe((handlers) => { // Remove the handlers that should only appear in the More menu. this.allHandlers = handlers.filter((handler) => !handler.onlyInMore); @@ -131,8 +116,6 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { } }); } - - CoreNavHelper.instance.setMainMenuOpen(this.mainMenuId, true); } /** @@ -201,13 +184,13 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { * * @param data Data received. */ - protected handleRedirect(data: CoreEventLoadPageMainMenuData): void { + protected handleRedirect(data: CoreRedirectPayload): void { // Check if the redirect page is the root page of any of the tabs. - const i = this.tabs.findIndex((tab) => tab.page == data.redirectPage); + const i = this.tabs.findIndex((tab) => tab.page == data.redirectPath); if (i >= 0) { // Tab found. Open it with the params. - this.navCtrl.navigateForward(data.redirectPage, { + this.navCtrl.navigateForward(data.redirectPath, { queryParams: data.redirectParams, animated: false, }); @@ -227,7 +210,6 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { this.subscription?.unsubscribe(); this.redirectObs?.off(); window.removeEventListener('resize', this.initHandlers.bind(this)); - CoreNavHelper.instance.setMainMenuOpen(this.mainMenuId, false); this.keyboardObserver?.off(); } diff --git a/src/core/features/mainmenu/services/mainmenu.ts b/src/core/features/mainmenu/services/mainmenu.ts index 42a9a51b0..ac344f718 100644 --- a/src/core/features/mainmenu/services/mainmenu.ts +++ b/src/core/features/mainmenu/services/mainmenu.ts @@ -17,7 +17,6 @@ import { Injectable } from '@angular/core'; import { CoreApp } from '@services/app'; import { CoreLang } from '@services/lang'; import { CoreSites } from '@services/sites'; -import { CoreUtils } from '@services/utils/utils'; import { CoreConstants } from '@/core/constants'; import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from './mainmenu-delegate'; import { makeSingleton } from '@singletons'; @@ -42,20 +41,10 @@ export class CoreMainMenuProvider { * * @return Promise resolved with the current main menu handlers. */ - getCurrentMainMenuHandlers(): Promise { - const deferred = CoreUtils.instance.promiseDefer(); - - const subscription = CoreMainMenuDelegate.instance.getHandlersObservable().subscribe((handlers) => { - subscription?.unsubscribe(); - - // Remove the handlers that should only appear in the More menu. - handlers = handlers.filter(handler => !handler.onlyInMore); - - // Return main handlers. - deferred.resolve(handlers.slice(0, this.getNumItems())); - }); - - return deferred.promise; + getCurrentMainMenuHandlers(): CoreMainMenuHandlerToDisplay[] { + return CoreMainMenuDelegate.instance.getHandlers() + .filter(handler => !handler.onlyInMore) + .slice(0, this.getNumItems()); } /** diff --git a/src/core/features/sitehome/services/handlers/index-link.ts b/src/core/features/sitehome/services/handlers/index-link.ts index 567a0230a..505fa2716 100644 --- a/src/core/features/sitehome/services/handlers/index-link.ts +++ b/src/core/features/sitehome/services/handlers/index-link.ts @@ -19,7 +19,7 @@ import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; import { CoreSiteHome } from '../sitehome'; import { makeSingleton } from '@singletons'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; /** * Handler to treat links to site home index. @@ -43,7 +43,8 @@ export class CoreSiteHomeIndexLinkHandlerService extends CoreContentLinksHandler getActions(): CoreContentLinksAction[] | Promise { return [{ action: (siteId: string): void => { - CoreNavHelper.instance.goInSite('sitehome', [], siteId); + // @todo This should open the 'sitehome' setting as well. + CoreNavigator.instance.navigateToSiteHome({ siteId }); }, }]; } diff --git a/src/core/features/tag/services/handlers/index.link.ts b/src/core/features/tag/services/handlers/index.link.ts index b4836cd73..0e2103d61 100644 --- a/src/core/features/tag/services/handlers/index.link.ts +++ b/src/core/features/tag/services/handlers/index.link.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { Params } from '@angular/router'; import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; import { makeSingleton } from '@singletons'; import { CoreTag } from '../tag'; @@ -57,11 +57,11 @@ export class CoreTagIndexLinkHandlerService extends CoreContentLinksHandlerBase }; if (!pageParams.tagId && (!pageParams.tagName || !pageParams.collectionId)) { - CoreNavHelper.instance.goInSite('/tag/search', {}, siteId); + CoreNavigator.instance.navigateToSitePath('/tag/search', { siteId }); } else if (pageParams.areaId) { - CoreNavHelper.instance.goInSite('/tag/index-area', pageParams, siteId); + CoreNavigator.instance.navigateToSitePath('/tag/index-area', { params: pageParams, siteId }); } else { - CoreNavHelper.instance.goInSite('/tag/index', pageParams, siteId); + CoreNavigator.instance.navigateToSitePath('/tag/index', { params: pageParams, siteId }); } }, }]; diff --git a/src/core/features/tag/services/handlers/search.link.ts b/src/core/features/tag/services/handlers/search.link.ts index 382a09688..cb025d449 100644 --- a/src/core/features/tag/services/handlers/search.link.ts +++ b/src/core/features/tag/services/handlers/search.link.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { Params } from '@angular/router'; import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; import { makeSingleton } from '@singletons'; import { CoreTag } from '../tag'; @@ -47,7 +47,7 @@ export class CoreTagSearchLinkHandlerService extends CoreContentLinksHandlerBase query: params.query || '', }; - CoreNavHelper.instance.goInSite('/tag/search', pageParams, siteId); + CoreNavigator.instance.navigateToSitePath('/tag/search', { params: pageParams, siteId }); }, }]; } diff --git a/src/core/features/user/services/handlers/profile-link.ts b/src/core/features/user/services/handlers/profile-link.ts index ca3f70c6f..d9a98b174 100644 --- a/src/core/features/user/services/handlers/profile-link.ts +++ b/src/core/features/user/services/handlers/profile-link.ts @@ -17,7 +17,7 @@ import { Params } from '@angular/router'; import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; -import { CoreNavHelper } from '@services/nav-helper'; +import { CoreNavigator } from '@services/navigator'; import { makeSingleton } from '@singletons'; /** @@ -54,7 +54,7 @@ export class CoreUserProfileLinkHandlerService extends CoreContentLinksHandlerBa userId: parseInt(params.id, 10), }; - CoreNavHelper.instance.goInSite('/user', pageParams, siteId); + CoreNavigator.instance.navigateToSitePath('/user', { params: pageParams, siteId }); }, }]; } diff --git a/src/core/services/app.ts b/src/core/services/app.ts index d14d121a8..e09f53260 100644 --- a/src/core/services/app.ts +++ b/src/core/services/app.ts @@ -25,7 +25,6 @@ import { makeSingleton, Keyboard, Network, StatusBar, Platform, Device } from '@ import { CoreLogger } from '@singletons/logger'; import { CoreColors } from '@singletons/colors'; import { DBNAME, SCHEMA_VERSIONS_TABLE_NAME, SCHEMA_VERSIONS_TABLE_SCHEMA, SchemaVersionsDBEntry } from '@services/database/app'; -import { CoreNavHelper } from './nav-helper'; /** * Object responsible of managing schema versions. @@ -178,10 +177,10 @@ export class CoreAppProvider { * Get an ID for a main menu. * * @return Main menu ID. - * @deprecated since 3.9.5. Use CoreNavHelperService.getMainMenuId instead. + * @deprecated since 3.9.5. No longer supported. */ getMainMenuId(): number { - return CoreNavHelper.instance.getMainMenuId(); + return 0; } /** @@ -306,10 +305,10 @@ export class CoreAppProvider { * Check if the main menu is open. * * @return Whether the main menu is open. - * @deprecated since 3.9.5. Use CoreNavHelperService.isMainMenuOpen instead. + * @deprecated since 3.9.5. No longer supported. */ isMainMenuOpen(): boolean { - return CoreNavHelper.instance.isMainMenuOpen(); + return false; } /** @@ -445,17 +444,6 @@ export class CoreAppProvider { this.keyboardClosing = false; } - /** - * Set a main menu as open or not. - * - * @param id Main menu ID. - * @param open Whether it's open or not. - * @deprecated since 3.9.5. Use CoreNavHelperService.setMainMenuOpen instead. - */ - setMainMenuOpen(id: number, open: boolean): void { - CoreNavHelper.instance.setMainMenuOpen(id, open); - } - /** * Start an SSO authentication process. * Please notice that this function should be called when the app receives the new token from the browser, diff --git a/src/core/services/nav-helper.ts b/src/core/services/nav-helper.ts deleted file mode 100644 index 69819bac4..000000000 --- a/src/core/services/nav-helper.ts +++ /dev/null @@ -1,364 +0,0 @@ -// (C) Copyright 2015 Moodle Pty Ltd. -// -// Licensed 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 { Injectable } from '@angular/core'; -import { Params, Router } from '@angular/router'; -import { CoreMainMenu } from '@features/mainmenu/services/mainmenu'; -import { NavController } from '@ionic/angular'; -import { NavigationOptions } from '@ionic/angular/providers/nav-controller'; - -import { makeSingleton } from '@singletons'; -import { CoreEvents } from '@singletons/events'; -import { CoreConstants } from '../constants'; -import { CoreSites } from './sites'; -import { CoreDomUtils } from './utils/dom'; -import { CoreTextUtils } from './utils/text'; -import { CoreUrlUtils } from './utils/url'; - -/** - * Provider to provide some helper functions regarding navigation. - */ -@Injectable({ providedIn: 'root' }) -export class CoreNavHelperService { - - static readonly OPEN_COURSE = 'open_course'; - - protected pageToLoad?: {page: string; params?: Params; time: number}; // Page to load once main menu is opened. - protected mainMenuOpen?: number; - protected mainMenuId = 0; - - constructor( - protected router: Router, - protected navCtrl: NavController, - ) { - CoreEvents.on(CoreEvents.MAIN_MENU_OPEN, () => { - /* If there is any page pending to be opened, do it now. Don't open pages stored more than 5 seconds ago, probably - the function to open the page was called when it shouldn't. */ - if (this.pageToLoad && Date.now() - this.pageToLoad.time < 5000) { - this.loadPageInMainMenu(this.pageToLoad.page, this.pageToLoad.params); - delete this.pageToLoad; - } - }); - } - - /** - * Get current page route without params. - * - * @return Current page route. - */ - getCurrentPage(): string { - return CoreUrlUtils.instance.removeUrlParams(this.router.url); - } - - /** - * Open a new page in the current main menu tab. - * - * @param page Page to open. - * @param pageParams Params to send to the page. - * @return Promise resolved when done. - */ - async goInCurrentMainMenuTab(page: string, pageParams: Params): Promise { - const currentPage = this.getCurrentPage(); - - const routeMatch = currentPage.match(/^\/main\/([^/]+)/); - if (!routeMatch || !routeMatch[0]) { - // Not in a tab. Stop. - return; - } - - let path = ''; - if (routeMatch[1] && page.match(new RegExp(`^/${routeMatch[1]}(/|$)`))) { - path = CoreTextUtils.instance.concatenatePaths('/main', page); - } else { - path = CoreTextUtils.instance.concatenatePaths(routeMatch[0], page); - } - - await this.navCtrl.navigateForward(path, { - queryParams: pageParams, - }); - } - - /** - * Goes to a certain page in a certain site. If the site is current site it will perform a regular navigation, - * otherwise it will load the other site and open the page in main menu. - * - * @param path Path to go. - * @param pageParams Params to send to the page. - * @param siteId Site ID. If not defined, current site. - * @param checkMenu If true, check if the root page is on a main menu tab. Only the path will be checked. - * @return Promise resolved when done. - */ - async goInSite(path: string, pageParams: Params, siteId?: string, checkMenu?: boolean): Promise { - - siteId = siteId || CoreSites.instance.getCurrentSiteId(); - - // @todo: When this function was in ContentLinksHelper, this code was inside NgZone. Check if it's needed. - - if (!CoreSites.instance.isLoggedIn() || siteId != CoreSites.instance.getCurrentSiteId()) { - await this.openInSiteMainMenu(path, pageParams, siteId); - - return; - } - - if (checkMenu) { - let isInMenu = false; - // Check if the page is in the main menu. - try { - isInMenu = await CoreMainMenu.instance.isCurrentMainMenuHandler(path); - } catch { - isInMenu = false; - } - - if (isInMenu) { - // Just select the tab. @todo test. - CoreNavHelper.instance.loadPageInMainMenu(path, pageParams); - - return; - } - } - - await this.goInCurrentMainMenuTab(path, pageParams); - } - - /** - * Get an ID for a main menu. - * - * @return Main menu ID. - */ - getMainMenuId(): number { - return this.mainMenuId++; - } - - /** - * Open a page that doesn't belong to any site. - * - * @param page Page to open. - * @param params Params of the page. - * @return Promise resolved when done. - */ - async goToNoSitePage(page: string, params?: Params): Promise { - const currentPage = this.getCurrentPage(); - - if (currentPage == page) { - // Already at page, nothing to do. - return; - } - - if (page == '/login/sites') { - // Just open the page as root. - await this.navCtrl.navigateRoot(page, { queryParams: params }); - - return; - } - - if (page == '/login/credentials' && currentPage == '/login/site') { - // Just open the new page to keep the navigation history. - await this.navCtrl.navigateForward(page, { queryParams: params }); - - return; - } - - // Check if there is any site stored. - const hasSites = await CoreSites.instance.hasSites(); - - if (!hasSites) { - // There are sites stored, open sites page first to be able to go back. - await this.navCtrl.navigateRoot('/login/sites'); - - await this.navCtrl.navigateForward(page, { queryParams: params }); - - return; - } - - if (page != '/login/site') { - // Open the new site page to be able to go back. - await this.navCtrl.navigateRoot('/login/site'); - - await this.navCtrl.navigateForward(page, { queryParams: params }); - } else { - // Just open the page as root. - await this.navCtrl.navigateRoot(page, { queryParams: params }); - } - } - - /** - * Go to the initial page of a site depending on 'userhomepage' setting. - * - * @param options Options. - * @return Promise resolved when done. - */ - goToSiteInitialPage(options?: CoreNavHelperOpenMainMenuOptions): Promise { - return this.openMainMenu(options); - } - - /** - * Check if the main menu is open. - * - * @return Whether the main menu is open. - */ - isMainMenuOpen(): boolean { - return typeof this.mainMenuOpen != 'undefined'; - } - - /** - * Load a certain page in the main menu. - * - * @param page Route of the page to load. - * @param params Params to pass to the page. - */ - loadPageInMainMenu(page: string, params?: Params): void { - if (!this.isMainMenuOpen()) { - // Main menu not open. Store the page to be loaded later. - this.pageToLoad = { - page: page, - params: params, - time: Date.now(), - }; - - return; - } - - if (page == CoreNavHelperService.OPEN_COURSE) { - // @todo Use the openCourse function. - } else { - CoreEvents.trigger(CoreEvents.LOAD_PAGE_MAIN_MENU, { redirectPage: page, redirectParams: params }); - } - } - - /** - * Load a site and load a certain page in that site. - * - * @param siteId Site to load. - * @param page Name of the page to load. - * @param params Params to pass to the page. - * @return Promise resolved when done. - */ - protected async loadSiteAndPage(siteId: string, page: string, params?: Params): Promise { - if (siteId == CoreConstants.NO_SITE_ID) { - // Page doesn't belong to a site, just load the page. - await this.navCtrl.navigateRoot(page, params); - - return; - } - - const modal = await CoreDomUtils.instance.showModalLoading(); - - try { - const loggedIn = await CoreSites.instance.loadSite(siteId, page, params); - - if (!loggedIn) { - return; - } - - await this.openMainMenu({ - redirectPage: page, - redirectParams: params, - }); - } catch (error) { - // Site doesn't exist. - await this.navCtrl.navigateRoot('/login/sites'); - } finally { - modal.dismiss(); - } - } - - /** - * Open the main menu, loading a certain page. - * - * @param options Options. - * @return Promise resolved when done. - */ - protected async openMainMenu(options?: CoreNavHelperOpenMainMenuOptions): Promise { - - // Due to DeepLinker, we need to remove the path from the URL before going to main menu. - // IonTabs checks the URL to determine which path to load for deep linking, so we clear the URL. - // @todo this.location.replaceState(''); - - if (options?.redirectPage == CoreNavHelperService.OPEN_COURSE) { - // Load the main menu first, and then open the course. - try { - await this.navCtrl.navigateRoot('/'); - } finally { - // @todo: Open course. - } - } else { - // Open the main menu. - const queryParams: Params = Object.assign({}, options); - delete queryParams.navigationOptions; - - await this.navCtrl.navigateRoot('/', { - queryParams, - ...options?.navigationOptions, - }); - } - } - - /** - * Open a new page, setting it as the root page and loading the right site if needed. - * - * @param page Name of the page to load. Special cases: OPEN_COURSE (to open course page). - * @param params Params to pass to the page. - * @param siteId Site to load. If not defined, current site. - * @return Promise resolved when done. - */ - async openInSiteMainMenu(page: string, params?: Params, siteId?: string): Promise { - siteId = siteId || CoreSites.instance.getCurrentSiteId(); - - if (!CoreSites.instance.isLoggedIn()) { - if (siteId) { - await this.loadSiteAndPage(siteId, page, params); - } else { - await this.navCtrl.navigateRoot('/login/sites'); - } - - return; - } - - if (siteId && siteId != CoreSites.instance.getCurrentSiteId()) { - // Target page belongs to a different site. Change site. - // @todo: Check site plugins. - await CoreSites.instance.logout(); - - await this.loadSiteAndPage(siteId, page, params); - } else { - // Current page, open it in main menu. - this.loadPageInMainMenu(page, params); - } - } - - /** - * Set a main menu as open or not. - * - * @param id Main menu ID. - * @param open Whether it's open or not. - */ - setMainMenuOpen(id: number, open: boolean): void { - if (open) { - this.mainMenuOpen = id; - CoreEvents.trigger(CoreEvents.MAIN_MENU_OPEN); - } else if (this.mainMenuOpen == id) { - delete this.mainMenuOpen; - } - } - -} - -export class CoreNavHelper extends makeSingleton(CoreNavHelperService) {} - -export type CoreNavHelperOpenMainMenuOptions = { - redirectPage?: string; // Route of the page to open in main menu. If not defined, default tab will be selected. - redirectParams?: Params; // Params to pass to the selected tab if any. - urlToOpen?: string; // URL to open once the main menu is loaded. - navigationOptions?: NavigationOptions; // Navigation options. -}; diff --git a/src/core/services/navigator.ts b/src/core/services/navigator.ts new file mode 100644 index 000000000..7c03690fc --- /dev/null +++ b/src/core/services/navigator.ts @@ -0,0 +1,247 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed 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 { Injectable } from '@angular/core'; +import { ActivatedRoute, Params } from '@angular/router'; + +import { NavigationOptions } from '@ionic/angular/providers/nav-controller'; + +import { CoreConstants } from '@/core/constants'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreMainMenu } from '@features/mainmenu/services/mainmenu'; +import { CoreMainMenuHomeHandlerService } from '@features/mainmenu/services/handlers/mainmenu'; +import { CoreObject } from '@singletons/object'; +import { CoreSites } from '@services/sites'; +import { CoreUtils } from '@services/utils/utils'; +import { CoreUrlUtils } from '@services/utils/url'; +import { makeSingleton, NavController, Router } from '@singletons'; + +const DEFAULT_MAIN_MENU_TAB = CoreMainMenuHomeHandlerService.PAGE_NAME; + +/** + * Redirect payload. + */ +export type CoreRedirectPayload = { + redirectPath: string; + redirectParams?: Params; +}; + +/** + * Navigation options. + */ +export type CoreNavigationOptions = { + animated?: boolean; + params?: Params; + reset?: boolean; +}; + +/** + * Service to provide some helper functions regarding navigation. + */ +@Injectable({ providedIn: 'root' }) +export class CoreNavigatorService { + + /** + * Check whether the active route is using the given path. + * + * @param path Path. + * @return Whether the active route is using the given path. + */ + isCurrent(path: string): boolean { + return this.getCurrentPath() === path; + } + + /** + * Get current main menu tab. + * + * @return Current main menu tab or null if the current route is not using the main menu. + */ + getCurrentMainMenuTab(): string | null { + const currentPath = this.getCurrentPath(); + const matches = /^\/main\/([^/]+).*$/.exec(currentPath); + + return matches?.[1] ?? null; + } + + /** + * Navigate to a new path. + * + * @param path Path to navigate to. + * @param options Navigation options. + * @return Whether navigation suceeded. + */ + async navigate(path: string, options: CoreNavigationOptions = {}): Promise { + const url: string[] = [/^[./]/.test(path) ? path : `./${path}`]; + const navigationOptions: NavigationOptions = CoreObject.withoutEmpty({ + animated: options.animated, + queryParams: CoreObject.isEmpty(options.params ?? {}) ? null : options.params, + relativeTo: path.startsWith('/') ? null : this.getCurrentRoute(), + }); + const navigationResult = (options.reset ?? false) + ? await NavController.instance.navigateRoot(url, navigationOptions) + : await NavController.instance.navigateForward(url, navigationOptions); + + return navigationResult !== false; + } + + /** + * Navigate to the login credentials route. + * + * @param params Page params. + * @return Whether navigation suceeded. + */ + async navigateToLoginCredentials(params: Params = {}): Promise { + // If necessary, open the previous path to keep the navigation history. + if (!this.isCurrent('/login/site') && !this.isCurrent('/login/sites')) { + const hasSites = await CoreSites.instance.hasSites(); + + await this.navigate(hasSites ? '/login/sites' : '/login/site', { reset: true }); + } + + // Navigate to login credentials page. + return this.navigate('/login/credentials', { params }); + } + + /** + * Navigate to the home route of the current site. + * + * @param options Navigation options. + * @return Whether navigation suceeded. + */ + async navigateToSiteHome(options: Omit & { siteId?: string } = {}): Promise { + return this.navigateToSitePath(DEFAULT_MAIN_MENU_TAB, options); + } + + /** + * Navigate to a site path, loading the site if necessary. + * + * @param path Site path to visit. + * @param options Navigation and site options. + * @return Whether navigation suceeded. + */ + async navigateToSitePath( + path: string, + options: Omit & { siteId?: string } = {}, + ): Promise { + const siteId = options.siteId ?? CoreSites.instance.getCurrentSiteId(); + const navigationOptions: CoreNavigationOptions = CoreObject.without(options, ['siteId']); + + // @todo: When this function was in ContentLinksHelper, this code was inside NgZone. Check if it's needed. + + // If the path doesn't belong to a site, call standard navigation. + if (siteId === CoreConstants.NO_SITE_ID) { + return this.navigate(path, { + ...navigationOptions, + reset: true, + }); + } + + // If we are logged into a different site, log out first. + if (CoreSites.instance.isLoggedIn() && CoreSites.instance.getCurrentSiteId() !== siteId) { + // @todo: Check site plugins and store redirect. + + await CoreSites.instance.logout(); + } + + // If we are not logged into the site, load the site. + if (!CoreSites.instance.isLoggedIn()) { + const modal = await CoreDomUtils.instance.showModalLoading(); + + try { + const loggedIn = await CoreSites.instance.loadSite(siteId, path, options.params); + + if (!loggedIn) { + // User has been redirected to the login page and will be redirected to the site path after login. + return true; + } + } catch (error) { + // Site doesn't exist. + return this.navigate('/login/sites', { reset: true }); + } finally { + modal.dismiss(); + } + } + + // User is logged in, navigate to the site path. + return this.navigateToMainMenuPath(path, navigationOptions); + } + + /** + * Get the active route path. + * + * @return Current path. + */ + protected getCurrentPath(): string { + return CoreUrlUtils.instance.removeUrlParams(Router.instance.url); + } + + /** + * Get current activated route. + * + * @param route Parent route. + * @return Current activated route. + */ + protected getCurrentRoute(route?: ActivatedRoute): ActivatedRoute { + route = route ?? Router.instance.routerState.root; + + return route.children.length === 0 ? route : this.getCurrentRoute(route.children[0]); + } + + /** + * Navigate to a path within the main menu. + * If the path belongs to a visible tab, that tab will be selected. + * If it doesn't, the current tab or the default tab will be used instead. + * + * @param options Navigation options. + * @return Whether navigation suceeded. + */ + protected async navigateToMainMenuPath(path: string, options: Omit = {}): Promise { + // Due to DeepLinker, we need to remove the path from the URL before going to main menu. + // IonTabs checks the URL to determine which path to load for deep linking, so we clear the URL. + // @todo this.location.replaceState(''); + + path = path.replace(/^(\.|\/main)?\//, ''); + + // Open the path within the corresponding main tab. + const pathRoot = /^[^/]+/.exec(path)?.[0] ?? ''; + const isCurrentMainMenuHandler = await CoreUtils.instance.ignoreErrors( + CoreMainMenu.instance.isCurrentMainMenuHandler(pathRoot), + false, + ); + + if (isCurrentMainMenuHandler) { + return this.navigate(`/main/${path}`, options); + } + + // Open the path within the current main tab. + const currentMainMenuTab = this.getCurrentMainMenuTab(); + + if (currentMainMenuTab) { + return this.navigate(`/main/${currentMainMenuTab}/${path}`, options); + } + + // Open the path within the default main tab. + // @todo test that this is working as expected + return this.navigate(`/main/${DEFAULT_MAIN_MENU_TAB}`, { + ...options, + params: { + redirectPath: `/main/${DEFAULT_MAIN_MENU_TAB}/${path}`, + redirectParams: options.params, + } as CoreRedirectPayload, + }); + } + +} + +export class CoreNavigator extends makeSingleton(CoreNavigatorService) {} diff --git a/src/core/services/tests/navigator.test.ts b/src/core/services/tests/navigator.test.ts new file mode 100644 index 000000000..0d80c2270 --- /dev/null +++ b/src/core/services/tests/navigator.test.ts @@ -0,0 +1,154 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed 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 { NavController as NavControllerService } from '@ionic/angular'; + +import { mock, mockSingleton } from '@/testing/utils'; + +import { CoreNavigatorService } from '@services/navigator'; +import { CoreUtils, CoreUtilsProvider } from '@services/utils/utils'; +import { CoreUrlUtils, CoreUrlUtilsProvider } from '@services/utils/url'; +import { NavController, Router } from '@singletons'; +import { ActivatedRoute, RouterState } from '@angular/router'; +import { CoreSites } from '@services/sites'; +import { CoreMainMenu } from '@features/mainmenu/services/mainmenu'; + +describe('CoreNavigator', () => { + + let router: { + url?: string; + routerState?: Partial; + }; + let currentMainMenuHandlers: string[]; + let navigator: CoreNavigatorService; + let navControllerMock: NavControllerService; + + beforeEach(() => { + router = { url: '/' }; + currentMainMenuHandlers = ['home']; + navigator = new CoreNavigatorService(); + navControllerMock = mockSingleton(NavController, ['navigateRoot', 'navigateForward']); + + mockSingleton(Router, router); + mockSingleton(CoreUtils, new CoreUtilsProvider(mock())); + mockSingleton(CoreUrlUtils, new CoreUrlUtilsProvider()); + mockSingleton(CoreSites, { getCurrentSiteId: () => 42, isLoggedIn: () => true }); + mockSingleton(CoreMainMenu, { isCurrentMainMenuHandler: path => Promise.resolve(currentMainMenuHandlers.includes(path)) }); + }); + + it('matches against current path', () => { + router.url = '/main/foo'; + + expect(navigator.isCurrent('/main/foo')).toBe(true); + expect(navigator.isCurrent('/main')).toBe(false); + }); + + it('gets the current main menu tab', () => { + expect(navigator.getCurrentMainMenuTab()).toBeNull(); + + router.url = '/main/foo'; + expect(navigator.getCurrentMainMenuTab()).toBe('foo'); + + router.url = '/main/foo/bar'; + expect(navigator.getCurrentMainMenuTab()).toBe('foo'); + }); + + it('navigates to absolute paths', async () => { + const success = await navigator.navigate('/main/foo/bar', { reset: true }); + + expect(success).toBe(true); + expect(navControllerMock.navigateRoot).toHaveBeenCalledWith(['/main/foo/bar'], {}); + }); + + it('navigates to relative paths', async () => { + // Arrange. + const mainOutletRoute = { routeConfig: { path: 'foo' }, children: [] }; + const primaryOutletRoute = { routeConfig: { path: 'main' }, children: [mainOutletRoute] }; + const rootRoute = { children: [primaryOutletRoute] }; + + router.routerState = { root: rootRoute as unknown as ActivatedRoute }; + + // Act. + const success = await navigator.navigate('./bar'); + + // Assert. + expect(success).toBe(true); + expect(navControllerMock.navigateForward).toHaveBeenCalledWith(['./bar'], { relativeTo: mainOutletRoute }); + }); + + it('navigates to site paths', async () => { + // Arrange + router.url = '/main/foo'; + + // Act + const success = await navigator.navigateToSitePath('/user/42'); + + // Assert + expect(success).toBe(true); + expect(navControllerMock.navigateForward).toHaveBeenCalledWith(['/main/foo/user/42'], {}); + }); + + it('navigates to site paths using tabs', async () => { + // Arrange + currentMainMenuHandlers.push('users'); + + // Act + const success = await navigator.navigateToSitePath('/users/user/42'); + + // Assert + expect(success).toBe(true); + expect(navControllerMock.navigateForward).toHaveBeenCalledWith(['/main/users/user/42'], {}); + }); + + it('navigates to site paths using the default tab', async () => { + const success = await navigator.navigateToSitePath('/user/42'); + + expect(success).toBe(true); + expect(navControllerMock.navigateForward).toHaveBeenCalledWith(['/main/home'], { + queryParams: { + redirectPath: '/main/home/user/42', + }, + }); + }); + + it('navigates to site paths ussing different path formats', async () => { + currentMainMenuHandlers.push('users'); + + const assertNavigation = async (currentPath, sitePath, expectedPath) => { + router.url = currentPath; + + const success = await navigator.navigateToSitePath(sitePath); + + expect(success).toBe(true); + expect(navControllerMock.navigateForward).toHaveBeenCalledWith([expectedPath], {}); + }; + + await assertNavigation('/main/users', '/main/users/user/42', '/main/users/user/42'); + await assertNavigation('/main/users', '/users/user/42', '/main/users/user/42'); + await assertNavigation('/main/users', '/user/42', '/main/users/user/42'); + await assertNavigation('/main/home', '/users/user/42', '/main/users/user/42'); + }); + + it('navigates to site home', async () => { + const success = await navigator.navigateToSiteHome(); + + expect(success).toBe(true); + expect(navControllerMock.navigateForward).toHaveBeenCalledWith(['/main/home'], {}); + }); + + it.todo('navigates to a different site'); + it.todo('navigates to login credentials'); + it.todo('navigates to NO_SITE_ID site'); + +}); diff --git a/src/core/services/utils/utils.ts b/src/core/services/utils/utils.ts index ba4861dad..16342150d 100644 --- a/src/core/services/utils/utils.ts +++ b/src/core/services/utils/utils.ts @@ -1580,15 +1580,19 @@ export class CoreUtilsProvider { * Ignore errors from a promise. * * @param promise Promise to ignore errors. - * @return Promise with ignored errors. + * @param fallbackResult Value to return if the promise is rejected. + * @return Promise with ignored errors, resolving to the fallback result if provided. */ - async ignoreErrors(promise: Promise): Promise { + async ignoreErrors(promise: Promise): Promise; + async ignoreErrors(promise: Promise, fallback: Fallback): Promise; + async ignoreErrors(promise: Promise, fallback?: Fallback): Promise { try { const result = await promise; return result; } catch (error) { // Ignore errors. + return fallback; } } diff --git a/src/core/singletons/events.ts b/src/core/singletons/events.ts index 122d0426a..745e3d058 100644 --- a/src/core/singletons/events.ts +++ b/src/core/singletons/events.ts @@ -61,9 +61,7 @@ export class CoreEvents { static readonly KEYBOARD_CHANGE = 'keyboard_change'; static readonly CORE_LOADING_CHANGED = 'core_loading_changed'; static readonly ORIENTATION_CHANGE = 'orientation_change'; - static readonly LOAD_PAGE_MAIN_MENU = 'load_page_main_menu'; static readonly SEND_ON_ENTER_CHANGED = 'send_on_enter_changed'; - static readonly MAIN_MENU_OPEN = 'main_menu_open'; static readonly SELECT_COURSE_TAB = 'select_course_tab'; static readonly WS_CACHE_INVALIDATED = 'ws_cache_invalidated'; static readonly SITE_STORAGE_DELETED = 'site_storage_deleted'; @@ -227,14 +225,6 @@ export type CoreEventLoadingChangedData = { uniqueId: string; }; -/** - * Data passed to LOAD_PAGE_MAIN_MENU event. - */ -export type CoreEventLoadPageMainMenuData = { - redirectPage: string; - redirectParams?: Params; -}; - /** * Data passed to COURSE_STATUS_CHANGED event. */ diff --git a/src/core/singletons/index.ts b/src/core/singletons/index.ts index c9a96adce..3353eab15 100644 --- a/src/core/singletons/index.ts +++ b/src/core/singletons/index.ts @@ -13,6 +13,7 @@ // limitations under the License. import { ApplicationRef, ApplicationInitStatus, Injector, NgZone as NgZoneService, Type } from '@angular/core'; +import { Router as RouterService } from '@angular/router'; import { HttpClient } from '@angular/common/http'; import { @@ -23,6 +24,7 @@ import { ToastController as ToastControllerService, GestureController as GestureControllerService, ActionSheetController as ActionSheetControllerService, + NavController as NavControllerService, } from '@ionic/angular'; import { Badge as BadgeService } from '@ionic-native/badge/ngx'; @@ -150,6 +152,8 @@ export class ToastController extends makeSingleton(ToastControllerService) {} export class GestureController extends makeSingleton(GestureControllerService) {} export class ApplicationInit extends makeSingleton(ApplicationInitStatus) {} export class Application extends makeSingleton(ApplicationRef) {} +export class NavController extends makeSingleton(NavControllerService) {} +export class Router extends makeSingleton(RouterService) {} // Convert external libraries injectables. export class Translate extends makeSingleton(TranslateService) {} diff --git a/src/core/singletons/object.ts b/src/core/singletons/object.ts index ac908aa09..589c8b834 100644 --- a/src/core/singletons/object.ts +++ b/src/core/singletons/object.ts @@ -12,24 +12,60 @@ // See the License for the specific language governing permissions and // limitations under the License. +export type CoreObjectWithoutEmpty = { + [k in keyof T]: T[k] extends undefined | null ? never : T[k]; +}; + /** * Singleton with helper functions for objects. */ export class CoreObject { /** - * Delete all keys from an object whose value are null or undefined. + * Check whether the given object is empty. * - * @param object Object to modify. + * @param object Object. + * @return Whether the given object is empty. */ - static removeUndefined(object: T): T { - for (const name in object) { - if (object[name] === undefined) { - delete object[name]; - } + static isEmpty(object: Record): boolean { + return Object.keys(object).length === 0; + } + + /** + * Create a new object without the specified keys. + * + * @param obj Object. + * @param keys Keys to remove from the new object. + * @return New object without the specified keys. + */ + static without(obj: T, keys: K[]): Omit { + const newObject: T = { ...obj }; + + for (const key of keys) { + delete newObject[key]; } - return object; + return newObject; + } + + /** + * Create a new object without empty values (null or undefined). + * + * @param obj Objet. + * @return New object without empty values. + */ + static withoutEmpty(obj: T): CoreObjectWithoutEmpty { + const cleanObj = {}; + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined) { + continue; + } + + cleanObj[key] = value; + } + + return cleanObj as CoreObjectWithoutEmpty; } } diff --git a/src/testing/utils.ts b/src/testing/utils.ts index 8bbfddc4e..ab2425723 100644 --- a/src/testing/utils.ts +++ b/src/testing/utils.ts @@ -47,22 +47,26 @@ export function mock( return instance as T; } -export function mockSingleton(singletonClass: CoreSingletonClass, instance?: Record): void; -export function mockSingleton( +export function mockSingleton(singletonClass: CoreSingletonClass, instance: T): T; +export function mockSingleton(singletonClass: CoreSingletonClass, instance?: Record): T; +export function mockSingleton( singletonClass: CoreSingletonClass, methods: string[], instance?: Record, -): void; -export function mockSingleton( - singletonClass: CoreSingletonClass, +): T; +export function mockSingleton( + singletonClass: CoreSingletonClass, methodsOrInstance: string[] | Record = [], instance: Record = {}, -): void { +): T { instance = Array.isArray(methodsOrInstance) ? instance : methodsOrInstance; const methods = Array.isArray(methodsOrInstance) ? methodsOrInstance : []; + const mockInstance = mock(methods, instance); - singletonClass.setInstance(mock(methods, instance)); + singletonClass.setInstance(mockInstance); + + return mockInstance; } export async function renderComponent(component: Type, config: Partial = {}): Promise> { diff --git a/src/types/angular.d.ts b/src/types/angular.d.ts new file mode 100644 index 000000000..ab158dc8c --- /dev/null +++ b/src/types/angular.d.ts @@ -0,0 +1,26 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed 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 { UrlTree } from '@angular/router'; +import { NavigationOptions } from '@ionic/angular/providers/nav-controller'; + +declare module '@ionic/angular' { + + export class NavController { + + navigateForward(url: string | UrlTree | any[], options?: NavigationOptions): Promise; + + } + +}