diff --git a/package-lock.json b/package-lock.json index e877887c..187f0796 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@aduh95/viz.js": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@aduh95/viz.js/-/viz.js-3.4.0.tgz", - "integrity": "sha512-KI2nVf9JdwWCXqK6RVf+9/096G7VWN4Z84mnynlyZKao2xQENW8WNEjLmvdlxS5X8PNWXFC1zqwm7tveOXw/4A==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@aduh95/viz.js/-/viz.js-3.5.0.tgz", + "integrity": "sha512-ahLdpRAoGsdgEfy2SGV2wnnHrBSLDHuwA32v+BoNGnz1gqajr8VMzF8y6mIQt28hHi4LQ272wqSi78DK4YdT2g==", "dev": true }, "@angular-devkit/architect": { @@ -535,6 +535,11 @@ } } }, + "@angular/animations": { + "version": "9.1.13", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-9.1.13.tgz", + "integrity": "sha512-ane1eeQmsP7fcAiLgRhle7YIDgE88WDMMvzqJYhSxwLzXNF/hwqNeskmNcjo8bLt9h/yTIjrCQbycLCHJfU8UQ==" + }, "@angular/cdk": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.0.0.tgz", @@ -545,9 +550,9 @@ }, "dependencies": { "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" } } }, @@ -833,9 +838,9 @@ } }, "@babel/compat-data": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz", - "integrity": "sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", + "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", "dev": true }, "@babel/core": { @@ -892,9 +897,9 @@ } }, "@babel/eslint-parser": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.14.7.tgz", - "integrity": "sha512-6WPwZqO5priAGIwV6msJcdc9TsEPzYeYdS/Xuoap+/ihkgN6dzHp2bcAAwyWZ5bLzk0vvjDmKvRwkqNaiJ8BiQ==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.15.0.tgz", + "integrity": "sha512-+gSPtjSBxOZz4Uh8Ggqu7HbfpB8cT1LwW0DnVVLZEJvzXauiD0Di3zszcBkRmfGGrLdYeHUwcflG7i3tr9kQlw==", "dev": true, "requires": { "eslint-scope": "^5.1.1", @@ -960,12 +965,12 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz", - "integrity": "sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.0.tgz", + "integrity": "sha512-h+/9t0ncd4jfZ8wsdAsoIxSa61qhBYlycXiHWqJaQBCXAhDCMbPRSMTGnZIkkmt1u4ag+UQmuqcILwqKzZ4N2A==", "dev": true, "requires": { - "@babel/compat-data": "^7.14.5", + "@babel/compat-data": "^7.15.0", "@babel/helper-validator-option": "^7.14.5", "browserslist": "^4.16.6", "semver": "^6.3.0" @@ -1041,12 +1046,12 @@ } }, "@babel/helper-member-expression-to-functions": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz", - "integrity": "sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.0.tgz", + "integrity": "sha512-Jq8H8U2kYiafuj2xMTPQwkTBnEEdGKpT35lJEQsRRjnG0LW3neucsaMWLgKcwu3OHKNeYugfw+Z20BXBSEs2Lg==", "dev": true, "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.15.0" } }, "@babel/helper-module-imports": { @@ -1059,19 +1064,19 @@ } }, "@babel/helper-module-transforms": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", - "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.0.tgz", + "integrity": "sha512-RkGiW5Rer7fpXv9m1B3iHIFDZdItnO2/BLfWVW/9q7+KqQSDY5kUfQEbzdXM1MVhJGcugKV7kRrNVzNxmk7NBg==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-replace-supers": "^7.14.5", - "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-replace-supers": "^7.15.0", + "@babel/helper-simple-access": "^7.14.8", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.9", "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/traverse": "^7.15.0", + "@babel/types": "^7.15.0" }, "dependencies": { "@babel/template": { @@ -1114,24 +1119,24 @@ } }, "@babel/helper-replace-supers": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", - "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.0.tgz", + "integrity": "sha512-6O+eWrhx+HEra/uJnifCwhwMd6Bp5+ZfZeJwbqUTuqkhIT6YcRhiZCOOFChRypOIe0cV46kFrRBlm+t5vHCEaA==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-member-expression-to-functions": "^7.15.0", "@babel/helper-optimise-call-expression": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/traverse": "^7.15.0", + "@babel/types": "^7.15.0" } }, "@babel/helper-simple-access": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz", - "integrity": "sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz", + "integrity": "sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg==", "dev": true, "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.14.8" } }, "@babel/helper-skip-transparent-expression-wrappers": { @@ -1153,9 +1158,9 @@ } }, "@babel/helper-validator-identifier": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", - "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==" + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", + "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==" }, "@babel/helper-validator-option": { "version": "7.14.5", @@ -1189,14 +1194,14 @@ } }, "@babel/helpers": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.6.tgz", - "integrity": "sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA==", + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.3.tgz", + "integrity": "sha512-HwJiz52XaS96lX+28Tnbu31VeFSQJGOeKHJeaEPQlTl7PnlhFElWPj8tUXtqFIzeN86XxXoBr+WFAyK2PPVz6g==", "dev": true, "requires": { "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/traverse": "^7.15.0", + "@babel/types": "^7.15.0" }, "dependencies": { "@babel/template": { @@ -1269,15 +1274,15 @@ } }, "@babel/parser": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz", - "integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==", + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.3.tgz", + "integrity": "sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.7.tgz", - "integrity": "sha512-RK8Wj7lXLY3bqei69/cc25gwS5puEc3dknoFPFbqfy3XxYQBQFvu4ioWpafMBAB+L9NyptQK4nMOa5Xz16og8Q==", + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.9.tgz", + "integrity": "sha512-d1lnh+ZnKrFKwtTYdw320+sQWCTwgkB9fmUhNXRADA4akR6wLjaruSGnIEUjpt9HCOwTr4ynFTKu19b7rFRpmw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.14.5", @@ -1480,18 +1485,18 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.14.5.tgz", - "integrity": "sha512-LBYm4ZocNgoCqyxMLoOnwpsmQ18HWTQvql64t3GvMUzLQrNoV1BDG0lNftC8QKYERkZgCCT/7J5xWGObGAyHDw==", + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.15.3.tgz", + "integrity": "sha512-nBAzfZwZb4DkaGtOes1Up1nOAp9TDRRFw4XBzBBSG9QK7KVFmYzgj9o9sbPv7TX5ofL4Auq4wZnxCoPnI/lz2Q==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.14.5" } }, "@babel/plugin-transform-classes": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.5.tgz", - "integrity": "sha512-J4VxKAMykM06K/64z9rwiL6xnBHgB1+FVspqvlgCdwD1KUbQNfszeKVVOMh59w3sztHYIZDgnhOC4WbdEfHFDA==", + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.9.tgz", + "integrity": "sha512-NfZpTcxU3foGWbl4wxmZ35mTsYJy8oQocbeIMoDAGGFarAmSQlL+LWMkDx/tj6pNotpbX3rltIA4dprgAPOq5A==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.14.5", @@ -1599,14 +1604,14 @@ } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.5.tgz", - "integrity": "sha512-en8GfBtgnydoao2PS+87mKyw62k02k7kJ9ltbKe0fXTHrQmG6QZZflYuGI1VVG7sVpx4E1n7KBpNlPb8m78J+A==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.15.0.tgz", + "integrity": "sha512-3H/R9s8cXcOGE8kgMlmjYYC9nqr5ELiPkJn4q0mypBrjhYQoc+5/Maq69vV4xRPWnkzZuwJPf5rArxpB/35Cig==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.14.5", + "@babel/helper-module-transforms": "^7.15.0", "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-simple-access": "^7.14.8", "babel-plugin-dynamic-import-node": "^2.3.3" } }, @@ -1634,9 +1639,9 @@ } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.7.tgz", - "integrity": "sha512-DTNOTaS7TkW97xsDMrp7nycUVh6sn/eq22VaxWfEdzuEbRsiaOU0pqU7DlyUGHVsbQbSghvjKRpEl+nUCKGQSg==", + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.9.tgz", + "integrity": "sha512-l666wCVYO75mlAtGFfyFwnWmIXQm3kSH0C3IRnJqWcZbWkoihyAdDhFm2ZWaxWTqvBvhVFfJjMRQ0ez4oN1yYA==", "dev": true, "requires": { "@babel/helper-create-regexp-features-plugin": "^7.14.5" @@ -1851,20 +1856,20 @@ } }, "@babel/runtime": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", - "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz", + "integrity": "sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==", "requires": { "regenerator-runtime": "^0.13.4" } }, "@babel/runtime-corejs3": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.14.7.tgz", - "integrity": "sha512-Wvzcw4mBYbTagyBVZpAJWI06auSIj033T/yNE0Zn1xcup83MieCddZA7ls3kme17L4NOGBrQ09Q+nKB41RLWBA==", + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.15.3.tgz", + "integrity": "sha512-30A3lP+sRL6ml8uhoJSs+8jwpKzbw8CqBvDc1laeptxPm5FahumJxirigcbD2qTs71Sonvj1cyZB0OKGAmxQ+A==", "dev": true, "requires": { - "core-js-pure": "^3.15.0", + "core-js-pure": "^3.16.0", "regenerator-runtime": "^0.13.4" } }, @@ -1880,29 +1885,29 @@ } }, "@babel/traverse": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz", - "integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.0.tgz", + "integrity": "sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==", "dev": true, "requires": { "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", + "@babel/generator": "^7.15.0", "@babel/helper-function-name": "^7.14.5", "@babel/helper-hoist-variables": "^7.14.5", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.7", - "@babel/types": "^7.14.5", + "@babel/parser": "^7.15.0", + "@babel/types": "^7.15.0", "debug": "^4.1.0", "globals": "^11.1.0" }, "dependencies": { "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.0.tgz", + "integrity": "sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==", "dev": true, "requires": { - "@babel/types": "^7.14.5", + "@babel/types": "^7.15.0", "jsesc": "^2.5.1", "source-map": "^0.5.0" } @@ -1931,12 +1936,12 @@ } }, "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", + "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.9", "to-fast-properties": "^2.0.0" } }, @@ -2142,12 +2147,20 @@ "comment-parser": "^1.1.5", "esquery": "^1.4.0", "jsdoc-type-pratt-parser": "1.0.4" + }, + "dependencies": { + "jsdoc-type-pratt-parser": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-1.0.4.tgz", + "integrity": "sha512-jzmW9gokeq9+bHPDR1nCeidMyFUikdZlbOhKzh9+/nJqB75XhpNKec1/UuxW5c4+O+Pi31Gc/dCboyfSm/pSpQ==", + "dev": true + } } }, "@eslint/eslintrc": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz", - "integrity": "sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -2171,9 +2184,9 @@ } }, "globals": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", - "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==", + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", + "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -2282,13 +2295,14 @@ "integrity": "sha512-1DRub6i8hNhHYy5HWlun0CjhmE1cwZZWyLB9ptkwLkZdfFEazwj0eKjz3BmpCs6wy593smNn/x9G0lCwvLCZlA==", "requires": { "@types/cordova": "^0.0.34" - }, - "dependencies": { - "@types/cordova": { - "version": "0.0.34", - "resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz", - "integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ=" - } + } + }, + "@ionic-native/dialogs": { + "version": "5.31.1", + "resolved": "https://registry.npmjs.org/@ionic-native/dialogs/-/dialogs-5.31.1.tgz", + "integrity": "sha512-17yONTph8ZAE6zSwdBSFOd53LznHiUlXKMy+Yy+Wj2g29LXUm0SMJuEYCvnvjWlo3msABKngh7WiKWRWakkF2A==", + "requires": { + "@types/cordova": "^0.0.34" } }, "@ionic-native/geolocation": { @@ -2454,9 +2468,9 @@ } }, "ws": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.2.tgz", - "integrity": "sha512-lkF7AWRicoB9mAgjeKbGqVUekLnSNO4VjKVnuPHpQeOxZOErX6BPXwJk70nFslRCEEA8EVW7ZjKwXaP9N+1sKQ==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", + "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", "dev": true } } @@ -2472,9 +2486,9 @@ }, "dependencies": { "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" } } }, @@ -2639,9 +2653,9 @@ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" }, "@nodelib/fs.walk": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz", - "integrity": "sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "requires": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -3083,26 +3097,26 @@ "integrity": "sha512-QsxWayZyusnqSZrlCl81R71rA3KqFjVVQSH4E0rGN15F1GdQaFonKlHLyCOLKLig1zzC+DQkLLiUuocexuvdeQ==" }, "@szmarczak/http-timer": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", - "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", "requires": { "defer-to-connect": "^2.0.0" } }, "@types/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", "requires": { "@types/connect": "*", "@types/node": "*" } }, "@types/cacheable-request": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", - "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", + "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", "requires": { "@types/http-cache-semantics": "*", "@types/keyv": "*", @@ -3119,9 +3133,9 @@ } }, "@types/connect": { - "version": "3.4.34", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", - "integrity": "sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==", + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", "requires": { "@types/node": "*" } @@ -3152,9 +3166,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.22", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.22.tgz", - "integrity": "sha512-WdqmrUsRS4ootGha6tVwk/IVHM1iorU8tGehftQD2NWiPniw/sm7xdJOIlXLwqdInL9wBw/p7oO8vaYEF3NDmA==", + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", + "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", "requires": { "@types/node": "*", "@types/qs": "*", @@ -3195,9 +3209,9 @@ } }, "@types/http-cache-semantics": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", - "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" }, "@types/jasmine": { "version": "3.3.12", @@ -3225,9 +3239,9 @@ "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==" }, "@types/keyv": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", - "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.2.tgz", + "integrity": "sha512-/FvAK2p4jQOaJ6CGDHJTqZcUtbZe820qIeTg7o0Shg7drB4JHeL+V/dhSaly7NXx6u8eSee+r7coT+yuJEvDLg==", "requires": { "@types/node": "*" } @@ -3251,9 +3265,9 @@ } }, "@types/lodash": { - "version": "4.14.170", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.170.tgz", - "integrity": "sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q==", + "version": "4.14.172", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.172.tgz", + "integrity": "sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw==", "dev": true }, "@types/lodash-es": { @@ -3276,14 +3290,14 @@ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "@types/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==" + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==" }, "@types/minimist": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", - "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, "@types/morgan": { @@ -3313,26 +3327,26 @@ } }, "@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, "@types/q": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", - "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", + "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==", "dev": true }, "@types/qs": { - "version": "6.9.6", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz", - "integrity": "sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==" + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" }, "@types/range-parser": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", - "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "@types/responselike": { "version": "1.0.0", @@ -3343,9 +3357,9 @@ } }, "@types/selenium-webdriver": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.17.tgz", - "integrity": "sha512-tGomyEuzSC1H28y2zlW6XPCaDaXFaD6soTdb4GNdmte2qfHtrKqhy0ZFs4r/1hpazCfEZqeTSRLvSasmEx89uw==", + "version": "3.0.19", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.19.tgz", + "integrity": "sha512-OFUilxQg+rWL2FMxtmIgCkUDlJB6pskkpvmew7yeXfzzsOBb5rc+y2+DjHm+r3r1ZPPcJefK3DveNSYWGiy68g==", "dev": true }, "@types/semver": { @@ -3357,9 +3371,9 @@ } }, "@types/serve-static": { - "version": "1.13.9", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz", - "integrity": "sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA==", + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", "requires": { "@types/mime": "^1", "@types/node": "*" @@ -3984,9 +3998,9 @@ "dev": true }, "acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true }, "acorn-node": { @@ -4826,9 +4840,9 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "core-js": { - "version": "3.15.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.15.2.tgz", - "integrity": "sha512-tKs41J7NJVuaya8DxIOCnl8QuPHx5/ZVbFo1oKgVl1qHFBBrDctzQGtuLjPpRdNTWmKPH6oEvgN/MUID+l485Q==" + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.1.tgz", + "integrity": "sha512-AAkP8i35EbefU+JddyWi12AWE9f2N/qr/pwnDtWz4nyUIBGMJPX99ANFFRSw6FefM374lDujdtLDyhN2A/btHw==" }, "has-flag": { "version": "3.0.0", @@ -5133,16 +5147,16 @@ } }, "browserslist": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", - "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", + "version": "4.16.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.7.tgz", + "integrity": "sha512-7I4qVwqZltJ7j37wObBe3SoTz+nS8APaNcrBOlgoirb6/HbEU2XxW/LpUDTCngM6iauwFqmRTuOMfyKnFGY5JA==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001219", + "caniuse-lite": "^1.0.30001248", "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.723", + "electron-to-chromium": "^1.3.793", "escalade": "^3.1.1", - "node-releases": "^1.1.71" + "node-releases": "^1.1.73" } }, "browserstack": { @@ -5202,9 +5216,9 @@ "dev": true }, "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "buffer-indexof": { "version": "1.1.1", @@ -5421,9 +5435,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001242", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001242.tgz", - "integrity": "sha512-KvNuZ/duufelMB3w2xtf9gEWCSxJwUgoxOx5b6ScLXC4kPc9xsczUVCPrQU26j5kOsHM4pSUL54tAZt5THQKug==", + "version": "1.0.30001251", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001251.tgz", + "integrity": "sha512-HOe1r+9VkU4TFmnU70z+r7OLmtR+/chB1rdcJUeQlAinjEeb0cKL20tlAtOagNZhbrtLnCvV19B4FmF1rgzl6A==", "dev": true }, "canonical-path": { @@ -5510,9 +5524,9 @@ "dev": true }, "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", "dev": true } } @@ -5979,13 +5993,13 @@ } }, "color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.3.tgz", - "integrity": "sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", "dev": true, "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.4" + "color-convert": "^1.9.3", + "color-string": "^1.6.0" }, "dependencies": { "color-convert": { @@ -6019,9 +6033,9 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "color-string": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.5.tgz", - "integrity": "sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz", + "integrity": "sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==", "dev": true, "requires": { "color-name": "^1.0.0", @@ -6035,9 +6049,9 @@ "dev": true }, "colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.3.0.tgz", + "integrity": "sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w==", "dev": true }, "colors": { @@ -7174,6 +7188,11 @@ "resolved": "https://registry.npmjs.org/cordova-plugin-device/-/cordova-plugin-device-2.0.3.tgz", "integrity": "sha512-Jb3V72btxf3XHpkPQsGdyc8N6tVBYn1vsxSFj43fIz9vonJDUThYPCJJHqk6PX6N4dJw6I4FjxkpfCR4LDYMlw==" }, + "cordova-plugin-dialogs": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/cordova-plugin-dialogs/-/cordova-plugin-dialogs-2.0.2.tgz", + "integrity": "sha512-FUHI6eEVeoz2VkxbF0P56QlUQLGzXcvw3i4xuXyM9gEct6Y+FA3Xzgl2pJTZcTg5wRqLWzN08kgNoHPkom15pw==" + }, "cordova-plugin-geolocation": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cordova-plugin-geolocation/-/cordova-plugin-geolocation-4.1.0.tgz", @@ -7293,12 +7312,12 @@ "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==" }, "core-js-compat": { - "version": "3.15.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.15.2.tgz", - "integrity": "sha512-Wp+BJVvwopjI+A1EFqm2dwUmWYXrvucmtIB2LgXn/Rb+gWPKYxtmb4GKHGKG/KGF1eK9jfjzT38DITbTOCX/SQ==", + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.16.1.tgz", + "integrity": "sha512-NHXQXvRbd4nxp9TEmooTJLUf94ySUG6+DSsscBpTftN1lQLQ4LjnWvc7AoIo4UjDsFF3hB8Uh5LLCRRdaiT5MQ==", "dev": true, "requires": { - "browserslist": "^4.16.6", + "browserslist": "^4.16.7", "semver": "7.0.0" }, "dependencies": { @@ -7311,9 +7330,9 @@ } }, "core-js-pure": { - "version": "3.15.2", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.15.2.tgz", - "integrity": "sha512-D42L7RYh1J2grW8ttxoY1+17Y4wXZeKe7uyplAI3FkNQyI5OgBIAjUfFiTPfL1rs0qLpxaabITNbjKl1Sp82tA==", + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.16.1.tgz", + "integrity": "sha512-TyofCdMzx0KMhi84mVRS8rL1XsRk2SPUNz2azmth53iRN0/08Uim9fdhQTaZTG1LqaXHYVci4RDHka6WrXfnvg==", "dev": true }, "core-util-is": { @@ -7424,9 +7443,9 @@ } }, "crypto-js": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", - "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==", "dev": true }, "cson-parser": { @@ -8322,9 +8341,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.768", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.768.tgz", - "integrity": "sha512-I4UMZHhVSK2pwt8jOIxTi3GIuc41NkddtKT/hpuxp9GO5UWJgDKTBa4TACppbVAuKtKbMK6BhQZvT5tFF1bcNA==", + "version": "1.3.803", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.803.tgz", + "integrity": "sha512-tmRK9qB8Zs8eLMtTBp+w2zVS9MUe62gQQQHjYdAc5Zljam3ZIokNb+vZLPRz9RCREp6EFRwyhOFwbt1fEriQ2Q==", "dev": true }, "elementtree": { @@ -8584,9 +8603,9 @@ } }, "es-abstract": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", - "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", + "version": "1.18.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.5.tgz", + "integrity": "sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA==", "dev": true, "requires": { "call-bind": "^1.0.2", @@ -8595,11 +8614,12 @@ "get-intrinsic": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", "is-callable": "^1.2.3", "is-negative-zero": "^2.0.1", "is-regex": "^1.1.3", "is-string": "^1.0.6", - "object-inspect": "^1.10.3", + "object-inspect": "^1.11.0", "object-keys": "^1.1.1", "object.assign": "^4.1.2", "string.prototype.trimend": "^1.0.4", @@ -8839,9 +8859,9 @@ "dev": true }, "globals": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", - "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==", + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", + "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -9145,20 +9165,20 @@ }, "dependencies": { "@babel/core": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.6.tgz", - "integrity": "sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.0.tgz", + "integrity": "sha512-tXtmTminrze5HEUPn/a0JtOzzfp0nk+UEXQ/tqIJo3WDGypl/2OFQEMll/zSFU8f/lfmfLXvTaORHF3cfXIQMw==", "dev": true, "requires": { "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", - "@babel/helper-compilation-targets": "^7.14.5", - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helpers": "^7.14.6", - "@babel/parser": "^7.14.6", + "@babel/generator": "^7.15.0", + "@babel/helper-compilation-targets": "^7.15.0", + "@babel/helper-module-transforms": "^7.15.0", + "@babel/helpers": "^7.14.8", + "@babel/parser": "^7.15.0", "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5", + "@babel/traverse": "^7.15.0", + "@babel/types": "^7.15.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -9168,12 +9188,12 @@ } }, "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.0.tgz", + "integrity": "sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==", "dev": true, "requires": { - "@babel/types": "^7.14.5", + "@babel/types": "^7.15.0", "jsesc": "^2.5.1", "source-map": "^0.5.0" } @@ -9637,9 +9657,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.6.tgz", - "integrity": "sha512-GnLuqj/pvQ7pX8/L4J84nijv6sAnlwvSDpMkJi9i7nPmPxGtRPkBSStfvDW5l6nMdX9VWe+pkKWFTgD+vF2QSQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -9850,9 +9870,9 @@ }, "dependencies": { "flatted": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.0.tgz", - "integrity": "sha512-XprP7lDrVT+kE2c2YlfiV+IfS9zxukiIOvNamPNsImNhXadSsQEbosItdL9bUQlCZXR13SvPk20BjWSWLA7m4A==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", + "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", "dev": true } } @@ -10792,9 +10812,9 @@ } }, "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" }, "grapheme-splitter": { "version": "1.0.4", @@ -10906,6 +10926,15 @@ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", "dev": true }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -11592,6 +11621,17 @@ "ipaddr.js": "^1.9.0" } }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, "interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", @@ -11607,9 +11647,9 @@ } }, "ionicons": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-5.5.2.tgz", - "integrity": "sha512-SHVBBtzNVQjY4jtcqKEHkqL5nIQwA/o2MIdU9JtMz8kQAB0NRVJFv5AxwmVbXXKDpxz57SiEjeLp8Uzt7jirpw==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-5.5.3.tgz", + "integrity": "sha512-L71djrMi8pAad66tpwdnO1vwcyluCFvehzxU1PpH1k/HpYBZhZ5IaYhqXipmqUvu5aEbd4cbRguYyI5Fd4bxTw==", "requires": { "@stencil/core": "^2.5.0" } @@ -11694,12 +11734,13 @@ } }, "is-arguments": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", - "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", "dev": true, "requires": { - "call-bind": "^1.0.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" } }, "is-arrayish": { @@ -11709,10 +11750,13 @@ "dev": true }, "is-bigint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", - "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", - "dev": true + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } }, "is-binary-path": { "version": "2.1.0", @@ -11724,12 +11768,13 @@ } }, "is-boolean-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", - "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, "requires": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" } }, "is-buffer": { @@ -11756,9 +11801,9 @@ } }, "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", "dev": true }, "is-color-stop": { @@ -11776,9 +11821,9 @@ } }, "is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz", + "integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==", "requires": { "has": "^1.0.3" } @@ -11804,10 +11849,13 @@ } }, "is-date-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", - "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", - "dev": true + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } }, "is-descriptor": { "version": "0.1.6", @@ -11894,10 +11942,13 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-number-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", - "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", - "dev": true + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } }, "is-obj": { "version": "2.0.0", @@ -11951,13 +12002,13 @@ } }, "is-regex": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", - "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, "requires": { "call-bind": "^1.0.2", - "has-symbols": "^1.0.2" + "has-tostringtag": "^1.0.0" } }, "is-relative": { @@ -11976,15 +12027,18 @@ "dev": true }, "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" }, "is-string": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", - "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==", - "dev": true + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } }, "is-symbol": { "version": "1.0.4", @@ -12293,9 +12347,9 @@ } }, "joi": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.4.0.tgz", - "integrity": "sha512-F4WiW2xaV6wc1jxete70Rw4V/VuMd6IN+a5ilZsxG4uYtUXWu2kq9W5P2dz30e7Gmw8RCbY/u/uk+dMPma9tAg==", + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.4.2.tgz", + "integrity": "sha512-Lm56PP+n0+Z2A2rfRvsfWVDXGEWjXxatPopkQ8qQ5mxCEhwHG+Ettgg5o98FFaxilOxozoa14cFhrE/hOzh/Nw==", "requires": { "@hapi/hoek": "^9.0.0", "@hapi/topo": "^5.0.0", @@ -12325,9 +12379,9 @@ "dev": true }, "jsdoc-type-pratt-parser": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-1.0.4.tgz", - "integrity": "sha512-jzmW9gokeq9+bHPDR1nCeidMyFUikdZlbOhKzh9+/nJqB75XhpNKec1/UuxW5c4+O+Pi31Gc/dCboyfSm/pSpQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-1.1.1.tgz", + "integrity": "sha512-uelRmpghNwPBuZScwgBG/OzodaFk5RbO5xaivBdsAY70icWfShwZ7PCMO0x1zSkOa8T1FzHThmrdoyg/0AwV5g==", "dev": true }, "jsesc": { @@ -12464,9 +12518,9 @@ } }, "jszip": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.6.0.tgz", - "integrity": "sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", + "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", "dev": true, "requires": { "lie": "~3.3.0", @@ -13707,16 +13761,16 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", - "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==" + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", + "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==" }, "mime-types": { - "version": "2.1.31", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", - "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", + "version": "2.1.32", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", + "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", "requires": { - "mime-db": "1.48.0" + "mime-db": "1.49.0" } }, "mimic-fn": { @@ -14126,9 +14180,9 @@ }, "dependencies": { "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" } } }, @@ -14194,9 +14248,9 @@ } }, "node-releases": { - "version": "1.1.73", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", - "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==", + "version": "1.1.74", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.74.tgz", + "integrity": "sha512-caJBVempXZPepZoZAPCWRTNxYQ+xtG/KAi4ozTA5A+nJ7IU+kLQCbqaUjb5Rwy14M9upBWiQ4NutcmW04LJSRw==", "dev": true }, "nodemailer": { @@ -14448,9 +14502,9 @@ } }, "object-inspect": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", - "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", "dev": true }, "object-is": { @@ -14939,18 +14993,26 @@ } }, "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "version": "4.4.17", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.17.tgz", + "integrity": "sha512-q7OwXq6NTdcYIa+k58nEMV3j1euhDhGCs/VRw9ymx/PbH0jtIM2+VTgDE/BW3rbLkrBUXs5fzEKgic5oUciu7g==", "dev": true, "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" + "chownr": "^1.1.4", + "fs-minipass": "^1.2.7", + "minipass": "^2.9.0", + "minizlib": "^1.3.3", + "mkdirp": "^0.5.5", + "safe-buffer": "^5.2.1", + "yallist": "^3.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, "which": { @@ -15162,12 +15224,12 @@ } }, "pdfkit": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.12.1.tgz", - "integrity": "sha512-ruNLx49hVW3ePJziKjHtWdTHN1VZHLCUCcbui/vx4lYwFLEM1d8W0L7ObYPbN8EifK7s281ZMugCLgSbk+KRhg==", + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.12.3.tgz", + "integrity": "sha512-+qDLgm2yq6WOKcxTb43lDeo3EtMIDQs0CK1RNqhHC9iT6u0KOmgwAClkYh9xFw2ATbmUZzt4f7KMwDCOfPDluA==", "dev": true, "requires": { - "crypto-js": "^3.3.0", + "crypto-js": "^4.0.0", "fontkit": "^1.8.1", "linebreak": "^1.0.2", "png-js": "^1.0.0" @@ -15249,13 +15311,13 @@ "integrity": "sha512-sxMwpDw/ySY1WB2CE3+IdMuEcWibJ72DDOsXLkSmEaSzwEUaYBT6DWgOfBiHGCux4q433X6+OEFWjlVqp7gL6g==" }, "plist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.2.tgz", - "integrity": "sha512-MSrkwZBdQ6YapHy87/8hDU8MnIcyxBKjeF+McXnr5A9MtffPewTs7G3hlpodT5TacyfIyFTaJEhh3GGcmasTgQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.3.tgz", + "integrity": "sha512-ghdOKN99hh1oEmAlwBmPYo4L+tSQ7O3jRpkhWqOrMz86CWotpVzMevvQ+czo7oPDpOZyA6K06Ci7QVHpoh9gaA==", "requires": { "base64-js": "^1.5.1", "xmlbuilder": "^9.0.7", - "xmldom": "^0.5.0" + "xmldom": "^0.6.0" } }, "pluralize": { @@ -16816,9 +16878,9 @@ } }, "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "regenerator-transform": { "version": "0.14.5", @@ -17015,9 +17077,9 @@ } }, "resolve-alpn": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.1.2.tgz", - "integrity": "sha512-8OyfzhAtA32LVUsJSke3auIyINcwdh5l3cvYKdKO0nvsYSKuiLfTM5i78PJswFPT8y6cPW+L1v6/hE95chcpDA==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.0.tgz", + "integrity": "sha512-e4FNQs+9cINYMO5NMFc6kOUCdohjqFPSgMuwuZAOUWqrfWsen+Yjy5qZFkV5K7VO7tFSLKcUL97olkED7sCBHA==" }, "resolve-cwd": { "version": "2.0.0", @@ -17543,6 +17605,17 @@ "rechoir": "^0.6.2" } }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -17608,9 +17681,9 @@ } }, "smart-buffer": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", - "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true }, "snapdragon": { @@ -18033,9 +18106,9 @@ } }, "spdx-license-ids": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz", - "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", + "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", "dev": true }, "spdy": { @@ -19025,9 +19098,9 @@ }, "dependencies": { "ajv": { - "version": "8.6.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.1.tgz", - "integrity": "sha512-42VLtQUOLefAvKFAQIxIZDaThq6om/PrfP0CYk3/vn+y4BMNkKnbli8ON2QCiHov4KkzOSJ/xSoBJdayiiYvVQ==", + "version": "8.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", + "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -19056,9 +19129,9 @@ "dev": true }, "tar": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", - "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==", + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.8.tgz", + "integrity": "sha512-sb9b0cp855NbkMJcskdSYA7b11Q8JsX4qe4pyUAfHp+Y6jBjJeek2ZVlwEfWayshEIwlIzXx0Fain3QG9JPm2A==", "dev": true, "requires": { "chownr": "^2.0.0", @@ -19987,9 +20060,9 @@ "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==" }, "uglify-js": { - "version": "3.13.10", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.10.tgz", - "integrity": "sha512-57H3ACYFXeo1IaZ1w02sfA71wI60MGco/IQFjOqK+WtKoprh7Go2/yvd2HPtoJILO2Or84ncLccI4xoHMTSbGg==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.1.tgz", + "integrity": "sha512-JhS3hmcVaXlp/xSo3PKY5R0JqKs5M3IV+exdLHW99qKvKivPO4Z8qbej6mte17SOPqAOVMjt/XGgWacnFSzM3g==", "optional": true }, "ultron": { @@ -20283,9 +20356,9 @@ } }, "url-parse": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", - "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz", + "integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==", "dev": true, "requires": { "querystringify": "^2.1.1", @@ -21860,9 +21933,9 @@ } }, "xmldom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.5.0.tgz", - "integrity": "sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz", + "integrity": "sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg==" }, "xmlhttprequest-ssl": { "version": "1.5.5", @@ -22002,9 +22075,9 @@ }, "dependencies": { "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" } } } diff --git a/package.json b/package.json index 3c9637b5..e4ea4587 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "test": "ng test" }, "dependencies": { + "@angular/animations": "9.1.13", "@angular/cdk": "12.0.0", "@angular/common": "9.1.12", "@angular/core": "9.1.12", @@ -51,6 +52,7 @@ "@asymmetrik/ngx-leaflet-markercluster": "2.1.1", "@capacitor/core": "2.4.6", "@ionic-native/core": "5.29.0", + "@ionic-native/dialogs": "5.31.1", "@ionic-native/diagnostic": "5.32.0", "@ionic-native/geolocation": "5.29.0", "@ionic-native/network": "5.31.1", @@ -68,6 +70,7 @@ "cordova-ios": "6.2.0", "cordova-plugin-androidx-adapter": "1.1.3", "cordova-plugin-device": "2.0.3", + "cordova-plugin-dialogs": "2.0.2", "cordova.plugins.diagnostic": "6.0.3", "cordova-plugin-geolocation": "4.1.0", "cordova-plugin-ionic-keyboard": "2.2.0", diff --git a/src/app/animation/animation-choreographer.ts b/src/app/animation/animation-choreographer.ts new file mode 100644 index 00000000..08c79818 --- /dev/null +++ b/src/app/animation/animation-choreographer.ts @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +import {SHARED_AXIS_DIRECTIONS} from './material-motion'; + +/** + * /** + * Choreograph a shared axis animation based on a row of values so that changing state + * results in the correct, expected behavior of reverting the previous animation etc. + * + * The Choreographer manages motion of an element that changes value. This can be used in a variety of ways, + * for example multi-view choreographing can be achieved as such + * + * ```html + *
+ *
+ *
+ *
+ * ``` + * + * @see {@link https://material.io/design/motion/the-motion-system.html#shared-axis} + */ +export class SharedAxisChoreographer { + /** + * Expected next value + */ + private expectedValue: T; + + /** + * Animation State + */ + animationState: string; + + /** + * Current value to read from + */ + currentValue: T; + + constructor(initialValue: T, readonly pages?: T[]) { + this.currentValue = initialValue; + this.expectedValue = initialValue; + } + + /** + * Must be linked to the animation callback + */ + animationDone() { + this.animationState = 'in'; + this.currentValue = this.expectedValue; + } + + /** + * Change view for a new state that the current active view should receive + */ + changeViewForState(newValue: T, direction?: -1 | 0 | 1) { + if (direction === 0) { + this.currentValue = this.expectedValue = newValue; + return; + } + + this.expectedValue = newValue; + + // pre-place animation state + // new element comes in from the right and pushes the old one to the left + this.animationState = + SHARED_AXIS_DIRECTIONS[ + direction ?? this.getDirection(this.currentValue, newValue) + ]; + } + + /** + * Get direction from to + */ + getDirection(from: T, to: T) { + const element = this.pages?.find(it => it === from || it === to); + + return element === from ? 1 : element === to ? -1 : 0; + } +} diff --git a/src/app/animation/material-motion.ts b/src/app/animation/material-motion.ts new file mode 100644 index 00000000..1fe23272 --- /dev/null +++ b/src/app/animation/material-motion.ts @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import { + animate, + sequence, + state, + style, + transition, + trigger, +} from '@angular/animations'; + +/** + * Fade transition + * + * @see {@link https://material.io/design/motion/the-motion-system.html#fade} + */ +export const materialFade = trigger('materialFade', [ + state('in', style({opacity: 1})), + transition(':enter', [style({opacity: 0}), animate('250ms ease')]), + transition(':leave', [animate('200ms ease', style({opacity: 0}))]), +]); + +/** + * Fade transition + * + * @see {@link https://material.io/design/motion/the-motion-system.html#fade} + */ +export const materialManualFade = trigger('materialManualFade', [ + state('in', style({opacity: 1})), + state('out', style({opacity: 0})), + transition('in => out', animate('200ms ease')), + transition('out => in', animate('250ms ease')), +]); + +/** + * Fade through transition + * + * @see {@link https://material.io/design/motion/the-motion-system.html#fade-through} + */ +export const materialFadeThrough = trigger('materialFadeThrough', [ + state('in', style({transform: 'scale(100%)', opacity: 1})), + transition(':enter', [ + style({transform: 'scale(80%)', opacity: 0}), + animate('250ms ease'), + ]), + transition(':leave', [animate('200ms ease', style({opacity: 0}))]), +]); + +export const SHARED_AXIS_DIRECTIONS = { + [-1]: 'go-backward', + [0]: 'in', + [1]: 'go-forward', +}; + +/** + * Shared axis transition along the X-Axis + * + * Needs to be manually choreographed + * + * @see {@link https://material.io/design/motion/the-motion-system.html#shared-axis} + * @see {SharedAxisChoreographer} + */ +export const materialSharedAxisX = trigger('materialSharedAxisX', [ + state( + SHARED_AXIS_DIRECTIONS[-1], + style({opacity: 0, transform: 'translateX(30px)'}), + ), + state( + SHARED_AXIS_DIRECTIONS[0], + style({opacity: 1, transform: 'translateX(0)'}), + ), + state( + SHARED_AXIS_DIRECTIONS[1], + style({opacity: 0, transform: 'translateX(-30px)'}), + ), + transition( + `${SHARED_AXIS_DIRECTIONS[-1]} => ${SHARED_AXIS_DIRECTIONS[0]}`, + sequence([ + style({opacity: 0, transform: 'translateX(-30px)'}), + animate('100ms ease-out'), + ]), + ), + transition(`${SHARED_AXIS_DIRECTIONS[0]} => *`, animate('100ms ease-out')), + transition( + `${SHARED_AXIS_DIRECTIONS[1]} => ${SHARED_AXIS_DIRECTIONS[0]}`, + sequence([ + style({opacity: 0, transform: 'translateX(30px)'}), + animate('100ms ease-out'), + ]), + ), +]); diff --git a/src/app/animation/skeleton-transitions/chip-loading-transition.ts b/src/app/animation/skeleton-transitions/chip-loading-transition.ts new file mode 100644 index 00000000..d17594b6 --- /dev/null +++ b/src/app/animation/skeleton-transitions/chip-loading-transition.ts @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {animate, style, transition, trigger} from '@angular/animations'; + +export const chipTransition = trigger('chipTransition', [ + transition(':enter', [ + style({ + 'opacity': 0, + 'transform': 'scaleX(80%)', + 'transform-origin': 'left', + }), + animate('200ms ease', style({opacity: 1, transform: 'scaleX(100%)'})), + ]), +]); + +export const chipSkeletonTransition = trigger('chipSkeletonTransition', [ + transition(':leave', [ + style({ + 'opacity': 1, + 'transform': 'scaleX(100%)', + 'transform-origin': 'left', + }), + animate('200ms ease', style({opacity: 0, transform: 'scaleX(120%)'})), + ]), +]); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 60455fb9..24e6b13e 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -23,8 +23,8 @@ import localeDe from '@angular/common/locales/de'; import {APP_INITIALIZER, NgModule, Provider} from '@angular/core'; import {BrowserModule} from '@angular/platform-browser'; import {RouteReuseStrategy} from '@angular/router'; -import {Diagnostic} from '@ionic-native/diagnostic/ngx'; import {SplashScreen} from '@ionic-native/splash-screen/ngx'; +import {Diagnostic} from '@ionic-native/diagnostic/ngx'; import {StatusBar} from '@ionic-native/status-bar/ngx'; import {IonicModule, IonicRouteStrategy} from '@ionic/angular'; import { @@ -45,12 +45,15 @@ import {DataModule} from './modules/data/data.module'; import {MapModule} from './modules/map/map.module'; import {MenuModule} from './modules/menu/menu.module'; import {NewsModule} from './modules/news/news.module'; +import {ScheduleModule} from './modules/schedule/schedule.module'; import {SettingsModule} from './modules/settings/settings.module'; import {SettingsProvider} from './modules/settings/settings.provider'; import {StorageModule} from './modules/storage/storage.module'; import {ThingTranslateModule} from './translation/thing-translate.module'; import {fakeBackendProvider} from './_helpers/fake-backend.interceptor'; +import {UtilModule} from './util/util.module'; import {initLogger} from './_helpers/ts-logger'; +import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; registerLocaleData(localeDe); @@ -132,6 +135,7 @@ const providers: Provider[] = [ imports: [ AppRoutingModule, BrowserModule, + BrowserAnimationsModule, CommonModule, ConfigModule, DataModule, @@ -139,6 +143,7 @@ const providers: Provider[] = [ MapModule, MenuModule, NewsModule, + ScheduleModule, SettingsModule, StorageModule, ThingTranslateModule.forRoot(), @@ -149,6 +154,7 @@ const providers: Provider[] = [ useFactory: createTranslateLoader, }, }), + UtilModule, // use maximal logging level when not in production, minimal (log only fatal errors) in production LoggerModule.forRoot({ level: environment.production diff --git a/src/app/modules/data/chips/action-chip-list.html b/src/app/modules/data/chips/action-chip-list.html index 3c81cd12..dd764b8f 100644 --- a/src/app/modules/data/chips/action-chip-list.html +++ b/src/app/modules/data/chips/action-chip-list.html @@ -3,6 +3,7 @@ *ngIf="applicable['locate']()" [item]="item" > + . */ -import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core'; -import {SCDateSeries} from '@openstapps/core'; -import {every, groupBy, some, sortBy, values} from 'lodash-es'; +import { + ChangeDetectorRef, + Component, + Input, + OnDestroy, + OnInit, +} from '@angular/core'; +import {PopoverController} from '@ionic/angular'; +import {SCDateSeries, SCUuid} from '@openstapps/core'; +import { + difference, + every, + flatMap, + groupBy, + mapValues, + some, + sortBy, + union, + values, +} from 'lodash-es'; import {capitalize, last} from 'lodash-es'; +import {Subscription} from 'rxjs'; +import {ScheduleProvider} from '../../schedule/schedule.provider'; enum Selection { ON = 2, @@ -28,7 +48,6 @@ enum Selection { * * The generic is to preserve type safety of how deep the tree goes. */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any class TreeNode | SelectionValue> { /** * Value of this node @@ -54,19 +73,16 @@ class TreeNode | SelectionValue> { * Accumulate values of children to set current value */ private accumulateApplyValues() { - const selections: number[] = this.children.map( - it => - /* eslint-disable unicorn/no-nested-ternary */ - it instanceof TreeNode - ? it.checked - ? Selection.ON - : it.indeterminate - ? Selection.PARTIAL - : Selection.OFF - : (it as SelectionValue).selected + const selections: number[] = this.children.map(it => + it instanceof TreeNode + ? it.checked ? Selection.ON - : Selection.OFF, - /* eslint-enable unicorn/no-nested-ternary */ + : it.indeterminate + ? Selection.PARTIAL + : Selection.OFF + : (it as SelectionValue).selected + ? Selection.ON + : Selection.OFF, ); this.checked = every(selections, it => it === Selection.ON); @@ -83,7 +99,7 @@ class TreeNode | SelectionValue> { if (child instanceof TreeNode) { child.checked = this.checked; child.indeterminate = false; - // eslint-disable-next-line @typescript-eslint/no-explicit-any + // tslint:disable-next-line:no-any (child as TreeNode).applyValueDownwards(); } else { (child as SelectionValue).selected = this.checked; @@ -149,7 +165,7 @@ interface SelectionValue { templateUrl: 'add-event-popover.html', styleUrls: ['add-event-popover.scss'], }) -export class AddEventPopoverComponent implements OnInit { +export class AddEventPopoverComponent implements OnInit, OnDestroy { /** * Lodash alias */ @@ -170,23 +186,70 @@ export class AddEventPopoverComponent implements OnInit { */ selection: TreeNode>; - constructor(readonly ref: ChangeDetectorRef) {} + /** + * Uuids + */ + uuids: SCUuid[]; + + /** + * Uuid Subscription + */ + uuidSubscription: Subscription; + + constructor( + readonly ref: ChangeDetectorRef, + readonly scheduleProvider: ScheduleProvider, + readonly popoverController: PopoverController, + ) {} + + /** + * Destroy + */ + ngOnDestroy() { + this.uuidSubscription.unsubscribe(); + } /** * Init */ ngOnInit() { - this.selection = new TreeNode( - values( - groupBy( - sortBy( - this.items.map(item => ({selected: false, item: item})), - it => it.item.frequency, - ), - it => it.item.frequency, - ), - ).map(item => new TreeNode(item, this.ref)), - this.ref, + this.uuidSubscription = this.scheduleProvider.uuids$.subscribe( + async result => { + this.uuids = result; + + this.selection = new TreeNode( + values( + groupBy( + sortBy( + this.items.map(item => ({ + selected: this.uuids.includes(item.uid), + item: item, + })), + it => it.item.frequency, + ), + it => it.item.frequency, + ), + ).map(item => new TreeNode(item, this.ref)), + this.ref, + ); + }, ); } + + /** + * On selection change + */ + async onCommit(save: boolean) { + if (save) { + const {false: unselected, true: selected} = mapValues( + groupBy(flatMap(this.selection.children, 'children'), 'selected'), + value => value.map(it => it.item.uid), + ); + this.scheduleProvider.uuids$.next( + union(difference(this.uuids, unselected), selected), + ); + } + + await this.popoverController.dismiss(); + } } diff --git a/src/app/modules/data/chips/add-event-popover.html b/src/app/modules/data/chips/add-event-popover.html index edea1a30..b62fdecd 100644 --- a/src/app/modules/data/chips/add-event-popover.html +++ b/src/app/modules/data/chips/add-event-popover.html @@ -45,4 +45,12 @@ +
+ {{ + 'abort' | translate + }} + {{ + 'ok' | translate + }} +
diff --git a/src/app/modules/data/chips/add-event-popover.scss b/src/app/modules/data/chips/add-event-popover.scss index 5f5b23ef..02c74bb6 100644 --- a/src/app/modules/data/chips/add-event-popover.scss +++ b/src/app/modules/data/chips/add-event-popover.scss @@ -5,3 +5,7 @@ ion-card-content { width: fit-content; } + +.action-buttons { + float: right; +} diff --git a/src/app/modules/data/chips/data/add-event-action-chip.component.ts b/src/app/modules/data/chips/data/add-event-action-chip.component.ts index c00a0b7d..54d3499a 100644 --- a/src/app/modules/data/chips/data/add-event-action-chip.component.ts +++ b/src/app/modules/data/chips/data/add-event-action-chip.component.ts @@ -1,4 +1,4 @@ -/* eslint-disable class-methods-use-this */ +/* tslint:disable:prefer-function-over-method */ /* * Copyright (C) 2021 StApps * This program is free software: you can redistribute it and/or modify it @@ -13,11 +13,18 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -import {Component, Input, OnInit} from '@angular/core'; +import {Component, Input, OnDestroy, OnInit} from '@angular/core'; import {PopoverController} from '@ionic/angular'; -import {SCDateSeries, SCThing, SCThingType} from '@openstapps/core'; -import {DataProvider} from '../../data.provider'; +import {SCDateSeries, SCThing, SCThingType, SCUuid} from '@openstapps/core'; +import {difference, map} from 'lodash-es'; +import {Subscription} from 'rxjs'; +import {ScheduleProvider} from '../../../schedule/schedule.provider'; import {AddEventPopoverComponent} from '../add-event-popover.component'; +import {CoordinatedSearchProvider} from '../../coordinated-search.provider'; +import { + chipSkeletonTransition, + chipTransition, +} from '../../../../animation/skeleton-transitions/chip-loading-transition'; enum AddEventStates { ADDED_ALL, @@ -33,8 +40,9 @@ enum AddEventStates { selector: 'stapps-add-event-action-chip', templateUrl: 'add-event-action-chip.html', styleUrls: ['add-event-action-chip.scss'], + animations: [chipSkeletonTransition, chipTransition], }) -export class AddEventActionChipComponent implements OnInit { +export class AddEventActionChipComponent implements OnInit, OnDestroy { /** * Associated date series */ @@ -91,9 +99,20 @@ export class AddEventActionChipComponent implements OnInit { }, }; + /** + * UUIDs + */ + uuids: SCUuid[]; + + /** + * UUID Subscription + */ + uuidSubscription: Subscription; + constructor( readonly popoverController: PopoverController, - readonly dataProvider: DataProvider, + readonly dataProvider: CoordinatedSearchProvider, + readonly scheduleProvider: ScheduleProvider, ) {} /** @@ -107,6 +126,13 @@ export class AddEventActionChipComponent implements OnInit { this.disabled = disabled; } + /** + * TODO + */ + ngOnDestroy() { + this.uuidSubscription?.unsubscribe(); + } + /** * Init */ @@ -115,7 +141,7 @@ export class AddEventActionChipComponent implements OnInit { this.item.type === SCThingType.DateSeries ? Promise.resolve([this.item as SCDateSeries]) : this.dataProvider - .search({ + .coordinatedSearch({ filter: { arguments: { filters: [ @@ -140,19 +166,36 @@ export class AddEventActionChipComponent implements OnInit { }, }) .then(it => it.data as SCDateSeries[]); - this.associatedDateSeries.then(it => - this.applyState( - it.length === 0 - ? AddEventStates.UNAVAILABLE - : AddEventStates.REMOVED_ALL, - ), + + this.uuidSubscription = this.scheduleProvider.uuids$.subscribe( + async result => { + this.uuids = result; + const associatedDateSeries = await this.associatedDateSeries; + if (associatedDateSeries.length === 0) { + this.applyState(AddEventStates.UNAVAILABLE); + + return; + } + switch ( + difference(map(associatedDateSeries, 'uid'), this.uuids).length + ) { + case 0: + this.applyState(AddEventStates.ADDED_ALL); + break; + case associatedDateSeries.length: + this.applyState(AddEventStates.REMOVED_ALL); + break; + default: + this.applyState(AddEventStates.ADDED_SOME); + break; + } + }, ); } /** * Action */ - // @Override async onClick(event: MouseEvent) { const associatedDateSeries = await this.associatedDateSeries; const popover = await this.popoverController.create({ @@ -165,12 +208,5 @@ export class AddEventActionChipComponent implements OnInit { event: event, }); await popover.present(); - // TODO: replace dummy implementation - await popover.onDidDismiss(); - this.applyState( - this.state === AddEventStates.ADDED_ALL - ? AddEventStates.REMOVED_ALL - : AddEventStates.ADDED_ALL, - ); } } diff --git a/src/app/modules/data/chips/data/add-event-action-chip.html b/src/app/modules/data/chips/data/add-event-action-chip.html index 7ae5dca8..a35cd95b 100644 --- a/src/app/modules/data/chips/data/add-event-action-chip.html +++ b/src/app/modules/data/chips/data/add-event-action-chip.html @@ -1,14 +1,16 @@ -
+
{{ label | translate }} + + + + +
- - - - - diff --git a/src/app/modules/data/chips/data/add-event-action-chip.scss b/src/app/modules/data/chips/data/add-event-action-chip.scss index f032704e..3c3deb99 100644 --- a/src/app/modules/data/chips/data/add-event-action-chip.scss +++ b/src/app/modules/data/chips/data/add-event-action-chip.scss @@ -1,3 +1,14 @@ -::ng-deep ion-skeleton-text { +:host ::ng-deep ion-skeleton-text { width: 50px; } + +.stack-children { + display: grid; + align-items: start; + justify-items: start; +} + +.stack-children > * { + grid-column-start: 1; + grid-row-start: 1; +} diff --git a/src/app/modules/data/coordinated-search.provider.spec.ts b/src/app/modules/data/coordinated-search.provider.spec.ts new file mode 100644 index 00000000..5ca8f803 --- /dev/null +++ b/src/app/modules/data/coordinated-search.provider.spec.ts @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +import {arrayToIndexMap} from './coordinated-search.provider'; + +describe('CoordinatedSearchProvider', () => { + it('transform arrays correctly', () => { + expect(arrayToIndexMap(['a', 'b', 'c'])).toEqual({0: 'a', 1: 'b', 2: 'c'}); + }); +}); diff --git a/src/app/modules/data/coordinated-search.provider.ts b/src/app/modules/data/coordinated-search.provider.ts new file mode 100644 index 00000000..3e936adc --- /dev/null +++ b/src/app/modules/data/coordinated-search.provider.ts @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {SCSearchRequest, SCSearchResponse} from '@openstapps/core'; +import {Injectable} from '@angular/core'; +import {DataProvider} from './data.provider'; + +/** + * Delay execution for (at least) a set amount of time + */ +async function delay(ms: number): Promise { + return await new Promise(resolve => setTimeout(resolve, ms)); +} + +/** + * Transforms an array to an object with the indices as keys + * + * ['a', 'b', 'c'] => {0: 'a', 1: 'b', 2: 'c'} + */ +export function arrayToIndexMap(array: T[]): Record { + // eslint-disable-next-line unicorn/no-array-reduce + return array.reduce((previous, current, index) => { + previous[index] = current; + return previous; + }, {} as Record); +} + +interface OngoingQuery { + request: SCSearchRequest; + response?: Promise; +} + +/** + * Coordinated search request that bundles requests from multiple modules into a single one + */ +@Injectable({ + providedIn: 'root', +}) +export class CoordinatedSearchProvider { + constructor(readonly dataProvider: DataProvider) {} + + /** + * Queue of ongoing queries + */ + queue: OngoingQuery[] = []; + + /** + * Default latency of search requests + */ + latencyMs = 50; + + /** + * Start a coordinated search that merges requests across components + * + * This method collects the request, then: + * 1. If the queue is full, dispatches all immediately + * 2. If not, waits a set amount of time for other requests to come in + */ + async coordinatedSearch( + query: SCSearchRequest, + latencyMs?: number, + ): Promise { + const ongoingQuery: OngoingQuery = {request: query}; + this.queue.push(ongoingQuery); + + if (this.queue.length < this.dataProvider.backendQueriesLimit) { + await delay(latencyMs ?? this.latencyMs); + } + + if (this.queue.length > 0) { + // because we are guaranteed to have limited our queue size to be + // <= to the backendQueriesLimite as of above, we can bypass the wrapper + // in the data provider that usually would be responsible for splitting up the requests + const responses = this.dataProvider.client.multiSearch( + arrayToIndexMap(this.queue.map(it => it.request)), + ); + + for (const [index, request] of this.queue.entries()) { + request.response = new Promise(resolve => + responses.then(it => resolve(it[index])), + ); + } + + this.queue = []; + } + + // Response is guaranteed to be defined here + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return await ongoingQuery.response!; + } +} diff --git a/src/app/modules/data/data.module.ts b/src/app/modules/data/data.module.ts index cd51cdd1..6c8ecae4 100644 --- a/src/app/modules/data/data.module.ts +++ b/src/app/modules/data/data.module.ts @@ -24,6 +24,7 @@ import {MarkdownModule} from 'ngx-markdown'; import {MomentModule} from 'ngx-moment'; import {ThingTranslateModule} from '../../translation/thing-translate.module'; import {MenuModule} from '../menu/menu.module'; +import {ScheduleProvider} from '../schedule/schedule.provider'; import {StorageModule} from '../storage/storage.module'; import {ActionChipListComponent} from './chips/action-chip-list.component'; import {AddEventPopoverComponent} from './chips/add-event-popover.component'; @@ -39,7 +40,6 @@ import {AddressDetailComponent} from './elements/address-detail.component'; import {OffersDetailComponent} from './elements/offers-detail.component'; import {OffersInListComponent} from './elements/offers-in-list.component'; import {OriginDetailComponent} from './elements/origin-detail.component'; -import {OriginInListComponent} from './elements/origin-in-list.component'; import {SimpleCardComponent} from './elements/simple-card.component'; import {DataListComponent} from './list/data-list.component'; import {FoodDataListComponent} from './list/food-data-list.component'; @@ -59,7 +59,6 @@ import {PlaceDetailContentComponent} from './types/place/place-detail-content.co import {PlaceListItemComponent} from './types/place/place-list-item.component'; import {PlaceMensaDetailComponent} from './types/place/special/mensa/place-mensa-detail.component'; import {SemesterDetailContentComponent} from './types/semester/semester-detail-content.component'; -import {VideoDetailContentComponent} from './types/video/video-detail-content.component'; import {MapWidgetComponent} from '../map/widget/map-widget.component'; import {LeafletModule} from '@asymmetrik/ngx-leaflet'; import {ArticleListItemComponent} from './types/article/article-list-item.component'; @@ -73,29 +72,30 @@ import {LongInlineTextComponent} from './elements/long-inline-text.component'; import {MessageListItemComponent} from './types/message/message-list-item.component'; import {OrganizationListItemComponent} from './types/organization/organization-list-item.component'; import {PersonListItemComponent} from './types/person/person-list-item.component'; -import {SemesterListItemComponent} from './types/semester/semester-list-item.component'; import {SkeletonListItemComponent} from './elements/skeleton-list-item.component'; import {SkeletonSegmentComponent} from './elements/skeleton-segment-button.component'; +import {VideoDetailContentComponent} from './types/video/video-detail-content.component'; +import {SemesterListItemComponent} from './types/semester/semester-list-item.component'; import {VideoListItemComponent} from './types/video/video-list-item.component'; +import {OriginInListComponent} from './elements/origin-in-list.component'; +import {CoordinatedSearchProvider} from './coordinated-search.provider'; /** * Module for handling data */ @NgModule({ declarations: [ + ActionChipListComponent, + AddEventActionChipComponent, AddEventPopoverComponent, - OffersDetailComponent, - OffersInListComponent, AddressDetailComponent, ArticleDetailContentComponent, ArticleListItemComponent, - SimpleCardComponent, - SkeletonSimpleCardComponent, CatalogDetailContentComponent, CatalogListItemComponent, DataDetailComponent, DataDetailContentComponent, - FoodDataListComponent, + DataIconPipe, DataListComponent, DataListItemComponent, DateSeriesDetailContentComponent, @@ -106,10 +106,14 @@ import {VideoListItemComponent} from './types/video/video-list-item.component'; EventListItemComponent, FavoriteDetailContentComponent, FavoriteListItemComponent, + FoodDataListComponent, + LocateActionChipComponent, LongInlineTextComponent, MapWidgetComponent, MessageDetailContentComponent, MessageListItemComponent, + OffersDetailComponent, + OffersInListComponent, OrganizationDetailContentComponent, OrganizationListItemComponent, OriginDetailComponent, @@ -122,21 +126,20 @@ import {VideoListItemComponent} from './types/video/video-list-item.component'; SearchPageComponent, SemesterDetailContentComponent, SemesterListItemComponent, + SimpleCardComponent, SkeletonListItemComponent, SkeletonSegmentComponent, + SkeletonSimpleCardComponent, VideoDetailContentComponent, VideoListItemComponent, - DataIconPipe, - ActionChipListComponent, - AddEventActionChipComponent, - LocateActionChipComponent, ], + entryComponents: [DataListComponent], imports: [ - IonicModule.forRoot(), CommonModule, - FormsModule, DataRoutingModule, + FormsModule, HttpClientModule, + IonicModule.forRoot(), LeafletModule, MarkdownModule.forRoot(), MenuModule, @@ -150,16 +153,26 @@ import {VideoListItemComponent} from './types/video/video-list-item.component'; TranslateModule.forChild(), ThingTranslateModule.forChild(), ], - providers: [DataProvider, DataFacetsProvider, Network, StAppsWebHttpClient], + providers: [ + CoordinatedSearchProvider, + DataProvider, + DataFacetsProvider, + Network, + ScheduleProvider, + StAppsWebHttpClient, + ], exports: [ + DataDetailComponent, + DataDetailContentComponent, + DataIconPipe, DataListComponent, DataListItemComponent, - DataDetailComponent, - SkeletonSimpleCardComponent, - SkeletonListItemComponent, - DataIconPipe, + DateSeriesListItemComponent, PlaceListItemComponent, - DataDetailContentComponent, + SimpleCardComponent, + SkeletonListItemComponent, + SkeletonSimpleCardComponent, + SearchPageComponent, ], }) export class DataModule {} diff --git a/src/app/modules/data/list/data-list.component.ts b/src/app/modules/data/list/data-list.component.ts index 9b7de7c5..639a232b 100644 --- a/src/app/modules/data/list/data-list.component.ts +++ b/src/app/modules/data/list/data-list.component.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 StApps + * Copyright (C) 2021 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. diff --git a/src/app/modules/data/list/search-page.component.ts b/src/app/modules/data/list/search-page.component.ts index 100bf20b..e6f5288a 100644 --- a/src/app/modules/data/list/search-page.component.ts +++ b/src/app/modules/data/list/search-page.component.ts @@ -49,6 +49,11 @@ export class SearchPageComponent implements OnInit, OnDestroy { */ @Input() forcedFilter?: SCSearchFilter; + /** + * If routing should be done if the user clicks on an item + */ + @Input() itemRouting? = true; + /** * Thing counter to start query the next page from */ @@ -120,46 +125,6 @@ export class SearchPageComponent implements OnInit, OnDestroy { protected router: Router, ) { this.initialize(); - - combineLatest([ - this.queryTextChanged.pipe( - debounceTime(this.searchQueryDueTime), - distinctUntilChanged(), - startWith(this.queryText), - ), - this.contextMenuService.filterQueryChanged$.pipe( - startWith(this.filterQuery), - ), - this.contextMenuService.sortQueryChanged$.pipe(startWith(this.sortQuery)), - ]).subscribe(async query => { - this.queryText = query[0]; - this.filterQuery = query[1]; - this.sortQuery = query[2]; - this.from = 0; - await this.fetchAndUpdateItems(); - this.queryChanged.next(); - }); - - this.fetchAndUpdateItems(); - - /** - * Subscribe to 'settings.changed' events - */ - this.subscriptions.push( - this.settingsProvider.settingsActionChanged$.subscribe( - ({type, payload}) => { - if (type === 'stapps.settings.changed') { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const {category, name, value} = payload!; - this.logger.log(`received event "settings.changed" with category: - ${category}, name: ${name}, value: ${JSON.stringify(value)}`); - } - }, - ), - this.dataRoutingService.itemSelectListener().subscribe(item => { - void this.router.navigate(['data-detail', item.uid]); - }), - ); } /** @@ -261,6 +226,48 @@ export class SearchPageComponent implements OnInit, OnDestroy { * Initialises the possible sort options in ContextMenuService */ ngOnInit(): void { + combineLatest([ + this.queryTextChanged.pipe( + debounceTime(this.searchQueryDueTime), + distinctUntilChanged(), + startWith(this.queryText), + ), + this.contextMenuService.filterQueryChanged$.pipe( + startWith(this.filterQuery), + ), + this.contextMenuService.sortQueryChanged$.pipe(startWith(this.sortQuery)), + ]).subscribe(async query => { + this.queryText = query[0]; + this.filterQuery = query[1]; + this.sortQuery = query[2]; + this.from = 0; + await this.fetchAndUpdateItems(); + this.queryChanged.next(); + }); + + void this.fetchAndUpdateItems(); + + /** + * Subscribe to 'settings.changed' events + */ + this.subscriptions.push( + this.settingsProvider.settingsActionChanged$.subscribe( + ({type, payload}) => { + if (type === 'stapps.settings.changed') { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const {category, name, value} = payload!; + this.logger.log(`received event "settings.changed" with category: + ${category}, name: ${name}, value: ${JSON.stringify(value)}`); + } + }, + ), + this.dataRoutingService.itemSelectListener().subscribe(item => { + if (this.itemRouting) { + void this.router.navigate(['data-detail', item.uid]); + } + }), + ); + this.contextMenuService.setContextSort({ name: 'sort', reversed: false, diff --git a/src/app/modules/data/types/date-series/date-series-detail-content.html b/src/app/modules/data/types/date-series/date-series-detail-content.html index 68025af5..22021b55 100644 --- a/src/app/modules/data/types/date-series/date-series-detail-content.html +++ b/src/app/modules/data/types/date-series/date-series-detail-content.html @@ -3,7 +3,7 @@ {{ 'inPlace' | propertyNameTranslate: item | titlecase }} - + {{ 'name' | thingTranslate: item.inPlace }} @@ -17,6 +17,10 @@ [title]="'Duration'" [content]="[item.duration | amDuration: 'minutes']" > + + + (); /** * Observable filterContext streams */ - // tslint:disable-next-line:member-ordering filterContextChanged$ = this.filterOptions.asObservable(); /** @@ -130,19 +55,21 @@ export class ContextMenuService { /** * Observable filterContext streams */ - // tslint:disable-next-line:member-ordering filterQueryChanged$ = this.filterQuery.asObservable(); + /** + * Forced SCThingTypeFilter + */ + forcedType?: SCThingType; + /** * Container for the sort context */ - // tslint:disable-next-line:member-ordering sortOptions = new Subject(); /** * Observable SortContext streams */ - // tslint:disable-next-line:member-ordering sortContextChanged$ = this.sortOptions.asObservable(); /** @@ -153,7 +80,6 @@ export class ContextMenuService { /** * Observable SortContext streams */ - // tslint:disable-next-line:member-ordering sortQueryChanged$ = this.sortQuery.asObservable(); /** @@ -166,6 +92,16 @@ export class ContextMenuService { ): SCSearchFilter | undefined => { const filters: SCSearchFilter[] = []; + if (typeof this.forcedType !== 'undefined') { + filters.push({ + arguments: { + field: 'type', + value: this.forcedType, + }, + type: 'value', + }); + } + for (const filterFacet of filterContext.options) { const optionFilters: SCSearchFilter[] = []; for (const filterBucket of filterFacet.buckets) { diff --git a/src/app/modules/menu/navigation/navigation.component.ts b/src/app/modules/menu/navigation/navigation.component.ts index f68df701..d206edd9 100644 --- a/src/app/modules/menu/navigation/navigation.component.ts +++ b/src/app/modules/menu/navigation/navigation.component.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2020 StApps + * Copyright (C) 2018, 2019 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. @@ -45,6 +45,15 @@ export class NavigationComponent { */ menu: SCAppConfigurationMenuCategory[]; + /** + * Possible languages to be used for translation + */ + public pages = [ + {title: 'Search', url: '/search', icon: 'search'}, + {title: 'Schedule', url: '/schedule', icon: 'calendar'}, + {title: 'Settings', url: '/settings', icon: 'settings'}, + ]; + /** * Core translator */ diff --git a/src/app/modules/schedule/page/calendar-view.component.ts b/src/app/modules/schedule/page/calendar-view.component.ts new file mode 100644 index 00000000..f7958e54 --- /dev/null +++ b/src/app/modules/schedule/page/calendar-view.component.ts @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import { + Component, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, + ViewChild, +} from '@angular/core'; +import {ActivatedRoute} from '@angular/router'; +import {IonDatetime, Platform} from '@ionic/angular'; +import {SCUuid} from '@openstapps/core'; +import {last} from 'lodash-es'; +import moment, {Moment} from 'moment'; +import {DateFormatPipe} from 'ngx-moment'; +import {Subscription} from 'rxjs'; +import {SharedAxisChoreographer} from '../../../animation/animation-choreographer'; +import { + materialFade, + materialManualFade, + materialSharedAxisX, +} from '../../../animation/material-motion'; +import {ScheduleProvider} from '../schedule.provider'; +import {ScheduleEvent, ScheduleResponsiveBreakpoint} from './schema/schema'; + +/** + * Component that displays the schedule + */ +@Component({ + selector: 'stapps-calendar-view', + templateUrl: 'calendar-view.html', + styleUrls: ['calendar-view.scss'], + animations: [materialFade, materialSharedAxisX, materialManualFade], +}) +export class CalendarViewComponent implements OnDestroy, OnInit, OnChanges { + /** + * UUID subscription + */ + private _uuidSubscription: Subscription; + + /** + * The day that is routed to + */ + protected routeDate: Moment; + + /** + * @see {blockDateTimeChange} + */ + anticipateDatetimeChangeBlocked = false; + + /** + * @see {blockDateTimeChange} + */ + // tslint:disable-next-line:no-magic-numbers + readonly anticipateDatetimeChangeTimeoutMs: 100; + + /** + * Animation state for cards + */ + cardsAnimationState: 'in' | 'out' = 'out'; + + /** + * The cursor + */ + @ViewChild('cursor', {read: HTMLElement}) cursor?: HTMLElement; + + /** + * Choreographer + */ + dateLabelsChoreographer: SharedAxisChoreographer; + + /** + * The date range to initially display + */ + displayDates: Moment[][] = []; + + /** + * Hours for grid + */ + readonly hours: number[]; + + /** + * Height of the slides based on the displayed hours + */ + readonly hoursAmount: number; + + /** + * Range of hours to display + */ + @Input() readonly hoursRange = { + from: 5, + to: 22, + }; + + /** + * Layout of the schedule + */ + @Input() layout: ScheduleResponsiveBreakpoint; + + /** + * Get the date format for the date field + */ + // tslint:disable-next-line:prefer-function-over-method + localDateFormat = moment.localeData().longDateFormat('L'); + + /** + * Route fragment + */ + routeFragment = 'schedule/calendar'; + + /** + * Vertical scale of the schedule (distance between hour lines) + */ + scale = 60; + + /** + * date -> (uid -> event) + */ + testSchedule: Record> = {}; + + /** + * UUIDs + */ + uuids: SCUuid[]; + + constructor( + protected readonly scheduleProvider: ScheduleProvider, + protected readonly activatedRoute: ActivatedRoute, + protected readonly datePipe: DateFormatPipe, + protected readonly platform: Platform, + ) { + // This could be done directly on the properties too instead of + // here in the constructor, but because of TSLint member ordering, + // some properties wouldn't be initialized, and if you disable + // member ordering, auto-fixing the file can still cause reordering + // of properties. + this.hoursAmount = this.hoursRange.to - this.hoursRange.from + 1; + this.hours = [...Array.from({length: this.hoursAmount}).keys()]; + } + + /** + * Because of some stupid Ionic implementation, there is no + * way to wait for the datetime picker to be dismissed without + * listening for (ionChange). Unfortunately that also includes + * changes caused by a page change, so whenever we do that, + * we have to block the event for a few milliseconds. + */ + blockDateTimeChange() { + this.anticipateDatetimeChangeBlocked = true; + setTimeout(() => { + this.anticipateDatetimeChangeBlocked = false; + }, this.anticipateDatetimeChangeTimeoutMs); + } + + /** + * Determine displayed dates according to display size + */ + determineDisplayDates() { + // let's boldly assume that we at least display one day + + const out = [moment(this.routeDate).startOf(this.layout.startOf)]; + for (let i = 1; i < this.layout.days; i++) { + out.push(out[0].clone().add(i, 'day')); + } + + this.displayDates = [ + out.map(it => it.clone().subtract(this.layout.days, 'days')), + out, + out.map(it => it.clone().add(this.layout.days, 'days')), + ]; + + this.dateLabelsChoreographer?.changeViewForState(this.getDateLabels(), 0); + // void this.mainSlides.slideTo(this.mode === 'schedule' ? 0 : 1, 0, false); + } + + /** + * Get date labels + */ + getDateLabels(): Moment[] { + return (this.displayDates[1] ?? this.displayDates[0]).map(it => it.clone()); + } + + /** + * Jump to a date + */ + jumpToDate(alt: IonDatetime, offset = 0, date?: Moment) { + if (this.anticipateDatetimeChangeBlocked) { + return; + } + + const newDate = (date ?? moment(alt.value)).subtract(offset, 'days'); + const direction = this.routeDate.isBefore(newDate) + ? 1 + : this.routeDate.isAfter(newDate) + ? -1 + : 0; + + this.blockDateTimeChange(); + this.routeDate = newDate; + this.determineDisplayDates(); + + this.dateLabelsChoreographer.changeViewForState( + this.getDateLabels(), + direction, + ); + } + + /** + * Load events + */ + async loadEvents(): Promise { + this.cardsAnimationState = 'out'; + const dateSeries = await this.scheduleProvider.getDateSeries(this.uuids); + + this.testSchedule = {}; + + for (const series of dateSeries) { + for (const date of series.dates) { + const parsedDate = moment(date).startOf('day').unix(); + + // fall back to default + (this.testSchedule[parsedDate] ?? (this.testSchedule[parsedDate] = {}))[ + series.uid + ] = { + dateSeries: series, + time: { + start: moment(date).hours(), + duration: series.duration, + }, + }; + } + } + + this.cursor?.scrollIntoView(); + this.cardsAnimationState = 'in'; + } + + /** + * On Changes + */ + ngOnChanges(changes: SimpleChanges) { + const layout = changes.layout?.currentValue as + | ScheduleResponsiveBreakpoint + | undefined; + if (layout) { + this.determineDisplayDates(); + } + } + + /** + * OnDestroy + */ + ngOnDestroy(): void { + this._uuidSubscription.unsubscribe(); + } + + /** + * Initialize + */ + ngOnInit() { + this._uuidSubscription = this.scheduleProvider.uuids$.subscribe( + async result => { + this.uuids = result; + await this.loadEvents(); + }, + ); + + let dayString: string | number | null = + this.activatedRoute.snapshot.paramMap.get('date'); + if (dayString == undefined || dayString === 'now') { + const urlFragment: string = last(window.location.href.split('/')) ?? ''; + + dayString = /^\d{4}-\d{2}-\d{2}$/.test(urlFragment) + ? urlFragment + : moment.now(); + } + this.routeDate = moment(dayString).startOf('day'); + this.dateLabelsChoreographer = new SharedAxisChoreographer( + this.getDateLabels(), + ); + + this.determineDisplayDates(); + } + + /** + * Change page + */ + async onPageChange(direction: number) { + this.blockDateTimeChange(); + const amount = direction * this.displayDates[0].length; + + this.routeDate.add(amount, 'days'); + window.history.replaceState( + {}, + '', + `#/${this.routeFragment}/${this.routeDate.format('YYYY-MM-DD')}`, + ); + + for (const slide of this.displayDates) { + for (const date of slide) { + date.add(amount, 'days'); + } + } + + this.dateLabelsChoreographer.changeViewForState( + this.getDateLabels(), + direction > 0 ? 1 : direction < 0 ? -1 : 0, + ); + } +} diff --git a/src/app/modules/schedule/page/calendar-view.html b/src/app/modules/schedule/page/calendar-view.html new file mode 100644 index 00000000..011ba473 --- /dev/null +++ b/src/app/modules/schedule/page/calendar-view.html @@ -0,0 +1,86 @@ +
+ + + + + + + + + + + + + {{ + item + | amDateFormat: ((item | dateIsThis: 'week') ? 'dddd' : 'll') + }} + + + + + + + + + + +
+ + + + + + +
+ + +
+ + +
+
+
+
+
+
+ +
+ {{ i + hoursRange.from }}:00 +
+
+
diff --git a/src/app/modules/schedule/page/calendar-view.scss b/src/app/modules/schedule/page/calendar-view.scss new file mode 100644 index 00000000..4957a3b7 --- /dev/null +++ b/src/app/modules/schedule/page/calendar-view.scss @@ -0,0 +1,97 @@ +div { + position: relative; + + .left-button, .right-button { + position: absolute; + top: 0; + bottom: 0; + margin: auto; + z-index: 5; + } + + .left-button { + left: 0; + } + + .right-button { + right: 0; + } +} + +.day-labels { + position: absolute; + top: 0; + left: 0; + padding: 8px 20px 8px 30px; + width: 100%; + + ion-row { + padding-right: 20px; + + ion-col { + ion-button { + position: absolute; + top: -8px; + font-size: large; + text-align: center; + width: 100%; + } + + // phantom element + ion-datetime { + position: absolute; + visibility: hidden; + height: 0 !important; + } + } + } +} + +.slide { + ion-grid { + position: absolute; + top: 0; + height: fit-content; + width: 100%; + padding-top: 8px; + + ion-row { + ion-col { + width: 100%; + + .vertical-line { + position: absolute; + top: 0; + left: 0; + border-left: 1px solid #dbdbdb; + z-index: 1; + } + + stapps-schedule-card { + z-index: 4; + position: absolute; + left: 0; + width: 100%; + } + } + } + } +} + +.hour-lines { + top: 0; + position: absolute; + display: flex; + flex-direction: row; + width: 100%; + + ion-label { + padding: 0 20px 20px; + } + + .horizontal-line { + width: 100%; + top: 0; + border-top: 1px solid #dbdbdb; + } +} diff --git a/src/app/modules/schedule/page/grid/infinite-slides.component.ts b/src/app/modules/schedule/page/grid/infinite-slides.component.ts new file mode 100644 index 00000000..6c653694 --- /dev/null +++ b/src/app/modules/schedule/page/grid/infinite-slides.component.ts @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +import {Component, EventEmitter, Input, Output, ViewChild} from '@angular/core'; +import {IonSlides} from '@ionic/angular'; + +/** + * Component that can display infinite slides + */ +@Component({ + selector: 'stapps-infinite-slides', + templateUrl: 'infinite-slides.html', + styleUrls: ['infinite-slides.scss'], +}) +export class InfiniteSlidesComponent { + /** + * If the view was initialized + */ + initialized = false; + + /** + * Callback for when the page has changed + * + * The caller needs to replace the component here + */ + @Output() pageChangeCallback: EventEmitter<{ + /** + * The current page + */ + currentPage: number; + + /** + * The direction that was scrolled + */ + direction: number; + }> = new EventEmitter(); + + /** + * The virtual page we are currently on + */ + page = 0; + + /** + * Options for IonSlides + */ + @Input() slideOpts = { + initialSlide: 1, + speed: 200, + loop: false, + }; + + /** + * Slider element + */ + @ViewChild('slides') slides: IonSlides; + + /** + * Slide to next page + */ + async nextPage() { + await this.slides.slideNext(this.slideOpts.speed); + } + + /** + * Change page + */ + async onPageChange(direction: number) { + if (!this.initialized) { + // setting the initial page to 1 causes a page change to + // be emitted initially, which intern would cause the + // page to actually change one to far, so we listen for + // that first page change and skip it + this.initialized = true; + + return; + } + + this.page += direction; + + this.pageChangeCallback.emit({ + currentPage: this.page, + direction: direction, + }); + + // tslint:disable-next-line:no-magic-numbers + this.slides.slideTo(1, 0, false).then(); + } + + /** + * Slide to previous page + */ + async prevPage() { + await this.slides.slidePrev(this.slideOpts.speed); + } +} diff --git a/src/app/modules/schedule/page/grid/infinite-slides.html b/src/app/modules/schedule/page/grid/infinite-slides.html new file mode 100644 index 00000000..9ecd649f --- /dev/null +++ b/src/app/modules/schedule/page/grid/infinite-slides.html @@ -0,0 +1,9 @@ + + + diff --git a/src/app/modules/schedule/page/grid/infinite-slides.scss b/src/app/modules/schedule/page/grid/infinite-slides.scss new file mode 100644 index 00000000..0e8b596a --- /dev/null +++ b/src/app/modules/schedule/page/grid/infinite-slides.scss @@ -0,0 +1,4 @@ +ion-slides { + width: 100%; + height: 1100px; // BIG TODO: This is completely bypasses the scale parameter +} diff --git a/src/app/modules/schedule/page/grid/schedule-card.component.ts b/src/app/modules/schedule/page/grid/schedule-card.component.ts new file mode 100644 index 00000000..a52f0918 --- /dev/null +++ b/src/app/modules/schedule/page/grid/schedule-card.component.ts @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {Component, Input, OnInit} from '@angular/core'; +import moment from 'moment'; +import {ScheduleProvider} from '../../schedule.provider'; +import {ScheduleEvent} from '../schema/schema'; + +/** + * Component that can display a schedule event + */ +@Component({ + selector: 'stapps-schedule-card', + templateUrl: 'schedule-card.html', + styleUrls: ['../../../data/list/data-list-item.scss', 'schedule-card.scss'], +}) +export class ScheduleCardComponent implements OnInit { + /** + * The hour from which on the schedule is displayed + */ + @Input() fromHour = 0; + + /** + * Card Y start position + */ + fromY = 0; + + /** + * Card Y end position + */ + height = 0; + + /** + * Show the card without a top offset + */ + @Input() noOffset = false; + + /** + * The scale of the schedule + */ + @Input() scale = 1; + + /** + * The event + */ + @Input() scheduleEvent: ScheduleEvent; + + /** + * The title of the event + */ + title: string; + + constructor(private readonly scheduleProvider: ScheduleProvider) {} + + /** + * Get the note text + */ + getNote(): string | undefined { + return 'categories' in this.scheduleEvent.dateSeries.event + ? this.scheduleEvent.dateSeries.event.categories?.join(', ') + : undefined; + } + + /** + * Initializer + */ + ngOnInit() { + this.fromY = this.noOffset ? 0 : this.scheduleEvent.time.start; + this.height = moment.duration(this.scheduleEvent.time.duration).asHours(); + + this.title = this.scheduleEvent.dateSeries.event.name; + } + + /** + * Remove the event + */ + removeEvent(): false { + if (confirm('Remove event?')) { + this.scheduleProvider.uuids$.next( + this.scheduleProvider.uuids$.value.filter( + it => it !== this.scheduleEvent.dateSeries.uid, + ), + ); + } + + // to prevent event propagation + return false; + } +} diff --git a/src/app/modules/schedule/page/grid/schedule-card.html b/src/app/modules/schedule/page/grid/schedule-card.html new file mode 100644 index 00000000..2db52379 --- /dev/null +++ b/src/app/modules/schedule/page/grid/schedule-card.html @@ -0,0 +1,30 @@ + + + + {{ + this.scheduleEvent.dateSeries.event.name + | nullishCoalesce: this.scheduleEvent.dateSeries.name + }} + + + + + {{ scheduleEvent.dateSeries.frequency }} + until + {{ + scheduleEvent.dateSeries.dates | last | amDateFormat: 'DD. MMM YYYY' + }} + + + + + + {{ getNote() }} + + +
+
diff --git a/src/app/modules/schedule/page/grid/schedule-card.scss b/src/app/modules/schedule/page/grid/schedule-card.scss new file mode 100644 index 00000000..f9ccf726 --- /dev/null +++ b/src/app/modules/schedule/page/grid/schedule-card.scss @@ -0,0 +1,37 @@ +ion-card { + z-index: 2; + + ion-grid { + padding: 0; + margin: 0; + + ion-row { + ion-col { + height: 5px; + padding: 0; + margin: 0; + } + } + } + + ion-card-header { + height: available; + width: 100%; + + ion-card-title { + height: 50px; + width: 100%; + } + } + + div { + position: absolute; + bottom: 0; + height: 20px; + width: 100%; + background: linear-gradient( + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 255) 100% + ); + } +} diff --git a/src/app/modules/schedule/page/grid/schedule-cursor.component.ts b/src/app/modules/schedule/page/grid/schedule-cursor.component.ts new file mode 100644 index 00000000..52cbb3d9 --- /dev/null +++ b/src/app/modules/schedule/page/grid/schedule-cursor.component.ts @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018, 2019 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {Component, Input, OnInit} from '@angular/core'; +import moment from 'moment'; +import {HoursRange} from '../schema/schema'; + +/** + * Component that displays the schedule + */ +@Component({ + selector: 'stapps-schedule-cursor', + templateUrl: 'schedule-cursor.html', + styleUrls: ['schedule-cursor.scss'], +}) +export class ScheduleCursorComponent implements OnInit { + /** + * Cursor update + */ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore unused + private cursorInterval: NodeJS.Timeout; + + /** + * Range of hours to display + */ + @Input() readonly hoursRange: HoursRange; + + /** + * Cursor + */ + now = ScheduleCursorComponent.getCursorTime(); + + /** + * Vertical scale of the schedule (distance between hour lines) + */ + @Input() readonly scale: number; + + /** + * Get a floating point time 0..24 + */ + static getCursorTime(): number { + const mnt = moment(moment.now()); + + const hh = mnt.hours(); + const mm = mnt.minutes(); + + // tslint:disable-next-line:no-magic-numbers + return hh + mm / 60; + } + + /** + * Initialize + */ + ngOnInit() { + this.cursorInterval = setInterval(async () => { + this.now = ScheduleCursorComponent.getCursorTime(); + // tslint:disable-next-line:no-magic-numbers + }, 1000 * 60 /*1 Minute*/); + } +} diff --git a/src/app/modules/schedule/page/grid/schedule-cursor.html b/src/app/modules/schedule/page/grid/schedule-cursor.html new file mode 100644 index 00000000..06048b32 --- /dev/null +++ b/src/app/modules/schedule/page/grid/schedule-cursor.html @@ -0,0 +1,6 @@ +
+
+
+
+
+
diff --git a/src/app/modules/schedule/page/grid/schedule-cursor.scss b/src/app/modules/schedule/page/grid/schedule-cursor.scss new file mode 100644 index 00000000..8654d09b --- /dev/null +++ b/src/app/modules/schedule/page/grid/schedule-cursor.scss @@ -0,0 +1,38 @@ +div { + padding: 0; + margin: 0; + position: absolute; + display: flex; + flex-direction: row; + width: 100%; + top: 0; + z-index: 0; + + div { + width: 100%; + height: fit-content; + + hr { + width: calc(100% - 8px); + position: absolute; + margin-left: 4px; + margin-right: 16px; + margin-top: 8px; + height: 2px; + border-top: 2px solid var(--ion-color-primary); + margin-block-start: 0; + margin-block-end: 0; + } + + div { + width: 8px; + height: 8px; + position: absolute; + top: -3px; + left: -4px; + border-radius: 50% 0 50% 50%; + transform: rotateZ(45deg); + background-color: var(--ion-color-primary); + } + } +} diff --git a/src/app/modules/schedule/page/modal/modal-event-creator.component.ts b/src/app/modules/schedule/page/modal/modal-event-creator.component.ts new file mode 100644 index 00000000..15d70ff0 --- /dev/null +++ b/src/app/modules/schedule/page/modal/modal-event-creator.component.ts @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {Component, Input} from '@angular/core'; +import {SCSearchFilter, SCThingType} from '@openstapps/core'; + +/** + * TODO + */ +@Component({ + selector: 'modal-event-creator', + templateUrl: 'modal-event-creator.html', + styleUrls: ['modal-event-creator.scss'], +}) +export class ModalEventCreatorComponent { + /** + * Action when close is pressed + */ + @Input() dismissAction: () => void; + + /** + * Forced filter + */ + filter: SCSearchFilter = { + arguments: { + field: 'type', + value: SCThingType.AcademicEvent, + }, + type: 'value', + }; +} diff --git a/src/app/modules/schedule/page/modal/modal-event-creator.html b/src/app/modules/schedule/page/modal/modal-event-creator.html new file mode 100644 index 00000000..dad34f9e --- /dev/null +++ b/src/app/modules/schedule/page/modal/modal-event-creator.html @@ -0,0 +1,15 @@ + + {{ + 'schedule.addEventModal.addEvent' | translate + }} + + {{ 'schedule.addEventModal.close' | translate }} + + + + + + diff --git a/src/app/modules/schedule/page/modal/modal-event-creator.scss b/src/app/modules/schedule/page/modal/modal-event-creator.scss new file mode 100644 index 00000000..9bfd914c --- /dev/null +++ b/src/app/modules/schedule/page/modal/modal-event-creator.scss @@ -0,0 +1,17 @@ +ion-card-header { + ion-button { + position: absolute; + right: 0; + top: 0; + } +} + +ion-card-content { + height: 100%; + padding-left: 0; + padding-right: 0; + + stapps-data-list { + height: available; + } +} diff --git a/src/app/modules/schedule/page/schedule-page.component.ts b/src/app/modules/schedule/page/schedule-page.component.ts new file mode 100644 index 00000000..aaaed5c7 --- /dev/null +++ b/src/app/modules/schedule/page/schedule-page.component.ts @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import { + AfterViewInit, + Component, + HostListener, + OnInit, + ViewChild, +} from '@angular/core'; +import {ActivatedRoute} from '@angular/router'; +import {AnimationController, ModalController} from '@ionic/angular'; +import {last} from 'lodash-es'; +import {SharedAxisChoreographer} from '../../../animation/animation-choreographer'; +import {materialSharedAxisX} from '../../../animation/material-motion'; +import {ModalEventCreatorComponent} from './modal/modal-event-creator.component'; +import {ScheduleResponsiveBreakpoint} from './schema/schema'; +import {animate, style, transition, trigger} from '@angular/animations'; + +/** + * This needs to be sorted by break point low -> high + * + * Last entry must have `until: Infinity` + */ +const responsiveConfig: ScheduleResponsiveBreakpoint[] = [ + { + until: 768, + days: 1, + startOf: 'day', + }, + { + until: 1700, + days: 3, + startOf: 'day', + }, + { + until: Number.POSITIVE_INFINITY, + days: 7, + startOf: 'week', + }, +]; + +const fabAnimations = trigger('fabAnimation', [ + transition(':leave', [ + style({opacity: 1, transform: 'translate(0, 0) scale(1)'}), + animate( + '100ms ease-in', + style({opacity: 0, transform: 'translate(-5vw, -5vh) scale(200%)'}), + ), + ]), + transition(':enter', [ + style({opacity: 0, transform: 'translate(-5vw, -5vh) scale(200%)'}), + animate( + '200ms ease-out', + style({opacity: 1, transform: 'translate(0, 0) scale(1)'}), + ), + ]), +]); + +/** + * Component that displays the schedule + */ +@Component({ + selector: 'stapps-schedule-page', + templateUrl: 'schedule-page.html', + styleUrls: ['schedule-page.scss'], + animations: [materialSharedAxisX, fabAnimations], +}) +export class SchedulePageComponent implements OnInit, AfterViewInit { + /** + * Current width of the window + */ + private currentWindowWidth: number = window.innerWidth; + + /** + * Actual Segment Tab + */ + actualSegmentValue?: string | null; + + fabVisible = true; + + /** + * Layout + */ + layout: ScheduleResponsiveBreakpoint = SchedulePageComponent.getDaysToDisplay( + this.currentWindowWidth, + ); + + /** + * Vertical scale of the schedule (distance between hour lines) + */ + scale = 60; + + @ViewChild('segment') segmentView!: HTMLIonSegmentElement; + + /** + * Choreographer for the tab switching + */ + tabChoreographer: SharedAxisChoreographer; + + /** + * Weekly config for schedule + */ + weeklyConfig: ScheduleResponsiveBreakpoint = { + until: Number.POSITIVE_INFINITY, + days: 7, + startOf: 'week', + }; + + /** + * Amount of days that should be shown according to current display width + */ + static getDaysToDisplay(width: number): ScheduleResponsiveBreakpoint { + // the search could be optimized, but probably would have little + // actual effect with five entries. + // we can be sure we get an hit when the last value.until is infinity + // (unless someone has a display that reaches across the universe) + return ( + responsiveConfig.find(value => width < value.until) ?? + responsiveConfig[responsiveConfig.length - 1] + ); + } + + constructor( + private readonly modalController: ModalController, + private readonly activatedRoute: ActivatedRoute, + private readonly animationController: AnimationController, + ) {} + + ngOnInit() { + this.tabChoreographer = new SharedAxisChoreographer( + this.activatedRoute.snapshot.paramMap.get('mode'), + ['calendar', 'recurring', 'single'], + ); + } + + ngAfterViewInit() { + this.segmentView.value = this.tabChoreographer.currentValue; + } + + /** + * Resize callback + * + * Note: this may not fire when the browser transfers from full screen to windowed + * (Firefox & Chrome tested) + */ + @HostListener('window:resize', ['$event']) + onResize(_: UIEvent) { + const current = SchedulePageComponent.getDaysToDisplay( + this.currentWindowWidth, + ); + const next = SchedulePageComponent.getDaysToDisplay(window.innerWidth); + this.currentWindowWidth = window.innerWidth; + + if (current.days === next.days) { + this.layout = next; + } + } + + /** + * When the segment changes + */ + onSegmentChange() { + window.history.replaceState( + {}, + '', + `/#/schedule/${this.segmentView.value}/${last( + window.location.href.split('/'), + )}`, + ); + this.tabChoreographer.changeViewForState(this.segmentView.value); + } + + /** + * Add event modal sheet + */ + async showCreateEventModal() { + this.fabVisible = false; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any,unicorn/consistent-function-scoping + const enterAnimation = (baseElement: any) => { + const backdropAnimation = this.animationController + .create() + .addElement(baseElement.querySelector('.modal-wrapper')) + .fromTo('opacity', '0', 'var(--backdrop-opacity)'); + + const wrapperAnimation = this.animationController + .create() + .addElement(baseElement.querySelector('.modal-wrapper')) + .keyframes([ + { + opacity: '0', + transform: 'translate(30vw, 30vh) scale(0.5)', + }, + { + opacity: '1', + transform: 'translate(0, 0) scale(1)', + }, + ]); + + return this.animationController + .create() + .addElement(baseElement) + .easing('ease-out') + .duration(150) + .addAnimation([backdropAnimation, wrapperAnimation]); + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any,unicorn/consistent-function-scoping + const leaveAnimation = (baseElement: any) => { + return enterAnimation(baseElement).direction('reverse'); + }; + + const modal = await this.modalController.create({ + component: ModalEventCreatorComponent, + swipeToClose: true, + cssClass: 'add-modal', + componentProps: { + dismissAction: () => { + modal.dismiss(); + }, + }, + enterAnimation, + leaveAnimation, + }); + + await modal.present(); + await modal.onWillDismiss(); + + this.fabVisible = true; + } +} diff --git a/src/app/modules/schedule/page/schedule-page.html b/src/app/modules/schedule/page/schedule-page.html new file mode 100644 index 00000000..577e0b90 --- /dev/null +++ b/src/app/modules/schedule/page/schedule-page.html @@ -0,0 +1,57 @@ + + + + + + {{ 'schedule.calendar' | translate }} + + + {{ 'schedule.recurring' | translate }} + + + {{ 'schedule.single' | translate }} + + + + +
+ + + + +
+ + + + + + diff --git a/src/app/modules/schedule/page/schedule-page.scss b/src/app/modules/schedule/page/schedule-page.scss new file mode 100644 index 00000000..ea4d410e --- /dev/null +++ b/src/app/modules/schedule/page/schedule-page.scss @@ -0,0 +1,26 @@ + +ion-header { + padding: 8px; +} + +div { + height: 100%; +} + +.content-container { + display: grid; +} + +.content { + grid-column: 1; + grid-row: 1; +} + +.add-modal { + align-items: flex-end !important; + justify-content: flex-end !important; + + .modal-wrapper { + transform-origin: bottom right !important; + } +} diff --git a/src/app/modules/schedule/page/schedule-single-events.component.ts b/src/app/modules/schedule/page/schedule-single-events.component.ts new file mode 100644 index 00000000..4406ad9a --- /dev/null +++ b/src/app/modules/schedule/page/schedule-single-events.component.ts @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {Component, Input, OnDestroy, OnInit} from '@angular/core'; +import {SCDateSeries, SCUuid} from '@openstapps/core'; +import {flatMap, groupBy, omit, sortBy} from 'lodash-es'; +import moment from 'moment'; +import {Subscription} from 'rxjs'; +import {materialFade} from '../../../animation/material-motion'; +import {ScheduleProvider} from '../schedule.provider'; +import {ScheduleEvent} from './schema/schema'; + +/** + * A single event + */ +export interface ScheduleSingleEvent { + /** + * Day the event is on + */ + day: string; + + /** + * Event the date is referring to + */ + event: ScheduleEvent; +} + +/** + * Component that displays single events one after each other + */ +@Component({ + selector: 'stapps-single-events', + templateUrl: 'schedule-single-events.html', + styleUrls: ['schedule-single-events.scss'], + animations: [materialFade], +}) +export class ScheduleSingleEventsComponent implements OnInit, OnDestroy { + /** + * UUID subscription + */ + private _uuidSubscription: Subscription; + + /** + * The events to display + */ + private uuids: SCUuid[]; + + /** + * Events that are displayed + */ + events: Promise; + + /** + * Scale of the view + */ + @Input() scale = 60; + + /** + * Sorts dates to a list of days with events on each + */ + static groupDateSeriesToDays( + dateSeries: SCDateSeries[], + ): ScheduleSingleEvent[][] { + return sortBy( + groupBy( + flatMap(dateSeries, event => + event.dates.map(date => ({ + dateUnix: moment(date).unix(), + day: moment(date).startOf('day').toISOString(), + event: { + dateSeries: event, + time: { + start: + moment(date).hour() + + moment(date) + // tslint:disable-next-line:no-magic-numbers + .minute() / + 60, + duration: event.duration, + }, + }, + })), + ) + .sort((a, b) => a.dateUnix - b.dateUnix) + .map(event => omit(event, 'dateUnix')), + 'day', + ), + 'day', + ); + } + + constructor(protected readonly scheduleProvider: ScheduleProvider) {} + + /** + * Fetch date series items + */ + async fetchDateSeries(): Promise { + // TODO: only single events + const dateSeries = await this.scheduleProvider.getDateSeries( + this.uuids, + undefined /*TODO*/, + moment(moment.now()).startOf('week').toISOString(), + ); + + // TODO: replace with filter + return ScheduleSingleEventsComponent.groupDateSeriesToDays( + dateSeries.filter(it => it.frequency === 'single'), + ); + } + + /** + * OnDestroy + */ + ngOnDestroy(): void { + this._uuidSubscription.unsubscribe(); + } + + /** + * Initialize + */ + ngOnInit() { + this._uuidSubscription = this.scheduleProvider.uuids$.subscribe( + async result => { + this.uuids = result; + this.events = this.fetchDateSeries(); + }, + ); + } +} diff --git a/src/app/modules/schedule/page/schedule-single-events.html b/src/app/modules/schedule/page/schedule-single-events.html new file mode 100644 index 00000000..958262b8 --- /dev/null +++ b/src/app/modules/schedule/page/schedule-single-events.html @@ -0,0 +1,21 @@ + + + + + {{ day[0].day | amDateFormat: 'LL' }} + + + + {{ event.event.dateSeries.dates[0] | amDateFormat: 'HH:mm' }} + + + + + + + diff --git a/src/app/modules/schedule/page/schedule-single-events.scss b/src/app/modules/schedule/page/schedule-single-events.scss new file mode 100644 index 00000000..0b6c1487 --- /dev/null +++ b/src/app/modules/schedule/page/schedule-single-events.scss @@ -0,0 +1,17 @@ +ion-content { + height: 100%; +} + +.hour-label { + width: fit-content; +} + +.day-label { + padding: 16px; + font-size: large; + font-weight: bold; +} + +.event-card { + width: 100% +} diff --git a/src/app/modules/schedule/page/schedule-single-events.spec.ts b/src/app/modules/schedule/page/schedule-single-events.spec.ts new file mode 100644 index 00000000..b395dc3b --- /dev/null +++ b/src/app/modules/schedule/page/schedule-single-events.spec.ts @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +import {SCDateSeries} from '@openstapps/core'; +import {ScheduleSingleEventsComponent} from './schedule-single-events.component'; +import moment from 'moment'; + +describe('ScheduleSingleEvents', () => { + it('should group date series to days', () => { + const events: Partial[] = [ + { + dates: ['2021-12-24T10:00Z', '2021-12-24T12:00Z'], + duration: 'A', + }, + { + dates: ['2021-12-20T10:00Z'], + duration: 'B', + }, + { + dates: ['2021-12-24T10:15Z'], + duration: 'C', + }, + ]; + + const grouped = ScheduleSingleEventsComponent.groupDateSeriesToDays( + events as SCDateSeries[], + ); + const seriesToDate = (series: Partial, index: number) => { + const time = moment(series.dates?.[index]); + + return { + day: time.clone().startOf('day').toISOString(), + event: { + dateSeries: series as SCDateSeries, + time: { + start: time.hour() + time.minute() / 60, + duration: series.duration as string, + }, + }, + }; + }; + + expect(grouped).toEqual([ + [seriesToDate(events[1], 0)], + [ + seriesToDate(events[0], 0), + seriesToDate(events[2], 0), + seriesToDate(events[0], 1), + ], + ]); + }); +}); diff --git a/src/app/modules/schedule/page/schedule-view.component.ts b/src/app/modules/schedule/page/schedule-view.component.ts new file mode 100644 index 00000000..1fda9ff7 --- /dev/null +++ b/src/app/modules/schedule/page/schedule-view.component.ts @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {Component} from '@angular/core'; +import {ActivatedRoute} from '@angular/router'; +import {Platform} from '@ionic/angular'; +import moment from 'moment'; +import {DateFormatPipe} from 'ngx-moment'; +import { + materialFade, + materialManualFade, + materialSharedAxisX, +} from '../../../animation/material-motion'; +import {ScheduleProvider} from '../schedule.provider'; +import {CalendarViewComponent} from './calendar-view.component'; + +/** + * Component that displays the schedule + */ +@Component({ + selector: 'stapps-schedule-view', + // this is intentional for extending + templateUrl: 'calendar-view.html', + styleUrls: ['calendar-view.scss'], + animations: [materialFade, materialSharedAxisX, materialManualFade], +}) +export class ScheduleViewComponent extends CalendarViewComponent { + /** + * Route Fragment + */ + // @Override + routeFragment = 'schedule/recurring'; + + constructor( + scheduleProvider: ScheduleProvider, + activatedRoute: ActivatedRoute, + datePipe: DateFormatPipe, + platform: Platform, + ) { + super(scheduleProvider, activatedRoute, datePipe, platform); + } + + /** + * Determine displayed dates according to display size + */ + // @Override + determineDisplayDates() { + // let's boldly assume that we at least display one day + + const out = [moment(moment.now()).startOf(this.layout.startOf)]; + for (let i = 1; i < this.layout.days; i++) { + out.push(out[0].clone().add(i, 'day')); + } + + this.displayDates = [out]; + + // void this.mainSlides.slideTo(this.mode === 'schedule' ? 0 : 1, 0, false); + } + + /** + * Load events + */ + // @Override + async loadEvents(): Promise { + this.cardsAnimationState = 'out'; + const dateSeries = await this.scheduleProvider.getDateSeries( + this.uuids, + undefined, + moment(moment.now()).startOf('week').toISOString(), + ); + + this.testSchedule = {}; + + for (const series of dateSeries) { + if (series.dates.length > 0) { + const date = moment(moment.now()) + .startOf('week') + .day(moment(series.dates[0]).day()) + .unix(); + + // fall back to default + (this.testSchedule[date] ?? (this.testSchedule[date] = {}))[ + series.uid + ] = { + dateSeries: series, + time: { + start: moment(series.dates[0]).hours(), + duration: series.duration, + }, + }; + } + } + + this.cursor?.scrollIntoView(); + this.cardsAnimationState = 'in'; + } +} diff --git a/src/app/modules/schedule/page/schedule.service.ts b/src/app/modules/schedule/page/schedule.service.ts new file mode 100644 index 00000000..3e428058 --- /dev/null +++ b/src/app/modules/schedule/page/schedule.service.ts @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2020 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {Injectable} from '@angular/core'; + +/** + * MenuService provides bidirectional communication of context menu options and search queries + */ +@Injectable() +export class ScheduleService {} diff --git a/src/app/modules/schedule/page/schema/schema.ts b/src/app/modules/schedule/page/schema/schema.ts new file mode 100644 index 00000000..27089cec --- /dev/null +++ b/src/app/modules/schedule/page/schema/schema.ts @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2020 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +import {SCDateSeries, SCISO8601Duration} from '@openstapps/core'; +import {unitOfTime} from 'moment'; + +interface DateRange { + duration: SCISO8601Duration; + start: number; +} + +/** + * Minimal interface to provide information about a custom event + */ +export interface ScheduleEvent { + /** + * UUIDs of things related to the event + */ + dateSeries: SCDateSeries; + + /** + * How long the event goes + */ + time: DateRange; +} + +/** + * Range of hours + */ +export interface HoursRange { + /** + * Start hour + */ + from: number; + + /** + * End hour + */ + to: number; +} + +export interface ScheduleResponsiveBreakpoint { + /** + * Number of days to display + */ + days: number; + + /** + * When the first day should start + */ + startOf: unitOfTime.StartOf; + + /** + * Width until next breakpoint is hit + */ + until: number; +} diff --git a/src/app/modules/schedule/schedule.module.ts b/src/app/modules/schedule/schedule.module.ts new file mode 100644 index 00000000..b06c8fe4 --- /dev/null +++ b/src/app/modules/schedule/schedule.module.ts @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2018, 2019 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {CommonModule} from '@angular/common'; +import {NgModule} from '@angular/core'; +import {FormsModule} from '@angular/forms'; +import {RouterModule, Routes} from '@angular/router'; +import {IonicModule} from '@ionic/angular'; +import {TranslateModule} from '@ngx-translate/core'; +import {ScheduleCardComponent} from './page/grid/schedule-card.component'; + +import {DateFormatPipe, MomentModule} from 'ngx-moment'; +import {UtilModule} from '../../util/util.module'; +import {DataModule} from '../data/data.module'; +import {DataProvider} from '../data/data.provider'; +import {CalendarViewComponent} from './page/calendar-view.component'; +import {InfiniteSlidesComponent} from './page/grid/infinite-slides.component'; +import {ScheduleCursorComponent} from './page/grid/schedule-cursor.component'; +import {ModalEventCreatorComponent} from './page/modal/modal-event-creator.component'; +import {SchedulePageComponent} from './page/schedule-page.component'; +import {ScheduleSingleEventsComponent} from './page/schedule-single-events.component'; +import {ScheduleViewComponent} from './page/schedule-view.component'; +import {ScheduleProvider} from './schedule.provider'; + +const settingsRoutes: Routes = [ + {path: 'schedule', redirectTo: 'schedule/calendar/now'}, + {path: 'schedule/calendar', redirectTo: 'schedule/calendar/now'}, + {path: 'schedule/recurring', redirectTo: 'schedule/recurring/now'}, + {path: 'schedule/single', redirectTo: 'schedule/single/now'}, + // calendar | recurring | single + {path: 'schedule/:mode/:date', component: SchedulePageComponent}, +]; + +/** + * Schedule Module + */ +@NgModule({ + declarations: [ + CalendarViewComponent, + InfiniteSlidesComponent, + ModalEventCreatorComponent, + ScheduleCardComponent, + ScheduleCursorComponent, + SchedulePageComponent, + ScheduleSingleEventsComponent, + ScheduleViewComponent, + ], + imports: [ + CommonModule, + FormsModule, + UtilModule, + IonicModule.forRoot(), + TranslateModule.forChild(), + RouterModule.forChild(settingsRoutes), + DataModule, + MomentModule, + ], + providers: [ScheduleProvider, DataProvider, DateFormatPipe], +}) +export class ScheduleModule {} diff --git a/src/app/modules/schedule/schedule.provider.ts b/src/app/modules/schedule/schedule.provider.ts new file mode 100644 index 00000000..a0666399 --- /dev/null +++ b/src/app/modules/schedule/schedule.provider.ts @@ -0,0 +1,195 @@ +/* eslint-disable unicorn/no-null */ +/* + * Copyright (C) 2018-2020 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {Injectable, OnDestroy} from '@angular/core'; +import { + Bounds, + SCDateSeries, + SCISO8601Date, + SCSearchFilter, + SCThingType, + SCUuid, +} from '@openstapps/core'; +import {BehaviorSubject, Subscription} from 'rxjs'; +import {DataProvider} from '../data/data.provider'; + +/** + * Provider for app settings + */ +@Injectable() +export class ScheduleProvider implements OnDestroy { + // tslint:disable:prefer-function-over-method + + /** + * Storage key for event UUIDs + */ + private static uuidStorageKey = 'schedule::event_uuids'; + + private _uuids$?: BehaviorSubject; + + private _uuidSubscription?: Subscription; + + constructor(private readonly dataProvider: DataProvider) { + window.addEventListener('storage', this.storageEventListener.bind(this)); + } + + /** + * Push one or more values to local storage + */ + private static get(key: string): T[] { + const item = localStorage.getItem(key); + if (item == undefined) { + return []; + } + + return JSON.parse(item) as T[]; + } + + /** + * Push one or more values to local storage + */ + private static set(key: string, item: T[]) { + const newValue = JSON.stringify(item); + // prevent feedback loop from storageEvent -> _uuids$.next() -> set -> storageEvent + if (newValue !== localStorage.getItem(key)) { + localStorage.setItem(key, newValue); + } + } + + /** + * TODO + */ + public get uuids$(): BehaviorSubject { + if (!this._uuids$) { + this._uuids$ = new BehaviorSubject( + ScheduleProvider.get(ScheduleProvider.uuidStorageKey), + ); + this._uuidSubscription = this._uuids$.subscribe(result => { + ScheduleProvider.set(ScheduleProvider.uuidStorageKey, result); + }); + } + + return this._uuids$; + } + + /** + * Listen to updates in local storage + */ + private storageEventListener(event: StorageEvent) { + if ( + event.newValue && + event.storageArea === localStorage && + event.key === ScheduleProvider.uuidStorageKey + ) { + this._uuids$?.next(JSON.parse(event.newValue)); + } + } + + /** + * Load Date Series + */ + async getDateSeries( + uuids: SCUuid[], + frequencies?: Array<'single' | 'weekly' | 'biweekly'>, + from?: SCISO8601Date | 'now', + to?: SCISO8601Date | 'now', + ): Promise { + if (uuids.length === 0) { + return []; + } + + const filters: SCSearchFilter[] = [ + { + arguments: { + field: 'type', + value: SCThingType.DateSeries, + }, + type: 'value', + }, + { + arguments: { + filters: uuids.map(uid => ({ + arguments: { + field: 'uid', + value: uid, + }, + type: 'value', + })), + operation: 'or', + }, + type: 'boolean', + }, + ]; + + if (frequencies) { + filters.push({ + arguments: { + filters: frequencies.map(frequency => ({ + arguments: { + field: 'frequency', + value: frequency, + }, + type: 'value', + })), + operation: 'or', + }, + type: 'boolean', + }); + } + + if (from || to) { + const bounds: Bounds = {}; + if (from) { + bounds.lowerBound = { + limit: from, + mode: 'inclusive', + }; + } + if (to) { + bounds.upperBound = { + limit: to, + mode: 'inclusive', + }; + } + filters.push({ + arguments: { + field: 'dates', + bounds: bounds, + }, + type: 'date range', + }); + } + + return ( + await this.dataProvider.search({ + filter: { + arguments: { + filters: filters, + operation: 'and', + }, + type: 'boolean', + }, + }) + ).data as SCDateSeries[]; + } + + /** + * TODO + */ + ngOnDestroy(): void { + this._uuidSubscription?.unsubscribe(); + window.removeEventListener('storage', this.storageEventListener.bind(this)); + } +} diff --git a/src/app/modules/storage/storage.provider.spec.ts b/src/app/modules/storage/storage.provider.spec.ts index 98d1fa15..d967135b 100644 --- a/src/app/modules/storage/storage.provider.spec.ts +++ b/src/app/modules/storage/storage.provider.spec.ts @@ -135,7 +135,7 @@ describe('StorageProvider', () => { spyOn(storage, 'clear').and.callThrough(); await storageProvider.putMultiple(sampleEntries); let entries = await storageProvider.getAll(); - expect([...entries.values()].length).toBe(3); + expect([...entries.values()].length).not.toBe(0); await storageProvider.deleteAll(); entries = await storageProvider.getAll(); diff --git a/src/app/util/array-last.pipe.ts b/src/app/util/array-last.pipe.ts new file mode 100644 index 00000000..c056925a --- /dev/null +++ b/src/app/util/array-last.pipe.ts @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +import {Injectable, Pipe, PipeTransform} from '@angular/core'; +import {last} from 'lodash-es'; + +/** + * Get the last value of an array + */ +@Injectable() +@Pipe({ + name: 'last', + pure: true, +}) +export class ArrayLastPipe implements PipeTransform { + /** + * Transform + */ + // tslint:disable-next-line:prefer-function-over-method + transform(value: T[]): T | undefined { + return last(value); + } +} diff --git a/src/app/util/date-is-today.pipe.ts b/src/app/util/date-is-today.pipe.ts new file mode 100644 index 00000000..335af897 --- /dev/null +++ b/src/app/util/date-is-today.pipe.ts @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +import {Injectable, Pipe, PipeTransform} from '@angular/core'; +import moment, {Moment, unitOfTime} from 'moment'; + +/** + * Get the last value of an array + */ +@Injectable() +@Pipe({ + name: 'dateIsThis', + pure: false, // pure pipe can break in some change detection scenarios, + // specifically, on the calendar view it causes it to stay true even when you navigate +}) +export class DateIsThisPipe implements PipeTransform { + /** + * Transform + */ + // tslint:disable-next-line:prefer-function-over-method + transform(value: Moment, granularity: unitOfTime.StartOf): boolean { + return value.isSame(moment(moment.now()), granularity); + } +} diff --git a/src/app/util/nullish-coalecing.pipe.ts b/src/app/util/nullish-coalecing.pipe.ts new file mode 100644 index 00000000..064ae803 --- /dev/null +++ b/src/app/util/nullish-coalecing.pipe.ts @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +import {Injectable, Pipe, PipeTransform} from '@angular/core'; + +/** + * Get the last value of an array + */ +@Injectable() +@Pipe({ + name: 'nullishCoalesce', + pure: true, +}) +export class NullishCoalescingPipe implements PipeTransform { + /** + * Transform + */ + // tslint:disable-next-line:prefer-function-over-method + transform(value: T, fallback: G): T | G { + return value ?? fallback; + } +} diff --git a/src/app/util/util.module.ts b/src/app/util/util.module.ts new file mode 100644 index 00000000..7e63af06 --- /dev/null +++ b/src/app/util/util.module.ts @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +import {NgModule} from '@angular/core'; +import {ArrayLastPipe} from './array-last.pipe'; +import {DateIsThisPipe} from './date-is-today.pipe'; +import {NullishCoalescingPipe} from './nullish-coalecing.pipe'; + +@NgModule({ + declarations: [ArrayLastPipe, DateIsThisPipe, NullishCoalescingPipe], + exports: [ArrayLastPipe, DateIsThisPipe, NullishCoalescingPipe], +}) +export class UtilModule {} diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 00d04034..546b0b9c 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -1,4 +1,6 @@ { + "ok": "ok", + "abort": "abbrechen", "app": { "ui": { "CLOSE": "Schließen" @@ -118,6 +120,22 @@ "search": { "nothing_found": "Keine Ergebnisse" }, + "schedule": { + "recurring": "Stundenplan", + "calendar": "Kalender", + "single": "Einzeltermine", + "addEventModal": { + "close": "Schließen", + "addEvent": "Events Hinzufügen" + } + }, + "chips": { + "addEvent": { + "addEvent": "Event hinzufügen", + "addedToEvents": "Event hinzugefügt", + "pastEvent": "Event ist vorbei" + } + }, "settings": { "resetAlert.title": "Alle Einstellungen zurücksetzen?", "resetAlert.message": "Sind Sie sich sicher, alle Einstellungen auf ihre Anfangswerte zurückzusetzen?", diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 9b120bde..d01337f2 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1,4 +1,6 @@ { + "ok": "ok", + "abort": "abort", "app": { "ui": { "CLOSE": "Close" @@ -118,6 +120,22 @@ "search": { "nothing_found": "No results" }, + "schedule": { + "recurring": "Recurring", + "calendar": "Calendar", + "single": "Single Events", + "addEventModal": { + "close": "close", + "addEvent": "Add Events" + } + }, + "chips": { + "addEvent": { + "addEvent": "Add event", + "addedToEvents": "Added to events", + "pastEvent": "Event is over" + } + }, "settings": { "resetAlert.title": "Reset all settings?", "resetAlert.message": "Are you sure to reset all settings to their default values?",