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?",