mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-22 01:22:54 +00:00
feat: calendar plugin
This commit is contained in:
committed by
Rainer Killinger
parent
080e6fa3e8
commit
a57c3029df
4
.gitignore
vendored
4
.gitignore
vendored
@@ -12,6 +12,10 @@ log.txt
|
||||
.vscode/*
|
||||
npm-debug.log*
|
||||
|
||||
# This file is sometimes created automatically, even though
|
||||
# we actually use the capacitor.config.ts
|
||||
capacitor.config.json
|
||||
|
||||
.idea/
|
||||
.ionic/
|
||||
.sourcemaps/
|
||||
|
||||
14
PITFALLS.md
14
PITFALLS.md
@@ -18,6 +18,20 @@ in line `var distributionUrl = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_
|
||||
'https\\://services.gradle.org/distributions/gradle-4.1-all.zip';`
|
||||
* Repeat this for file `StudioBuilder.js`
|
||||
|
||||
#### Problem
|
||||
|
||||
`android.support... not found` on build
|
||||
|
||||
#### Solution
|
||||
|
||||
```
|
||||
npm install jetifier
|
||||
npx jetify
|
||||
npx cap sync android
|
||||
```
|
||||
|
||||
[more here](https://stackoverflow.com/questions/62195760/ionic-capacitor-build-cannot-find-symbol-android-support-v4-app-activitycompat)
|
||||
|
||||
### Run platform iOS
|
||||
#### Problem
|
||||
|
||||
|
||||
@@ -12,11 +12,17 @@ dependencies {
|
||||
implementation project(':capacitor-community-http')
|
||||
implementation project(':capacitor-app')
|
||||
implementation project(':capacitor-browser')
|
||||
implementation project(':capacitor-device')
|
||||
implementation project(':capacitor-dialog')
|
||||
implementation project(':capacitor-filesystem')
|
||||
implementation project(':capacitor-haptics')
|
||||
implementation project(':capacitor-keyboard')
|
||||
implementation project(':capacitor-local-notifications')
|
||||
implementation project(':capacitor-share')
|
||||
implementation project(':capacitor-splash-screen')
|
||||
implementation project(':capacitor-status-bar')
|
||||
implementation project(':capacitor-storage')
|
||||
implementation project(':transistorsoft-capacitor-background-fetch')
|
||||
implementation project(':capacitor-secure-storage-plugin')
|
||||
implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
||||
implementation "androidx.appcompat:appcompat:1.3.1"
|
||||
|
||||
@@ -15,5 +15,8 @@
|
||||
"SplashScreen": "screen",
|
||||
"SplashScreenDelay": "3000"
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"LocalNotifications": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,18 @@
|
||||
"pkg": "@capacitor/browser",
|
||||
"classpath": "com.capacitorjs.plugins.browser.BrowserPlugin"
|
||||
},
|
||||
{
|
||||
"pkg": "@capacitor/device",
|
||||
"classpath": "com.capacitorjs.plugins.device.DevicePlugin"
|
||||
},
|
||||
{
|
||||
"pkg": "@capacitor/dialog",
|
||||
"classpath": "com.capacitorjs.plugins.dialog.DialogPlugin"
|
||||
},
|
||||
{
|
||||
"pkg": "@capacitor/filesystem",
|
||||
"classpath": "com.capacitorjs.plugins.filesystem.FilesystemPlugin"
|
||||
},
|
||||
{
|
||||
"pkg": "@capacitor/haptics",
|
||||
"classpath": "com.capacitorjs.plugins.haptics.HapticsPlugin"
|
||||
@@ -19,6 +31,14 @@
|
||||
"pkg": "@capacitor/keyboard",
|
||||
"classpath": "com.capacitorjs.plugins.keyboard.KeyboardPlugin"
|
||||
},
|
||||
{
|
||||
"pkg": "@capacitor/local-notifications",
|
||||
"classpath": "com.capacitorjs.plugins.localnotifications.LocalNotificationsPlugin"
|
||||
},
|
||||
{
|
||||
"pkg": "@capacitor/share",
|
||||
"classpath": "com.capacitorjs.plugins.share.SharePlugin"
|
||||
},
|
||||
{
|
||||
"pkg": "@capacitor/splash-screen",
|
||||
"classpath": "com.capacitorjs.plugins.splashscreen.SplashScreenPlugin"
|
||||
@@ -31,6 +51,10 @@
|
||||
"pkg": "@capacitor/storage",
|
||||
"classpath": "com.capacitorjs.plugins.storage.StoragePlugin"
|
||||
},
|
||||
{
|
||||
"pkg": "@transistorsoft/capacitor-background-fetch",
|
||||
"classpath": "com.transistorsoft.bgfetch.capacitor.BackgroundFetchPlugin"
|
||||
},
|
||||
{
|
||||
"pkg": "capacitor-secure-storage-plugin",
|
||||
"classpath": "com.whitestein.securestorage.SecureStoragePlugin"
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
<widget version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||
<access origin="*" />
|
||||
|
||||
<feature name="Calendar">
|
||||
<param name="android-package" value="nl.xservices.plugins.Calendar"/>
|
||||
</feature>
|
||||
|
||||
<feature name="Device">
|
||||
<param name="android-package" value="org.apache.cordova.device.Device"/>
|
||||
</feature>
|
||||
|
||||
@@ -21,6 +21,10 @@ allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
// https://github.com/transistorsoft/capacitor-background-fetch/blob/master/example/android/build.gradle
|
||||
maven {
|
||||
url("${project(':transistorsoft-capacitor-background-fetch').projectDir}/libs")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,12 +11,27 @@ project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/
|
||||
include ':capacitor-browser'
|
||||
project(':capacitor-browser').projectDir = new File('../node_modules/@capacitor/browser/android')
|
||||
|
||||
include ':capacitor-device'
|
||||
project(':capacitor-device').projectDir = new File('../node_modules/@capacitor/device/android')
|
||||
|
||||
include ':capacitor-dialog'
|
||||
project(':capacitor-dialog').projectDir = new File('../node_modules/@capacitor/dialog/android')
|
||||
|
||||
include ':capacitor-filesystem'
|
||||
project(':capacitor-filesystem').projectDir = new File('../node_modules/@capacitor/filesystem/android')
|
||||
|
||||
include ':capacitor-haptics'
|
||||
project(':capacitor-haptics').projectDir = new File('../node_modules/@capacitor/haptics/android')
|
||||
|
||||
include ':capacitor-keyboard'
|
||||
project(':capacitor-keyboard').projectDir = new File('../node_modules/@capacitor/keyboard/android')
|
||||
|
||||
include ':capacitor-local-notifications'
|
||||
project(':capacitor-local-notifications').projectDir = new File('../node_modules/@capacitor/local-notifications/android')
|
||||
|
||||
include ':capacitor-share'
|
||||
project(':capacitor-share').projectDir = new File('../node_modules/@capacitor/share/android')
|
||||
|
||||
include ':capacitor-splash-screen'
|
||||
project(':capacitor-splash-screen').projectDir = new File('../node_modules/@capacitor/splash-screen/android')
|
||||
|
||||
@@ -26,5 +41,8 @@ project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacit
|
||||
include ':capacitor-storage'
|
||||
project(':capacitor-storage').projectDir = new File('../node_modules/@capacitor/storage/android')
|
||||
|
||||
include ':transistorsoft-capacitor-background-fetch'
|
||||
project(':transistorsoft-capacitor-background-fetch').projectDir = new File('../node_modules/@transistorsoft/capacitor-background-fetch/android')
|
||||
|
||||
include ':capacitor-secure-storage-plugin'
|
||||
project(':capacitor-secure-storage-plugin').projectDir = new File('../node_modules/capacitor-secure-storage-plugin/android')
|
||||
|
||||
@@ -18,6 +18,11 @@ const config: CapacitorConfig = {
|
||||
'SplashScreenDelay': '3000',
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
LocalNotifications: {
|
||||
// TODO
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
0
gradle.properties
Normal file
0
gradle.properties
Normal file
@@ -366,7 +366,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 8C99SX84P9;
|
||||
DEVELOPMENT_TEAM = QN788YUV45;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
@@ -388,7 +388,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 8C99SX84P9;
|
||||
DEVELOPMENT_TEAM = QN788YUV45;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSCalendarsUsageDescription</key>
|
||||
<string>App uses calendar for schedule sync</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
|
||||
@@ -15,5 +15,11 @@
|
||||
"SplashScreen": "screen",
|
||||
"SplashScreenDelay": "3000"
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"LocalNotifications": {}
|
||||
},
|
||||
"server": {
|
||||
"url": "http://141.2.95.77:8100"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
<widget version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||
<access origin="*" />
|
||||
|
||||
<feature name="Calendar">
|
||||
<param name="ios-package" value="Calendar"/>
|
||||
</feature>
|
||||
|
||||
<feature name="Device">
|
||||
<param name="ios-package" value="CDVDevice"/>
|
||||
</feature>
|
||||
|
||||
@@ -12,11 +12,17 @@ def capacitor_pods
|
||||
pod 'CapacitorCommunityHttp', :path => '../../node_modules/@capacitor-community/http'
|
||||
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
|
||||
pod 'CapacitorBrowser', :path => '../../node_modules/@capacitor/browser'
|
||||
pod 'CapacitorDevice', :path => '../../node_modules/@capacitor/device'
|
||||
pod 'CapacitorDialog', :path => '../../node_modules/@capacitor/dialog'
|
||||
pod 'CapacitorFilesystem', :path => '../../node_modules/@capacitor/filesystem'
|
||||
pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics'
|
||||
pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard'
|
||||
pod 'CapacitorLocalNotifications', :path => '../../node_modules/@capacitor/local-notifications'
|
||||
pod 'CapacitorShare', :path => '../../node_modules/@capacitor/share'
|
||||
pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen'
|
||||
pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
|
||||
pod 'CapacitorStorage', :path => '../../node_modules/@capacitor/storage'
|
||||
pod 'TransistorsoftCapacitorBackgroundFetch', :path => '../../node_modules/@transistorsoft/capacitor-background-fetch'
|
||||
pod 'CapacitorSecureStoragePlugin', :path => '../../node_modules/capacitor-secure-storage-plugin'
|
||||
pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins'
|
||||
pod 'CordovaPluginsResources', :path => '../capacitor-cordova-ios-plugins'
|
||||
|
||||
717
package-lock.json
generated
717
package-lock.json
generated
@@ -703,6 +703,22 @@
|
||||
"resolved": "https://registry.npmjs.org/@asymmetrik/ngx-leaflet-markercluster/-/ngx-leaflet-markercluster-5.0.1.tgz",
|
||||
"integrity": "sha512-XqRSgMKZN/670/nRXtjZ98fniuCzvQnZ5EDEOr+coEC4OI0OYeDIvAN253C5U8kJX6hIdzDtQcbOrC1milWUBw=="
|
||||
},
|
||||
"@awesome-cordova-plugins/calendar": {
|
||||
"version": "5.37.1",
|
||||
"resolved": "https://registry.npmjs.org/@awesome-cordova-plugins/calendar/-/calendar-5.37.1.tgz",
|
||||
"integrity": "sha512-sYnUXNC+sAcIm5RHP2Z0rmf0NA9ZhVxe4yr0dZ14jpzJfyrfGm6gTbKIBFjaZCBpMDSM2aofdHtzJ1Ps4hD7mw==",
|
||||
"requires": {
|
||||
"@types/cordova": "^0.0.34"
|
||||
}
|
||||
},
|
||||
"@awesome-cordova-plugins/core": {
|
||||
"version": "5.37.1",
|
||||
"resolved": "https://registry.npmjs.org/@awesome-cordova-plugins/core/-/core-5.37.1.tgz",
|
||||
"integrity": "sha512-7ySdolkR27NQ55pSA75TEwCbgVJL0EL0EBVcWfhwqfE5gAFt8vycGiyCrhBN9/ijFKxi/7NFHZEosX/WGNMQlA==",
|
||||
"requires": {
|
||||
"@types/cordova": "^0.0.34"
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.16.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
|
||||
@@ -2175,10 +2191,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@capacitor/filesystem": {
|
||||
"@capacitor/device": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/filesystem/-/filesystem-1.1.0.tgz",
|
||||
"integrity": "sha512-8O3UuvL8HNUEJvZnmn8yUmvgB1evtXfcF0oxIo3YbSlylqywJwS3JTiuhKmsvSxCdpbTy8IaTsutVh3gZgWbKg=="
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/device/-/device-1.1.0.tgz",
|
||||
"integrity": "sha512-HCFwOxmK7igEgNm20y+zYi+XQ0OlZYnE4oCaI82TGmA7sehlDpBBKbjmI2Bd8aM09+BXFbAAtq7JCxkEfY8nIg=="
|
||||
},
|
||||
"@capacitor/dialog": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/dialog/-/dialog-1.0.6.tgz",
|
||||
"integrity": "sha512-IzfiJv1Lxl+jnT+P6ky2Lj16qMEYssih69wFH+0lXM6NSyGht/RniBHmQ1hVuXIkw8FDT9o+a5wvYhm3svwfAw=="
|
||||
},
|
||||
"@capacitor/filesystem": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/filesystem/-/filesystem-1.0.6.tgz",
|
||||
"integrity": "sha512-8xqUbDZFGBMhgqoBSn9wEd9OBPdHIRegQ9zCCZcpHNf3FFAIby1ck+aDFnoq+Da49xhD6ks1SKCBSxz/26qWTw=="
|
||||
},
|
||||
"@capacitor/haptics": {
|
||||
"version": "1.1.3",
|
||||
@@ -2196,6 +2222,16 @@
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-1.1.3.tgz",
|
||||
"integrity": "sha512-WpD1f/3HH6IpADiRaFTDGdhrqYhZDikybXXhUdGAEEwHbErHt9zS5RQgbeROjGmkXcurVvQsalQ59YuKU0VzwA=="
|
||||
},
|
||||
"@capacitor/local-notifications": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/local-notifications/-/local-notifications-1.0.9.tgz",
|
||||
"integrity": "sha512-6znL6l2gDj1IS05yWLyo7ENYmid89rmNTQmQxURrJLdkn98AGDNrFlYoApo7+Hfjbb3cpGX4LNKPh+uWgeaBnw=="
|
||||
},
|
||||
"@capacitor/share": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/share/-/share-1.0.7.tgz",
|
||||
"integrity": "sha512-v7FRld2SdV64YjrZrKGoDyfYqcoEC2I4tk6nkhbOI8ZOaqm6XNiqCWEeTdeb6XPwDftozmfILSzhCxbASrXKMg=="
|
||||
},
|
||||
"@capacitor/splash-screen": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/splash-screen/-/splash-screen-1.1.6.tgz",
|
||||
@@ -2631,6 +2667,30 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@ionic-native/file": {
|
||||
"version": "5.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@ionic-native/file/-/file-5.36.0.tgz",
|
||||
"integrity": "sha512-x7yZ4VdC8n8FNlpRmUFtohNlOZnExvoxZ/6oCvGsV+ec8TJXUsDK/BYi1g+lkPTCUY3EmQIeBOe4PLO6fRJ7qg==",
|
||||
"requires": {
|
||||
"@types/cordova": "^0.0.34"
|
||||
}
|
||||
},
|
||||
"@ionic-native/file-opener": {
|
||||
"version": "5.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@ionic-native/file-opener/-/file-opener-5.36.0.tgz",
|
||||
"integrity": "sha512-UKp3pbqvQXsAtLMJ5JE+KcTMxpjSZMFebf6nvy/KJvwy85JGIaCV4ZVM/H9CFUrHJMWBH6wDbY+WPygnsrl4Yg==",
|
||||
"requires": {
|
||||
"@types/cordova": "^0.0.34"
|
||||
}
|
||||
},
|
||||
"@ionic-native/file-transfer": {
|
||||
"version": "5.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@ionic-native/file-transfer/-/file-transfer-5.36.0.tgz",
|
||||
"integrity": "sha512-n4kwLiPMCGvLwNDaj66Va8NWvflRJzk69RBWQSAUmQ6Hf2gE87NxLCvuvH9YRwFbFcBgEciGWzlEiacVLxx9mg==",
|
||||
"requires": {
|
||||
"@types/cordova": "^0.0.34"
|
||||
}
|
||||
},
|
||||
"@ionic-native/geolocation": {
|
||||
"version": "5.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@ionic-native/geolocation/-/geolocation-5.36.0.tgz",
|
||||
@@ -2926,6 +2986,95 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@ionic/cli": {
|
||||
"version": "6.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/cli/-/cli-6.18.1.tgz",
|
||||
"integrity": "sha512-EIV4zln0xpI2O4kADXZCBkLsX/NIkbqjTAJOlsH7BrsPLo20e3LULQiX9rxiX20YK7ssz/0Sae1s70XTsHnTaQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@ionic/cli-framework": "5.1.0",
|
||||
"@ionic/cli-framework-output": "2.2.2",
|
||||
"@ionic/cli-framework-prompts": "2.1.8",
|
||||
"@ionic/utils-array": "2.1.5",
|
||||
"@ionic/utils-fs": "3.1.5",
|
||||
"@ionic/utils-network": "2.1.5",
|
||||
"@ionic/utils-process": "2.1.8",
|
||||
"@ionic/utils-stream": "3.1.5",
|
||||
"@ionic/utils-subprocess": "2.1.8",
|
||||
"@ionic/utils-terminal": "2.3.1",
|
||||
"chalk": "^4.0.0",
|
||||
"debug": "^4.0.0",
|
||||
"diff": "^4.0.1",
|
||||
"elementtree": "^0.1.7",
|
||||
"leek": "0.0.24",
|
||||
"lodash": "^4.17.5",
|
||||
"open": "^7.0.4",
|
||||
"os-name": "^4.0.0",
|
||||
"semver": "^7.1.1",
|
||||
"split2": "^3.0.0",
|
||||
"ssh-config": "^1.1.1",
|
||||
"stream-combiner2": "^1.1.1",
|
||||
"superagent": "^5.2.1",
|
||||
"superagent-proxy": "^3.0.0",
|
||||
"tar": "^6.0.1",
|
||||
"tslib": "^2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"is-docker": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
|
||||
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
|
||||
"dev": true
|
||||
},
|
||||
"open": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
|
||||
"integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-docker": "^2.0.0",
|
||||
"is-wsl": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@ionic/cli-framework": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/cli-framework/-/cli-framework-5.1.0.tgz",
|
||||
"integrity": "sha512-Hb/P2zuHB3zQZN0qG7Lxda8IlP2mHisfb0KR+wc9cw2BSiH+rtXRd/A4JxndPznjWs00PHbWiEm0Ehas2pA/nw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@ionic/cli-framework-output": "2.2.2",
|
||||
"@ionic/utils-array": "2.1.5",
|
||||
"@ionic/utils-fs": "3.1.5",
|
||||
"@ionic/utils-object": "2.1.5",
|
||||
"@ionic/utils-process": "2.1.8",
|
||||
"@ionic/utils-stream": "3.1.5",
|
||||
"@ionic/utils-subprocess": "2.1.8",
|
||||
"@ionic/utils-terminal": "2.3.1",
|
||||
"chalk": "^4.0.0",
|
||||
"debug": "^4.0.0",
|
||||
"lodash": "^4.17.5",
|
||||
"minimist": "^1.2.0",
|
||||
"rimraf": "^3.0.0",
|
||||
"tslib": "^2.0.1",
|
||||
"write-file-atomic": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@ionic/cli-framework-output": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/cli-framework-output/-/cli-framework-output-2.2.2.tgz",
|
||||
@@ -2945,6 +3094,47 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@ionic/cli-framework-prompts": {
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/cli-framework-prompts/-/cli-framework-prompts-2.1.8.tgz",
|
||||
"integrity": "sha512-DjO4lQsmvficsZbPmpGqSSx+F1BfgSTQBwRqL5bl9Dkh9rIZ/ckcJcKqCciVOw9kIY7WTeNFOTwj2vWrkFn7+Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@ionic/utils-terminal": "2.3.1",
|
||||
"debug": "^4.0.0",
|
||||
"inquirer": "^7.0.0",
|
||||
"tslib": "^2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"inquirer": {
|
||||
"version": "7.3.3",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz",
|
||||
"integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-escapes": "^4.2.1",
|
||||
"chalk": "^4.1.0",
|
||||
"cli-cursor": "^3.1.0",
|
||||
"cli-width": "^3.0.0",
|
||||
"external-editor": "^3.0.3",
|
||||
"figures": "^3.0.0",
|
||||
"lodash": "^4.17.19",
|
||||
"mute-stream": "0.0.8",
|
||||
"run-async": "^2.4.0",
|
||||
"rxjs": "^6.6.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"through": "^2.3.6"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.7.0.tgz",
|
||||
@@ -3035,6 +3225,24 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@ionic/utils-network": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/utils-network/-/utils-network-2.1.5.tgz",
|
||||
"integrity": "sha512-HUQ1Ec4Mh2MXzzKdbbbDS6xYKwpFJ2XRY7SYXbaZT8+jiNahfHbsOfe62/p8bk41Yil7E9EagzGC2JvIFJh01w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "^4.0.0",
|
||||
"tslib": "^2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@ionic/utils-object": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/utils-object/-/utils-object-2.1.5.tgz",
|
||||
@@ -3848,6 +4056,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
|
||||
"integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="
|
||||
},
|
||||
"@transistorsoft/capacitor-background-fetch": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@transistorsoft/capacitor-background-fetch/-/capacitor-background-fetch-0.0.6.tgz",
|
||||
"integrity": "sha512-MnaPPuEzEty8jjnrd2blrifTG++/DrLeAcyxn8r42VYWD7p5Gv3xbNxOM81XOsLj1GpTJ571+msq8R7Z+gdqFg=="
|
||||
},
|
||||
"@trysound/sax": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
|
||||
@@ -3967,6 +4180,11 @@
|
||||
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/cordova": {
|
||||
"version": "0.0.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
|
||||
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
|
||||
},
|
||||
"@types/cors": {
|
||||
"version": "2.8.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
|
||||
@@ -6957,6 +7175,12 @@
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"cookiejar": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz",
|
||||
"integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"copy-anything": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz",
|
||||
@@ -7036,6 +7260,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"cordova-plugin-calendar": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/cordova-plugin-calendar/-/cordova-plugin-calendar-5.1.5.tgz",
|
||||
"integrity": "sha512-Qrz+Yo3ifpCsi0CWfLFrnP37Tgp3jxDmtNKILhU+f3g2EsbWwLc7VsKq8zCrxJihGr/vKpEoSFtWZGpEM3zobQ=="
|
||||
},
|
||||
"cordova-plugin-device": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cordova-plugin-device/-/cordova-plugin-device-2.0.3.tgz",
|
||||
@@ -7637,6 +7866,12 @@
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"data-uri-to-buffer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz",
|
||||
"integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==",
|
||||
"dev": true
|
||||
},
|
||||
"date-format": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.3.tgz",
|
||||
@@ -7845,6 +8080,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"degenerator": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/degenerator/-/degenerator-3.0.1.tgz",
|
||||
"integrity": "sha512-LFsIFEeLPlKvAKXu7j3ssIG6RT0TbI7/GhsqrI0DnHASEQjXQ0LUSYcjJteGgRGmZbl1TnMSxpNQIAiJ7Du5TQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ast-types": "^0.13.2",
|
||||
"escodegen": "^1.8.1",
|
||||
"esprima": "^4.0.0",
|
||||
"vm2": "^3.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"ast-types": {
|
||||
"version": "0.13.4",
|
||||
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
|
||||
"integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"del": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz",
|
||||
@@ -9422,6 +9686,12 @@
|
||||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
|
||||
},
|
||||
"fast-safe-stringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
|
||||
"dev": true
|
||||
},
|
||||
"fastq": {
|
||||
"version": "1.13.0",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
|
||||
@@ -9592,6 +9862,12 @@
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"formidable": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz",
|
||||
"integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==",
|
||||
"dev": true
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -9691,6 +9967,36 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ftp": {
|
||||
"version": "0.3.10",
|
||||
"resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz",
|
||||
"integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"readable-stream": "1.1.x",
|
||||
"xregexp": "2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.1",
|
||||
"isarray": "0.0.1",
|
||||
"string_decoder": "~0.10.x"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
@@ -9872,6 +10178,54 @@
|
||||
"pump": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"get-uri": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/get-uri/-/get-uri-3.0.2.tgz",
|
||||
"integrity": "sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@tootallnate/once": "1",
|
||||
"data-uri-to-buffer": "3",
|
||||
"debug": "4",
|
||||
"file-uri-to-path": "2",
|
||||
"fs-extra": "^8.1.0",
|
||||
"ftp": "^0.3.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"file-uri-to-path": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz",
|
||||
"integrity": "sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==",
|
||||
"dev": true
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"get-value": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
|
||||
@@ -11407,12 +11761,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"jetifier": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jetifier/-/jetifier-2.0.0.tgz",
|
||||
"integrity": "sha512-J4Au9KuT74te+PCCCHKgAjyLlEa+2VyIAEPNCdE5aNkAJ6FAJcAqcdzEkSnzNksIa9NkGmC4tPiClk2e7tCJuQ==",
|
||||
"dev": true
|
||||
},
|
||||
"joi": {
|
||||
"version": "17.5.0",
|
||||
"resolved": "https://registry.npmjs.org/joi/-/joi-17.5.0.tgz",
|
||||
@@ -11937,6 +12285,34 @@
|
||||
"npm-ci": "0.0.2"
|
||||
}
|
||||
},
|
||||
"leek": {
|
||||
"version": "0.0.24",
|
||||
"resolved": "https://registry.npmjs.org/leek/-/leek-0.0.24.tgz",
|
||||
"integrity": "sha1-5ADlfw5g2O8r1NBo3EKKVDRdvNo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "^2.1.0",
|
||||
"lodash.assign": "^3.2.0",
|
||||
"rsvp": "^3.0.21"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"less": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/less/-/less-4.1.1.tgz",
|
||||
@@ -12219,18 +12595,97 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||
},
|
||||
"lodash._baseassign": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz",
|
||||
"integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash._basecopy": "^3.0.0",
|
||||
"lodash.keys": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lodash._basecopy": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz",
|
||||
"integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._bindcallback": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz",
|
||||
"integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._createassigner": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz",
|
||||
"integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash._bindcallback": "^3.0.0",
|
||||
"lodash._isiterateecall": "^3.0.0",
|
||||
"lodash.restparam": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lodash._getnative": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
|
||||
"integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._isiterateecall": {
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz",
|
||||
"integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.assign": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz",
|
||||
"integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash._baseassign": "^3.0.0",
|
||||
"lodash._createassigner": "^3.0.0",
|
||||
"lodash.keys": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.isarguments": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
||||
"integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.isarray": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz",
|
||||
"integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.ismatch": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz",
|
||||
"integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.keys": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
|
||||
"integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash._getnative": "^3.0.0",
|
||||
"lodash.isarguments": "^3.0.0",
|
||||
"lodash.isarray": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
@@ -12242,6 +12697,12 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
|
||||
},
|
||||
"lodash.restparam": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz",
|
||||
"integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.truncate": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
|
||||
@@ -13058,6 +13519,12 @@
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"dev": true
|
||||
},
|
||||
"netmask": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
|
||||
"integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==",
|
||||
"dev": true
|
||||
},
|
||||
"netrc": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/netrc/-/netrc-0.1.4.tgz",
|
||||
@@ -13666,6 +14133,47 @@
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"dev": true
|
||||
},
|
||||
"pac-proxy-agent": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-5.0.0.tgz",
|
||||
"integrity": "sha512-CcFG3ZtnxO8McDigozwE3AqAw15zDvGH+OjXO4kzf7IkEKkQ4gxQ+3sdF50WmhQ4P/bVusXcqNE2S3XrNURwzQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@tootallnate/once": "1",
|
||||
"agent-base": "6",
|
||||
"debug": "4",
|
||||
"get-uri": "3",
|
||||
"http-proxy-agent": "^4.0.1",
|
||||
"https-proxy-agent": "5",
|
||||
"pac-resolver": "^5.0.0",
|
||||
"raw-body": "^2.2.0",
|
||||
"socks-proxy-agent": "5"
|
||||
},
|
||||
"dependencies": {
|
||||
"socks-proxy-agent": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"agent-base": "^6.0.2",
|
||||
"debug": "4",
|
||||
"socks": "^2.3.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pac-resolver": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-5.0.0.tgz",
|
||||
"integrity": "sha512-H+/A6KitiHNNW+bxBKREk2MCGSxljfqRX76NjummWEYIat7ldVXRU3dhRIE3iXZ0nvGBk6smv3nntxKkzRL8NA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"degenerator": "^3.0.1",
|
||||
"ip": "^1.1.5",
|
||||
"netmask": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"pacote": {
|
||||
"version": "11.3.5",
|
||||
"resolved": "https://registry.npmjs.org/pacote/-/pacote-11.3.5.tgz",
|
||||
@@ -15958,6 +16466,56 @@
|
||||
"ipaddr.js": "1.9.1"
|
||||
}
|
||||
},
|
||||
"proxy-agent": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-5.0.0.tgz",
|
||||
"integrity": "sha512-gkH7BkvLVkSfX9Dk27W6TyNOWWZWRilRfk1XxGNWOYJ2TuedAv1yFpCaU9QSBmBe716XOTNpYNOzhysyw8xn7g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"agent-base": "^6.0.0",
|
||||
"debug": "4",
|
||||
"http-proxy-agent": "^4.0.0",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"lru-cache": "^5.1.1",
|
||||
"pac-proxy-agent": "^5.0.0",
|
||||
"proxy-from-env": "^1.0.0",
|
||||
"socks-proxy-agent": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"socks-proxy-agent": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"agent-base": "^6.0.2",
|
||||
"debug": "4",
|
||||
"socks": "^2.3.3"
|
||||
}
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"dev": true
|
||||
},
|
||||
"prr": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||
@@ -16744,6 +17302,12 @@
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"rsvp": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz",
|
||||
"integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==",
|
||||
"dev": true
|
||||
},
|
||||
"run-async": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
|
||||
@@ -17674,6 +18238,12 @@
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||
},
|
||||
"ssh-config": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/ssh-config/-/ssh-config-1.1.6.tgz",
|
||||
"integrity": "sha512-ZPO9rECxzs5JIQ6G/2EfL1I9ho/BVZkx9HRKn8+0af7QgwAmumQ7XBFP1ggMyPMo+/tUbmv0HFdv4qifdO/9JA==",
|
||||
"dev": true
|
||||
},
|
||||
"sshpk": {
|
||||
"version": "1.17.0",
|
||||
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz",
|
||||
@@ -17849,6 +18419,54 @@
|
||||
"through": "~2.3.4"
|
||||
}
|
||||
},
|
||||
"stream-combiner2": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz",
|
||||
"integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"duplexer2": "~0.1.0",
|
||||
"readable-stream": "^2.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
|
||||
"dev": true
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"streamroller": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.2.tgz",
|
||||
@@ -17995,6 +18613,54 @@
|
||||
"resolved": "https://registry.npmjs.org/suncalc/-/suncalc-1.8.0.tgz",
|
||||
"integrity": "sha1-HZiYEJVjB4dQ9JlKlZ5lTYdqy/U="
|
||||
},
|
||||
"superagent": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-5.3.1.tgz",
|
||||
"integrity": "sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"component-emitter": "^1.3.0",
|
||||
"cookiejar": "^2.1.2",
|
||||
"debug": "^4.1.1",
|
||||
"fast-safe-stringify": "^2.0.7",
|
||||
"form-data": "^3.0.0",
|
||||
"formidable": "^1.2.2",
|
||||
"methods": "^1.1.2",
|
||||
"mime": "^2.4.6",
|
||||
"qs": "^6.9.4",
|
||||
"readable-stream": "^3.6.0",
|
||||
"semver": "^7.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
|
||||
"integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"superagent-proxy": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/superagent-proxy/-/superagent-proxy-3.0.0.tgz",
|
||||
"integrity": "sha512-wAlRInOeDFyd9pyonrkJspdRAxdLrcsZ6aSnS+8+nu4x1aXbz6FWSTT9M6Ibze+eG60szlL7JA8wEIV7bPWuyQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "^4.3.2",
|
||||
"proxy-agent": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
@@ -18893,6 +19559,15 @@
|
||||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
|
||||
"dev": true
|
||||
},
|
||||
"typedarray-to-buffer": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
|
||||
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-typedarray": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.3.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
|
||||
@@ -19204,6 +19879,12 @@
|
||||
"resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz",
|
||||
"integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w=="
|
||||
},
|
||||
"vm2": {
|
||||
"version": "3.9.5",
|
||||
"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.5.tgz",
|
||||
"integrity": "sha512-LuCAHZN75H9tdrAiLFf030oW7nJV5xwNMuk1ymOZwopmuK3d2H4L1Kv4+GFHgarKiLfXXLFU+7LDABHnwOkWng==",
|
||||
"dev": true
|
||||
},
|
||||
"void-elements": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
|
||||
@@ -20079,6 +20760,18 @@
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"write-file-atomic": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
|
||||
"integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"imurmurhash": "^0.1.4",
|
||||
"is-typedarray": "^1.0.0",
|
||||
"signal-exit": "^3.0.2",
|
||||
"typedarray-to-buffer": "^3.1.5"
|
||||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
|
||||
@@ -20129,6 +20822,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"xregexp": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz",
|
||||
"integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=",
|
||||
"dev": true
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
|
||||
17
package.json
17
package.json
@@ -54,18 +54,28 @@
|
||||
"@angular/router": "12.2.13",
|
||||
"@asymmetrik/ngx-leaflet": "8.1.0",
|
||||
"@asymmetrik/ngx-leaflet-markercluster": "5.0.1",
|
||||
"@awesome-cordova-plugins/calendar": "5.37.1",
|
||||
"@awesome-cordova-plugins/core": "5.37.1",
|
||||
"@capacitor-community/http": "1.4.1",
|
||||
"@capacitor/app": "1.0.6",
|
||||
"@capacitor/browser": "1.0.6",
|
||||
"@capacitor/core": "3.3.1",
|
||||
"@capacitor/device": "1.1.0",
|
||||
"@capacitor/dialog": "1.0.6",
|
||||
"@capacitor/filesystem": "1.0.6",
|
||||
"@capacitor/haptics": "1.1.3",
|
||||
"@capacitor/keyboard": "1.1.3",
|
||||
"@capacitor/local-notifications": "1.0.9",
|
||||
"@capacitor/share": "1.0.7",
|
||||
"@capacitor/splash-screen": "1.1.6",
|
||||
"@capacitor/status-bar": "1.0.6",
|
||||
"@capacitor/storage": "1.2.3",
|
||||
"@ionic-native/core": "5.36.0",
|
||||
"@ionic-native/diagnostic": "5.36.0",
|
||||
"@ionic-native/dialogs": "5.36.0",
|
||||
"@ionic-native/file": "5.36.0",
|
||||
"@ionic-native/file-opener": "5.36.0",
|
||||
"@ionic-native/file-transfer": "5.36.0",
|
||||
"@ionic-native/geolocation": "5.36.0",
|
||||
"@ionic-native/network": "5.36.0",
|
||||
"@ionic/angular": "5.7.0",
|
||||
@@ -75,7 +85,9 @@
|
||||
"@openstapps/api": "0.38.0",
|
||||
"@openstapps/configuration": "0.29.0",
|
||||
"@openstapps/core": "0.63.0",
|
||||
"@transistorsoft/capacitor-background-fetch": "0.0.6",
|
||||
"capacitor-secure-storage-plugin": "0.6.2",
|
||||
"cordova-plugin-calendar": "5.1.5",
|
||||
"cordova-plugin-device": "2.0.3",
|
||||
"cordova-plugin-dialogs": "2.0.2",
|
||||
"cordova-plugin-geolocation": "4.1.0",
|
||||
@@ -121,6 +133,7 @@
|
||||
"@capacitor/ios": "3.3.2",
|
||||
"@compodoc/compodoc": "1.1.14",
|
||||
"@ionic/angular-toolkit": "4.0.0",
|
||||
"@ionic/cli": "6.18.1",
|
||||
"@types/deepmerge": "2.2.0",
|
||||
"@types/form-data": "2.5.0",
|
||||
"@types/jasmine": "3.9.1",
|
||||
@@ -142,7 +155,6 @@
|
||||
"is-docker": "1.1.0",
|
||||
"jasmine-core": "3.9.0",
|
||||
"jasmine-spec-reporter": "7.0.0",
|
||||
"jetifier": "2.0.0",
|
||||
"karma": "6.3.4",
|
||||
"karma-chrome-launcher": "3.1.0",
|
||||
"karma-coverage-istanbul-reporter": "3.0.3",
|
||||
@@ -159,6 +171,9 @@
|
||||
"cordova": {
|
||||
"plugins": {
|
||||
"cordova-plugin-whitelist": {},
|
||||
"cordova-plugin-file-transfer": {
|
||||
"ANDROID_SUPPORT_V4_VERSION": "27.+"
|
||||
},
|
||||
"cordova-plugin-device": {},
|
||||
"cordova-plugin-geolocation": {
|
||||
"GEOLOCATION_USAGE_DESCRIPTION": "The app will use your location to provide features for navigation or distances information.",
|
||||
|
||||
@@ -27,6 +27,7 @@ import {ConfigProvider} from './modules/config/config.provider';
|
||||
import {SettingsProvider} from './modules/settings/settings.provider';
|
||||
import {NGXLogger} from 'ngx-logger';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
import {ScheduleSyncService} from './modules/background/schedule/schedule-sync.service';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let platformReadySpy: any;
|
||||
@@ -36,6 +37,7 @@ describe('AppComponent', () => {
|
||||
let settingsProvider: jasmine.SpyObj<SettingsProvider>;
|
||||
let configProvider: jasmine.SpyObj<ConfigProvider>;
|
||||
let ngxLogger: jasmine.SpyObj<NGXLogger>;
|
||||
let scheduleSyncServiceSpy: jasmine.SpyObj<ScheduleSyncService>;
|
||||
|
||||
let platformIsSpy;
|
||||
|
||||
@@ -59,6 +61,10 @@ describe('AppComponent', () => {
|
||||
'provideSetting',
|
||||
'setCategoriesOrder',
|
||||
]);
|
||||
scheduleSyncServiceSpy = jasmine.createSpyObj('ScheduleSyncService', [
|
||||
'getDifferences',
|
||||
'postDifferencesNotification',
|
||||
]);
|
||||
configProvider = jasmine.createSpyObj('ConfigProvider', ['init']);
|
||||
ngxLogger = jasmine.createSpyObj('NGXLogger', ['log', 'error', 'warn']);
|
||||
|
||||
@@ -73,6 +79,7 @@ describe('AppComponent', () => {
|
||||
{provide: Platform, useValue: platformSpy},
|
||||
{provide: TranslateService, useValue: translateServiceSpy},
|
||||
{provide: ThingTranslateService, useValue: thingTranslateServiceSpy},
|
||||
{provide: ScheduleSyncService, useValue: scheduleSyncServiceSpy},
|
||||
{provide: SettingsProvider, useValue: settingsProvider},
|
||||
{provide: ConfigProvider, useValue: configProvider},
|
||||
{provide: NGXLogger, useValue: ngxLogger},
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, NgZone} from '@angular/core';
|
||||
import {AfterContentInit, Component, NgZone} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {App, URLOpenListenerEvent} from '@capacitor/app';
|
||||
import {SplashScreen} from '@capacitor/splash-screen';
|
||||
@@ -24,6 +24,7 @@ import {PAIAAuthService} from './modules/auth/paia/paia-auth.service';
|
||||
import {DefaultAuthService} from './modules/auth/default-auth.service';
|
||||
import {environment} from '../environments/environment';
|
||||
import {AuthHelperService} from './modules/auth/auth-helper.service';
|
||||
import {ScheduleSyncService} from './modules/background/schedule/schedule-sync.service';
|
||||
|
||||
/**
|
||||
* TODO
|
||||
@@ -32,7 +33,7 @@ import {AuthHelperService} from './modules/auth/auth-helper.service';
|
||||
selector: 'app-root',
|
||||
templateUrl: 'app.component.html',
|
||||
})
|
||||
export class AppComponent {
|
||||
export class AppComponent implements AfterContentInit {
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
@@ -59,6 +60,7 @@ export class AppComponent {
|
||||
* @param paiaAuth Auth Service
|
||||
* @param authHelperService Helper service for OAuth providers
|
||||
* @param toastController Toast controller
|
||||
* @param scheduleSync TODO
|
||||
*/
|
||||
constructor(
|
||||
private readonly platform: Platform,
|
||||
@@ -71,10 +73,15 @@ export class AppComponent {
|
||||
private readonly paiaAuth: PAIAAuthService,
|
||||
private readonly authHelperService: AuthHelperService,
|
||||
private readonly toastController: ToastController,
|
||||
private readonly scheduleSync: ScheduleSyncService,
|
||||
) {
|
||||
void this.initializeApp();
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
void this.scheduleSync.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
|
||||
@@ -63,6 +63,8 @@ import {DebugDataCollectorService} from './modules/data/debug-data-collector.ser
|
||||
import {Browser} from './util/browser.factory';
|
||||
import {browserFactory} from './util/browser.factory';
|
||||
import {AuthModule} from './modules/auth/auth.module';
|
||||
import {ScheduleSyncService} from './modules/background/schedule/schedule-sync.service';
|
||||
import {BackgroundModule} from './modules/background/background.module';
|
||||
import {LibraryModule} from './modules/library/library.module';
|
||||
|
||||
registerLocaleData(localeDe);
|
||||
@@ -134,7 +136,13 @@ const providers: Provider[] = [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
deps: [NGXLogger, SettingsProvider, ConfigProvider, TranslateService],
|
||||
deps: [
|
||||
NGXLogger,
|
||||
SettingsProvider,
|
||||
ConfigProvider,
|
||||
TranslateService,
|
||||
ScheduleSyncService,
|
||||
],
|
||||
useFactory: initSettingsFactory,
|
||||
},
|
||||
];
|
||||
@@ -149,6 +157,7 @@ const providers: Provider[] = [
|
||||
AboutModule,
|
||||
AppRoutingModule,
|
||||
AuthModule,
|
||||
BackgroundModule,
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
CatalogModule,
|
||||
|
||||
39
src/app/modules/background/background.module.ts
Normal file
39
src/app/modules/background/background.module.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 Licens for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {NgModule} from '@angular/core';
|
||||
import {ScheduleSyncService} from './schedule/schedule-sync.service';
|
||||
import {DateFormatPipe, DurationPipe} from 'ngx-moment';
|
||||
import {CalendarModule} from '../calendar/calendar.module';
|
||||
import {ScheduleProvider} from '../calendar/schedule.provider';
|
||||
import {StorageProvider} from '../storage/storage.provider';
|
||||
import {CalendarService} from '../calendar/calendar.service';
|
||||
|
||||
/**
|
||||
* Schedule Module
|
||||
*/
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [CalendarModule],
|
||||
providers: [
|
||||
DurationPipe,
|
||||
DateFormatPipe,
|
||||
ScheduleProvider,
|
||||
StorageProvider,
|
||||
CalendarService,
|
||||
ScheduleSyncService,
|
||||
],
|
||||
})
|
||||
export class BackgroundModule {}
|
||||
20
src/app/modules/background/schedule/changes.ts
Normal file
20
src/app/modules/background/schedule/changes.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 Licens for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export interface ChangesOf<T, P extends Partial<T>> {
|
||||
new: T;
|
||||
old?: P;
|
||||
changes: Array<keyof P>;
|
||||
}
|
||||
28
src/app/modules/background/schedule/hash.ts
Normal file
28
src/app/modules/background/schedule/hash.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 Licens for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function hashStringToInt(string_: string): number {
|
||||
return [...string_].reduce(
|
||||
(accumulator, current) =>
|
||||
current.charCodeAt(0) +
|
||||
(accumulator << 6) +
|
||||
(accumulator << 16) -
|
||||
accumulator,
|
||||
0,
|
||||
);
|
||||
}
|
||||
201
src/app/modules/background/schedule/schedule-sync.service.ts
Normal file
201
src/app/modules/background/schedule/schedule-sync.service.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* 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 Licens for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Injectable, OnDestroy} from '@angular/core';
|
||||
import {
|
||||
DateSeriesRelevantData,
|
||||
dateSeriesRelevantKeys,
|
||||
formatRelevantKeys,
|
||||
ScheduleProvider,
|
||||
} from '../../calendar/schedule.provider';
|
||||
import {SCDateSeries, SCThingType, SCUuid} from '@openstapps/core';
|
||||
import {Device} from '@capacitor/device';
|
||||
import {LocalNotifications} from '@capacitor/local-notifications';
|
||||
import {ThingTranslateService} from '../../../translation/thing-translate.service';
|
||||
import {DateFormatPipe, DurationPipe} from 'ngx-moment';
|
||||
import {BackgroundFetch} from '@transistorsoft/capacitor-background-fetch';
|
||||
import {StorageProvider} from '../../storage/storage.provider';
|
||||
import {CalendarService} from '../../calendar/calendar.service';
|
||||
import {flatMap} from 'lodash-es';
|
||||
import {toICal} from '../../calendar/ical/ical';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {ChangesOf} from './changes';
|
||||
import {hashStringToInt} from './hash';
|
||||
import {
|
||||
CALENDAR_NOTIFICATIONS_ENABLED_KEY,
|
||||
CALENDAR_SYNC_ENABLED_KEY,
|
||||
CALENDAR_SYNC_SETTINGS_KEY,
|
||||
} from '../../settings/page/calendar-sync-settings-keys';
|
||||
|
||||
@Injectable()
|
||||
export class ScheduleSyncService implements OnDestroy {
|
||||
constructor(
|
||||
private scheduleProvider: ScheduleProvider,
|
||||
private storageProvider: StorageProvider,
|
||||
private translator: ThingTranslateService,
|
||||
private dateFormatPipe: DateFormatPipe,
|
||||
private durationFormatPipe: DurationPipe,
|
||||
private calendar: CalendarService,
|
||||
) {
|
||||
this.scheduleProvider.uuids$.subscribe(uuids => {
|
||||
this.uuids = uuids;
|
||||
void this.syncNativeCalendar();
|
||||
});
|
||||
}
|
||||
|
||||
uuids: SCUuid[];
|
||||
|
||||
uuidSubscription: Subscription;
|
||||
|
||||
ngOnDestroy() {
|
||||
this.uuidSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
private async isSyncEnabled(): Promise<boolean> {
|
||||
return await this.storageProvider.get(
|
||||
`${CALENDAR_SYNC_SETTINGS_KEY}.${CALENDAR_SYNC_ENABLED_KEY}`,
|
||||
);
|
||||
}
|
||||
|
||||
private async isNotificationsEnabled(): Promise<boolean> {
|
||||
return await this.storageProvider.get(
|
||||
`${CALENDAR_SYNC_SETTINGS_KEY}.${CALENDAR_NOTIFICATIONS_ENABLED_KEY}`,
|
||||
);
|
||||
}
|
||||
|
||||
async enable() {
|
||||
if ((await Device.getInfo()).platform === 'web') return;
|
||||
|
||||
await BackgroundFetch.stop();
|
||||
|
||||
if (
|
||||
[this.isSyncEnabled, this.isNotificationsEnabled].some(
|
||||
async it => await it(),
|
||||
)
|
||||
) {
|
||||
const status = await BackgroundFetch.configure(
|
||||
{
|
||||
minimumFetchInterval: 15,
|
||||
stopOnTerminate: false,
|
||||
enableHeadless: true,
|
||||
},
|
||||
async taskId => {
|
||||
await Promise.all([
|
||||
this.postDifferencesNotification(),
|
||||
this.syncNativeCalendar(),
|
||||
]);
|
||||
|
||||
await BackgroundFetch.finish(taskId);
|
||||
},
|
||||
);
|
||||
|
||||
if (status !== BackgroundFetch.STATUS_AVAILABLE) {
|
||||
if (status === BackgroundFetch.STATUS_DENIED) {
|
||||
console.error(
|
||||
'The user explicitly disabled background behavior for this app or for the whole system.',
|
||||
);
|
||||
} else if (status === BackgroundFetch.STATUS_RESTRICTED) {
|
||||
console.error(
|
||||
'Background updates are unavailable and the user cannot enable them again.',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.info('Starting background fetch.');
|
||||
|
||||
await BackgroundFetch.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getDifferences(): Promise<
|
||||
ChangesOf<SCDateSeries, DateSeriesRelevantData>[]
|
||||
> {
|
||||
const partialEvents = this.scheduleProvider.partialEvents$.getValue();
|
||||
|
||||
const result = (
|
||||
await this.scheduleProvider.getDateSeries(partialEvents.map(it => it.uid))
|
||||
).dates;
|
||||
|
||||
return result
|
||||
.map(it => ({
|
||||
new: it,
|
||||
old: partialEvents.find(partialEvent => partialEvent.uid === it.uid),
|
||||
}))
|
||||
.map(it => ({
|
||||
...it,
|
||||
changes: it.old
|
||||
? (Object.keys(it.old) as Array<keyof DateSeriesRelevantData>).filter(
|
||||
key =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
JSON.stringify(it.old![key]) !== JSON.stringify(it.new[key]),
|
||||
)
|
||||
: dateSeriesRelevantKeys,
|
||||
}));
|
||||
}
|
||||
|
||||
private formatChanges(
|
||||
changes: ChangesOf<SCDateSeries, DateSeriesRelevantData>,
|
||||
): string[] {
|
||||
return changes.changes.map(
|
||||
change =>
|
||||
`${
|
||||
this.translator.translator.translatedPropertyNames<SCDateSeries>(
|
||||
SCThingType.DateSeries,
|
||||
)?.[change]
|
||||
}: ${formatRelevantKeys[change](
|
||||
changes.new[change] as never,
|
||||
this.dateFormatPipe,
|
||||
this.durationFormatPipe,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
async syncNativeCalendar() {
|
||||
if (!(await this.isSyncEnabled())) return;
|
||||
|
||||
const dateSeries = (await this.scheduleProvider.getDateSeries(this.uuids))
|
||||
.dates;
|
||||
|
||||
const events = flatMap(dateSeries, event =>
|
||||
toICal(event, this.translator.translator, {
|
||||
allowRRuleExceptions: false,
|
||||
excludeCancelledEvents: true,
|
||||
}),
|
||||
);
|
||||
|
||||
return this.calendar.syncEvents(events);
|
||||
}
|
||||
|
||||
async postDifferencesNotification() {
|
||||
if (!(await this.isNotificationsEnabled())) return;
|
||||
|
||||
const differences = (await this.getDifferences()).filter(
|
||||
it => it.changes.length > 0,
|
||||
);
|
||||
if (differences.length === 0) return;
|
||||
|
||||
if ((await Device.getInfo()).platform === 'web') {
|
||||
// TODO: Implement web notification
|
||||
} else {
|
||||
await LocalNotifications.schedule({
|
||||
notifications: differences.map(it => ({
|
||||
title: it.new.event.name,
|
||||
body: this.formatChanges(it).join('\n'),
|
||||
id: hashStringToInt(it.new.uid),
|
||||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
226
src/app/modules/calendar/add-event-review-modal.component.ts
Normal file
226
src/app/modules/calendar/add-event-review-modal.component.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 Licens for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {
|
||||
getICalExport,
|
||||
getNativeCalendarExport,
|
||||
ICalEvent,
|
||||
serializeICal,
|
||||
toICal,
|
||||
toICalUpdates,
|
||||
} from './ical/ical';
|
||||
import moment from 'moment';
|
||||
import {Share} from '@capacitor/share';
|
||||
import {Directory, Encoding, Filesystem} from '@capacitor/filesystem';
|
||||
import {Device} from '@capacitor/device';
|
||||
import {CalendarService} from './calendar.service';
|
||||
import {Dialog} from '@capacitor/dialog';
|
||||
import {SCDateSeries} from '@openstapps/core';
|
||||
import {ThingTranslateService} from '../../translation/thing-translate.service';
|
||||
import {TranslateService} from '@ngx-translate/core';
|
||||
import {NewShareData, NewShareNavigator} from './new-share';
|
||||
|
||||
interface ICalInfo {
|
||||
title: string;
|
||||
events: ICalEvent[];
|
||||
cancelledEvents: ICalEvent[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'add-event-review-modal',
|
||||
templateUrl: 'add-event-review-modal.html',
|
||||
styleUrls: ['add-event-review-modal.scss'],
|
||||
})
|
||||
export class AddEventReviewModalComponent implements OnInit {
|
||||
moment = moment;
|
||||
|
||||
@Input() dismissAction: () => void;
|
||||
|
||||
@Input() dateSeries: SCDateSeries[];
|
||||
|
||||
iCalEvents: ICalInfo[];
|
||||
|
||||
includeCancelled = true;
|
||||
|
||||
isWeb = true;
|
||||
|
||||
constructor(
|
||||
readonly calendarService: CalendarService,
|
||||
readonly translator: ThingTranslateService,
|
||||
readonly translateService: TranslateService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
Device.getInfo().then(it => {
|
||||
this.isWeb = it.platform === 'web';
|
||||
});
|
||||
|
||||
this.iCalEvents = this.dateSeries.map(event => ({
|
||||
title:
|
||||
this.translator.translator.translatedAccess(event).event.name() ??
|
||||
'error',
|
||||
events: toICal(event, this.translator.translator, {
|
||||
allowRRuleExceptions: true,
|
||||
excludeCancelledEvents: false,
|
||||
}),
|
||||
cancelledEvents: toICalUpdates(event, this.translator.translator),
|
||||
}));
|
||||
}
|
||||
|
||||
async toCalendar() {
|
||||
await Dialog.confirm({
|
||||
title: this.translateService.instant(
|
||||
'schedule.toCalendar.reviewModal.dialogs.toCalendarConfirm.TITLE',
|
||||
),
|
||||
message: this.translateService.instant(
|
||||
'schedule.toCalendar.reviewModal.dialogs.toCalendarConfirm.DESCRIPTION',
|
||||
),
|
||||
});
|
||||
|
||||
await this.calendarService.syncEvents(
|
||||
getNativeCalendarExport(this.dateSeries, this.translator.translator),
|
||||
);
|
||||
|
||||
this.dismissAction();
|
||||
}
|
||||
|
||||
async download() {
|
||||
const blob = new Blob(
|
||||
[
|
||||
serializeICal(
|
||||
getICalExport(
|
||||
this.dateSeries,
|
||||
this.translator.translator,
|
||||
this.includeCancelled,
|
||||
),
|
||||
),
|
||||
],
|
||||
{
|
||||
type: 'text/calendar',
|
||||
},
|
||||
);
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${
|
||||
this.dateSeries.length === 1
|
||||
? this.dateSeries[0].event.name
|
||||
: 'stapps_calendar'
|
||||
}.ics`;
|
||||
a.click();
|
||||
}
|
||||
|
||||
async export() {
|
||||
const info = await Device.getInfo();
|
||||
|
||||
if (info.platform === 'web') {
|
||||
const blob = new Blob(
|
||||
[
|
||||
serializeICal(
|
||||
getICalExport(
|
||||
this.dateSeries,
|
||||
this.translator.translator,
|
||||
this.includeCancelled,
|
||||
),
|
||||
),
|
||||
],
|
||||
{
|
||||
type: 'text/calendar',
|
||||
},
|
||||
);
|
||||
const file = new File([blob], 'calendar.ics', {type: blob.type});
|
||||
const shareData: NewShareData = {
|
||||
files: [file],
|
||||
title: this.translateService.instant(
|
||||
'schedule.toCalendar.reviewModal.shareData.TITLE',
|
||||
),
|
||||
text: this.translateService.instant(
|
||||
'schedule.toCalendar.reviewModal.shareData.TEXT',
|
||||
),
|
||||
};
|
||||
|
||||
if (!(navigator as unknown as NewShareNavigator).canShare) {
|
||||
return Dialog.alert({
|
||||
title: this.translateService.instant(
|
||||
'schedule.toCalendar.reviewModal.dialogs.cannotShare.TITLE',
|
||||
),
|
||||
message: this.translateService.instant(
|
||||
'schedule.toCalendar.reviewModal.dialogs.cannotShare.DESCRIPTION',
|
||||
),
|
||||
});
|
||||
}
|
||||
console.log(
|
||||
(navigator as unknown as NewShareNavigator).canShare(shareData),
|
||||
);
|
||||
|
||||
if (!(navigator as unknown as NewShareNavigator).canShare(shareData)) {
|
||||
return Dialog.alert({
|
||||
title: this.translateService.instant(
|
||||
'schedule.toCalendar.reviewModal.dialogs.unsupportedFileType.TITLE',
|
||||
),
|
||||
message: this.translateService.instant(
|
||||
'schedule.toCalendar.reviewModal.dialogs.unsupportedFileType.DESCRIPTION',
|
||||
),
|
||||
});
|
||||
}
|
||||
try {
|
||||
await (navigator as unknown as NewShareNavigator).share(shareData);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return Dialog.alert({
|
||||
title: this.translateService.instant(
|
||||
'schedule.toCalendar.reviewModal.dialogs.failedShare.TITLE',
|
||||
),
|
||||
message: this.translateService.instant(
|
||||
'schedule.toCalendar.reviewModal.dialogs.failedShare.DESCRIPTION',
|
||||
),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const result = await Filesystem.writeFile({
|
||||
path: `${
|
||||
this.dateSeries.length === 1
|
||||
? this.dateSeries[0].event.name
|
||||
: this.translateService.instant(
|
||||
'schedule.toCalendar.reviewModal.shareData.FILE_TYPE',
|
||||
)
|
||||
}.ics`,
|
||||
data: serializeICal(
|
||||
getICalExport(
|
||||
this.dateSeries,
|
||||
this.translator.translator,
|
||||
this.includeCancelled,
|
||||
),
|
||||
),
|
||||
encoding: Encoding.UTF8,
|
||||
directory: Directory.Cache,
|
||||
});
|
||||
|
||||
await Share.share({
|
||||
title: this.translateService.instant(
|
||||
'schedule.toCalendar.reviewModal.shareData.TITLE',
|
||||
),
|
||||
text: this.translateService.instant(
|
||||
'schedule.toCalendar.reviewModal.shareData.TEXT',
|
||||
),
|
||||
url: result.uri,
|
||||
dialogTitle: this.translateService.instant(
|
||||
'schedule.toCalendar.reviewModal.shareData.TITLE',
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
81
src/app/modules/calendar/add-event-review-modal.html
Normal file
81
src/app/modules/calendar/add-event-review-modal.html
Normal file
@@ -0,0 +1,81 @@
|
||||
<!--
|
||||
~ Copyright (C) 2022 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 Licens for
|
||||
~ more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along with
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<div>
|
||||
<ion-card-header>
|
||||
<ion-card-title>{{
|
||||
'schedule.toCalendar.reviewModal.TITLE' | translate
|
||||
}}</ion-card-title>
|
||||
<ion-button fill="clear" (click)="dismissAction()">
|
||||
<ion-label>{{ 'modal.DISMISS' | translate }}</ion-label>
|
||||
</ion-button>
|
||||
</ion-card-header>
|
||||
|
||||
<ion-card-content>
|
||||
<ion-list lines="none">
|
||||
<ion-item-group *ngFor="let event of iCalEvents">
|
||||
<ion-item-divider>
|
||||
<ion-label>{{ event.title }}</ion-label>
|
||||
<ion-note slot="start" *ngIf="event.events.length > 1">
|
||||
<ion-icon name="warning"></ion-icon>
|
||||
</ion-note>
|
||||
</ion-item-divider>
|
||||
<ion-item *ngFor="let iCalEvent of event.events">
|
||||
<ion-label>
|
||||
<s *ngIf="iCalEvent.cancelled; else date"
|
||||
><ng-container [ngTemplateOutlet]="date"></ng-container>
|
||||
</s>
|
||||
<ng-template #date>
|
||||
{{ moment(iCalEvent.start) | amDateFormat: 'll, HH:mm' }}
|
||||
</ng-template>
|
||||
</ion-label>
|
||||
<ion-note *ngIf="iCalEvent.rrule">
|
||||
{{ iCalEvent.rrule.interval }}
|
||||
{{ iCalEvent.rrule.freq | sentencecase }}
|
||||
</ion-note>
|
||||
<ion-icon *ngIf="iCalEvent.rrule" name="repeat"></ion-icon>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</ion-list>
|
||||
</ion-card-content>
|
||||
|
||||
<div class="horizontal-flex">
|
||||
<ion-item lines="none">
|
||||
<ion-label>{{
|
||||
'schedule.toCalendar.reviewModal.INCLUDE_CANCELLED' | translate
|
||||
}}</ion-label>
|
||||
<ion-checkbox [(ngModel)]="includeCancelled" slot="end"></ion-checkbox>
|
||||
</ion-item>
|
||||
</div>
|
||||
<div class="horizontal-flex">
|
||||
<ion-button fill="clear" (click)="export()">
|
||||
{{ 'share' | translate }}
|
||||
<ion-icon slot="end" name="share"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button
|
||||
fill="outline"
|
||||
(click)="download()"
|
||||
*ngIf="isWeb; else exportButton"
|
||||
>
|
||||
{{ 'schedule.toCalendar.reviewModal.DOWNLOAD' | translate }}
|
||||
<ion-icon slot="end" name="download"></ion-icon>
|
||||
</ion-button>
|
||||
<ng-template #exportButton>
|
||||
<ion-button fill="outline" (click)="toCalendar()">
|
||||
{{ 'schedule.toCalendar.reviewModal.EXPORT' | translate }}
|
||||
<ion-icon slot="end" name="calendar"></ion-icon>
|
||||
</ion-button>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
29
src/app/modules/calendar/add-event-review-modal.scss
Normal file
29
src/app/modules/calendar/add-event-review-modal.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
div {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
ion-card-header {
|
||||
ion-button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
ion-card-content {
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.horizontal-flex {
|
||||
height: fit-content;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
}
|
||||
21
src/app/modules/calendar/calendar-info.ts
Normal file
21
src/app/modules/calendar/calendar-info.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 Licens for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export interface CalendarInfo {
|
||||
id: number;
|
||||
name: string;
|
||||
displayname: string;
|
||||
isPrimary: boolean;
|
||||
}
|
||||
41
src/app/modules/calendar/calendar.module.ts
Normal file
41
src/app/modules/calendar/calendar.module.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 Licens for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {NgModule} from '@angular/core';
|
||||
import {AddEventReviewModalComponent} from './add-event-review-modal.component';
|
||||
import {Calendar} from '@awesome-cordova-plugins/calendar/ngx';
|
||||
import {CalendarService} from './calendar.service';
|
||||
import {ScheduleProvider} from './schedule.provider';
|
||||
import {IonicModule} from '@ionic/angular';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {MomentModule} from 'ngx-moment';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AddEventReviewModalComponent],
|
||||
imports: [
|
||||
IonicModule.forRoot(),
|
||||
TranslateModule.forChild(),
|
||||
ThingTranslateModule.forChild(),
|
||||
FormsModule,
|
||||
CommonModule,
|
||||
MomentModule,
|
||||
],
|
||||
exports: [],
|
||||
providers: [Calendar, CalendarService, ScheduleProvider],
|
||||
})
|
||||
export class CalendarModule {}
|
||||
101
src/app/modules/calendar/calendar.service.ts
Normal file
101
src/app/modules/calendar/calendar.service.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 Licens for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Calendar} from '@awesome-cordova-plugins/calendar/ngx';
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ICalEvent} from './ical/ical';
|
||||
import moment, {duration, unitOfTime} from 'moment';
|
||||
import {Dialog} from '@capacitor/dialog';
|
||||
import {CalendarInfo} from './calendar-info';
|
||||
|
||||
const CALENDAR_NAME = 'StApps';
|
||||
|
||||
const RECURRENCE_PATTERNS: Partial<
|
||||
Record<unitOfTime.Diff, string | undefined>
|
||||
> = {
|
||||
year: 'yearly',
|
||||
month: 'monthly',
|
||||
week: 'weekly',
|
||||
day: 'daily',
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class CalendarService {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
constructor(readonly calendar: Calendar) {}
|
||||
|
||||
async createCalendar(): Promise<CalendarInfo | undefined> {
|
||||
await this.calendar.createCalendar({
|
||||
calendarName: CALENDAR_NAME,
|
||||
calendarColor: '#ff8740',
|
||||
});
|
||||
return this.findCalendar(CALENDAR_NAME);
|
||||
}
|
||||
|
||||
async listCalendars(): Promise<CalendarInfo[] | undefined> {
|
||||
return this.calendar.listCalendars();
|
||||
}
|
||||
|
||||
async findCalendar(name: string): Promise<CalendarInfo | undefined> {
|
||||
return (await this.listCalendars())?.find(
|
||||
(calendar: CalendarInfo) => calendar.name === name,
|
||||
);
|
||||
}
|
||||
|
||||
async purge(): Promise<CalendarInfo | undefined> {
|
||||
if (await this.findCalendar(CALENDAR_NAME)) {
|
||||
await this.calendar.deleteCalendar(CALENDAR_NAME);
|
||||
}
|
||||
return await this.createCalendar();
|
||||
}
|
||||
|
||||
async syncEvents(events: ICalEvent[]) {
|
||||
const calendar = await this.purge();
|
||||
if (!calendar) {
|
||||
return Dialog.alert({
|
||||
title: 'Error',
|
||||
message: 'Could not create calendar',
|
||||
});
|
||||
}
|
||||
|
||||
for (const iCalEvent of events) {
|
||||
// TODO: change to use non-interactive version after testing is complete
|
||||
const start = iCalEvent.rrule ? iCalEvent.rrule.from : iCalEvent.start;
|
||||
|
||||
await this.calendar.createEventWithOptions(
|
||||
iCalEvent.recurrenceSequence
|
||||
? `(${iCalEvent.recurrenceSequence}/${iCalEvent.recurrenceSequenceAmount}) ${iCalEvent.name}`
|
||||
: iCalEvent.name,
|
||||
iCalEvent.geo,
|
||||
iCalEvent.description,
|
||||
new Date(start),
|
||||
moment(start).add(duration(iCalEvent.duration)).toDate(),
|
||||
{
|
||||
id: `${iCalEvent.uuid}-${start}`,
|
||||
url: iCalEvent.url,
|
||||
calendarName: calendar.name,
|
||||
calendarId: calendar.id,
|
||||
...(iCalEvent.rrule
|
||||
? {
|
||||
recurrence: RECURRENCE_PATTERNS[iCalEvent.rrule.freq],
|
||||
recurrenceInterval: iCalEvent.rrule.interval,
|
||||
recurrenceEndDate: new Date(iCalEvent.rrule.until),
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/app/modules/calendar/ical/ical.spec.ts
Normal file
82
src/app/modules/calendar/ical/ical.spec.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 Licens for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {findRRules, RRule} from './ical';
|
||||
import moment, {unitOfTime} from 'moment';
|
||||
import {shuffle} from 'lodash-es';
|
||||
import {SCISO8601Date} from '@openstapps/core';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function expandRRule(rule: RRule): SCISO8601Date[] {
|
||||
const initial = moment(rule.from);
|
||||
const interval = rule.interval ?? 1;
|
||||
|
||||
return shuffle(
|
||||
Array.from({
|
||||
length:
|
||||
Math.floor(
|
||||
moment(rule.until).diff(initial, rule.freq, true) / interval,
|
||||
) + 1,
|
||||
}).map((_, i) =>
|
||||
initial
|
||||
.clone()
|
||||
.add(interval * i, rule.freq ?? 'day')
|
||||
.toISOString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
describe('iCal', () => {
|
||||
it('should find simple recurrence patterns', () => {
|
||||
for (const freq of ['day', 'week', 'month', 'year'] as unitOfTime.Diff[]) {
|
||||
for (const interval of [1, 2, 3]) {
|
||||
const pattern: RRule = {
|
||||
freq: freq,
|
||||
interval: interval,
|
||||
from: moment('2021-09-01T10:00').toISOString(),
|
||||
until: moment('2021-09-01T10:00')
|
||||
.add(4 * interval, freq)
|
||||
.toISOString(),
|
||||
};
|
||||
|
||||
expect(findRRules(expandRRule(pattern))).toEqual([pattern]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should find missing recurrence patterns', () => {
|
||||
const pattern: SCISO8601Date = moment('2021-09-01T10:00').toISOString();
|
||||
|
||||
expect(findRRules([pattern])).toEqual([pattern]);
|
||||
});
|
||||
|
||||
it('should find mixed recurrence patterns', () => {
|
||||
const singlePattern: SCISO8601Date =
|
||||
moment('2021-09-01T09:00').toISOString();
|
||||
|
||||
const weeklyPattern: RRule = {
|
||||
freq: 'week',
|
||||
interval: 1,
|
||||
from: moment('2021-09-03T10:00').toISOString(),
|
||||
until: moment('2021-09-03T10:00').add(4, 'weeks').toISOString(),
|
||||
};
|
||||
|
||||
expect(
|
||||
findRRules(shuffle([singlePattern, ...expandRRule(weeklyPattern)])),
|
||||
).toEqual([singlePattern, weeklyPattern]);
|
||||
});
|
||||
});
|
||||
414
src/app/modules/calendar/ical/ical.ts
Normal file
414
src/app/modules/calendar/ical/ical.ts
Normal file
@@ -0,0 +1,414 @@
|
||||
/*
|
||||
* 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 Licens for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {
|
||||
SCDateSeries,
|
||||
SCISO8601Date,
|
||||
SCISO8601Duration,
|
||||
SCThingTranslator,
|
||||
SCThingWithCategories,
|
||||
SCUuid,
|
||||
} from '@openstapps/core';
|
||||
import {
|
||||
difference,
|
||||
flatMap,
|
||||
isObject,
|
||||
last,
|
||||
mapValues,
|
||||
minBy,
|
||||
size,
|
||||
} from 'lodash-es';
|
||||
import moment, {unitOfTime} from 'moment';
|
||||
|
||||
export interface ICalEvent {
|
||||
name?: string;
|
||||
uuid: SCUuid;
|
||||
categories?: string[];
|
||||
description?: string;
|
||||
cancelled?: boolean;
|
||||
recurrenceId?: SCISO8601Date;
|
||||
geo?: string;
|
||||
/**
|
||||
* The sequence index if the series had to be split into multiple rrules
|
||||
*/
|
||||
recurrenceSequence?: number;
|
||||
recurrenceSequenceAmount?: number;
|
||||
rrule?: RRule;
|
||||
dates?: SCISO8601Date[];
|
||||
exceptionDates?: SCISO8601Date[];
|
||||
start: SCISO8601Date;
|
||||
sequence?: number;
|
||||
duration?: SCISO8601Duration;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export type ICalKeyValuePair = `${Uppercase<string>}${':' | '='}${string}`;
|
||||
|
||||
export type ICalLike = ICalKeyValuePair[];
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function timeDist(
|
||||
current: SCISO8601Date,
|
||||
next: SCISO8601Date | undefined,
|
||||
recurrence: unitOfTime.Diff,
|
||||
): number | undefined {
|
||||
if (!next) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const diff = moment(next).diff(moment(current), recurrence, true);
|
||||
|
||||
return Math.floor(diff) === diff ? diff : undefined;
|
||||
}
|
||||
|
||||
export interface RRule {
|
||||
freq: unitOfTime.Diff; // 'SECONDLY' | 'HOURLY' | 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY';
|
||||
interval: number;
|
||||
from: SCISO8601Date;
|
||||
until: SCISO8601Date;
|
||||
}
|
||||
|
||||
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
||||
|
||||
export interface MergedRRule {
|
||||
rrule?: RRule;
|
||||
exceptions?: SCISO8601Date[];
|
||||
date?: SCISO8601Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge compatible RRules to a single RRule with exceptions
|
||||
*/
|
||||
export function mergeRRules(
|
||||
rules: Array<RRule | SCISO8601Date>,
|
||||
allowExceptions = true,
|
||||
): MergedRRule[] {
|
||||
if (!allowExceptions)
|
||||
return rules.map(it => (typeof it === 'string' ? {date: it} : {rrule: it}));
|
||||
/*map(groupBy(rules, it => `${it.freq}@${it.interval}`), it => {
|
||||
|
||||
});*/
|
||||
|
||||
return rules.map(it =>
|
||||
typeof it === 'string' ? {date: it} : {rrule: it},
|
||||
) /* TODO */;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find RRules in a list of dates
|
||||
*/
|
||||
export function findRRules(
|
||||
dates: SCISO8601Date[],
|
||||
): Array<RRule | SCISO8601Date> {
|
||||
const sorted = dates.sort((a, b) => moment(a).unix() - moment(b).unix());
|
||||
|
||||
const output: Optional<RRule, 'freq'>[] = [
|
||||
{
|
||||
from: sorted[0],
|
||||
until: sorted[0],
|
||||
interval: -1,
|
||||
},
|
||||
];
|
||||
|
||||
for (let i = 0; i < sorted.length; i++) {
|
||||
const current = sorted[i];
|
||||
const next = sorted[i + 1] as SCISO8601Date | undefined;
|
||||
const element = last(output);
|
||||
|
||||
const units: unitOfTime.Diff[] = element?.freq
|
||||
? [element.freq]
|
||||
: ['day', 'week', 'month', 'year'];
|
||||
const freq = minBy(
|
||||
units.map(recurrence => ({
|
||||
recurrence: recurrence,
|
||||
dist: timeDist(current, next, recurrence),
|
||||
})),
|
||||
it => it.dist,
|
||||
)?.recurrence;
|
||||
const interval = freq ? timeDist(current, next, freq) : undefined;
|
||||
|
||||
if (element?.interval === -1) {
|
||||
element.freq = freq;
|
||||
element.interval = interval ?? -1;
|
||||
}
|
||||
|
||||
if (!freq || element?.freq !== freq || element.interval !== interval) {
|
||||
if (element) {
|
||||
element.until = current;
|
||||
}
|
||||
|
||||
if (next) {
|
||||
output.push({
|
||||
from: next,
|
||||
until: next,
|
||||
interval: -1,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
element.until = current;
|
||||
}
|
||||
}
|
||||
|
||||
return output.map(it => (it.freq ? (it as RRule) : it.from));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function strikethrough(text: string): string {
|
||||
return `\u274C ${[...text].join('\u0336')}\u0336`;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function getICalData(
|
||||
dateSeries: SCDateSeries,
|
||||
translator: SCThingTranslator,
|
||||
): Pick<ICalEvent, 'name' | 'uuid' | 'categories' | 'description' | 'geo'> {
|
||||
const translated = translator.translatedAccess(dateSeries);
|
||||
|
||||
return {
|
||||
name: translated.event()?.name,
|
||||
uuid: dateSeries.uid,
|
||||
categories: [
|
||||
'stapps',
|
||||
...((translated.event() as SCThingWithCategories<string, never>)
|
||||
?.categories ?? []),
|
||||
],
|
||||
description: translated.event()?.description ?? translated.description(),
|
||||
geo: translated.inPlace()?.name,
|
||||
};
|
||||
}
|
||||
|
||||
export interface ToICalOptions {
|
||||
allowRRuleExceptions?: boolean;
|
||||
excludeCancelledEvents?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function toICal(
|
||||
dateSeries: SCDateSeries,
|
||||
translator: SCThingTranslator,
|
||||
options: ToICalOptions = {},
|
||||
): ICalEvent[] {
|
||||
const rrules = findRRules(
|
||||
options.excludeCancelledEvents
|
||||
? difference(dateSeries.dates, dateSeries.exceptions ?? [])
|
||||
: dateSeries.dates,
|
||||
);
|
||||
|
||||
return mergeRRules(rrules, options.allowRRuleExceptions).map(
|
||||
(it, i, array) => ({
|
||||
...getICalData(dateSeries, translator),
|
||||
dates: dateSeries.dates,
|
||||
rrule: it.rrule,
|
||||
recurrenceSequence: array.length > 1 ? i + 1 : undefined,
|
||||
recurrenceSequenceAmount: array.length > 1 ? array.length : undefined,
|
||||
exceptionDates: it.exceptions,
|
||||
start: it.rrule?.from ?? it.date ?? dateSeries.dates[0],
|
||||
sequence: 0,
|
||||
duration: dateSeries.duration,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function toICalUpdates(
|
||||
dateSeries: SCDateSeries,
|
||||
translator: SCThingTranslator,
|
||||
): ICalEvent[] {
|
||||
return (
|
||||
dateSeries.exceptions?.map(exception => ({
|
||||
...getICalData(dateSeries, translator),
|
||||
sequence: 1,
|
||||
recurrenceId: exception,
|
||||
cancelled: true,
|
||||
start: exception,
|
||||
})) ?? []
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an ISO8601 date to a string in the format YYYYMMDDTHHMMSSZ
|
||||
*/
|
||||
export function iso8601ToICalDateTime<T extends SCISO8601Date | undefined>(
|
||||
date: T,
|
||||
): T extends SCISO8601Date ? string : undefined {
|
||||
return (
|
||||
date ? `${moment(date).utc().format('YYYYMMDDTHHmmss')}Z` : undefined
|
||||
) as never;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an ISO8601 date to a string in the format YYYYMMDD
|
||||
*/
|
||||
export function iso8601ToICalDate(date: SCISO8601Date): string {
|
||||
return `${moment(date).utc().format('YYYYMMDD')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively stringify all linebreaks to \n strings
|
||||
*/
|
||||
function stringifyLinebreaks<T extends string | unknown[] | unknown>(
|
||||
value: T,
|
||||
): T {
|
||||
if (typeof value === 'string') {
|
||||
return value.replace(/\r?\n|\r/g, '\\n') as T;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(stringifyLinebreaks) as T;
|
||||
}
|
||||
if (isObject(value)) {
|
||||
return mapValues(value, stringifyLinebreaks) as T;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize an ICal object to not contain line breaks and convert dates to iCal format
|
||||
*/
|
||||
export function normalizeICalDates(iCal: ICalEvent): ICalEvent {
|
||||
return {
|
||||
...iCal,
|
||||
dates: iCal.dates?.filter(it => it !== iCal.start).map(iso8601ToICalDate),
|
||||
exceptionDates: iCal.exceptionDates?.map(iso8601ToICalDate),
|
||||
start: iso8601ToICalDateTime(iCal.start),
|
||||
recurrenceId: iso8601ToICalDateTime(iCal.recurrenceId),
|
||||
};
|
||||
}
|
||||
|
||||
const REPEAT_FREQUENCIES: Partial<Record<unitOfTime.Diff, string>> = {
|
||||
day: 'DAILY',
|
||||
week: 'WEEKLY',
|
||||
month: 'MONTHLY',
|
||||
year: 'YEARLY',
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function serializeICalLike(iCal: ICalLike): string {
|
||||
return iCal.map(stringifyLinebreaks).join('\r\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all strings that are either undefined or end with 'undefined'
|
||||
*/
|
||||
function withoutNullishStrings<T extends string>(
|
||||
array: Array<T | `${string}${undefined}` | undefined>,
|
||||
): T[] {
|
||||
return array.filter(it => it && !it.endsWith('undefined')) as T[];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function serializeRRule(rrule?: RRule): string | undefined {
|
||||
return rrule
|
||||
? `FREQ=${
|
||||
REPEAT_FREQUENCIES[rrule.freq ?? 's']
|
||||
};UNTIL=${iso8601ToICalDateTime(rrule.until)};INTERVAL=${rrule.interval}`
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an iCal event to a string
|
||||
*/
|
||||
export function serializeICalEvent(iCal: ICalEvent): ICalLike {
|
||||
const normalized = normalizeICalDates(iCal);
|
||||
|
||||
return withoutNullishStrings<ICalKeyValuePair>([
|
||||
'BEGIN:VEVENT',
|
||||
`DTSTART:${normalized.start}`,
|
||||
`DURATION:${normalized.duration}`,
|
||||
`DTSTAMP:${moment().utc().format('YYYYMMDDTHHmmss')}Z`,
|
||||
`UID:${normalized.uuid}`,
|
||||
`RECURRENCE-ID:${normalized.recurrenceId}`,
|
||||
`CATEGORIES:${normalized.categories?.join(',')}`,
|
||||
`SUMMARY:${normalized.name}`,
|
||||
`DESCRIPTION:${normalized.description}`,
|
||||
`STATUS:${normalized.cancelled === true ? 'CANCELLED' : 'CONFIRMED'}`,
|
||||
`URL:${normalized.url}`,
|
||||
// `RDATE;VALUE=DATE:${normalized.dates.join(',')}`,
|
||||
size(normalized.exceptionDates) > 0
|
||||
? `EXDATE;VALUE=DATE:${normalized.exceptionDates?.join(',')}`
|
||||
: undefined,
|
||||
`RRULE:${serializeRRule(normalized.rrule)}`,
|
||||
'END:VEVENT',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an iCal object to a string
|
||||
*/
|
||||
export function serializeICal(iCal: ICalEvent[]): string {
|
||||
return serializeICalLike([
|
||||
'BEGIN:VCALENDAR',
|
||||
'VERSION:2.0',
|
||||
'PRODID:-//StApps//NONSGML StApps Calendar//EN',
|
||||
'NAME:StApps',
|
||||
'X-WR-CALNAME:StApps',
|
||||
'X-WR-CALDESC:StApps Calendar',
|
||||
'X-WR-TIMEZONE:Europe/Berlin',
|
||||
'LOCATION;LANGUAGE=en:Germany',
|
||||
'CALSCALE:GREGORIAN',
|
||||
'COLOR:#FF0000',
|
||||
'METHOD:PUBLISH',
|
||||
...flatMap(iCal, serializeICalEvent),
|
||||
'END:VCALENDAR',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transform date series for purpose of native calendar export
|
||||
*/
|
||||
export function getNativeCalendarExport(
|
||||
dateSeries: SCDateSeries[],
|
||||
translator: SCThingTranslator,
|
||||
): ICalEvent[] {
|
||||
return flatMap(dateSeries, event =>
|
||||
toICal(event, translator, {
|
||||
allowRRuleExceptions: false,
|
||||
excludeCancelledEvents: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transform date series for purpose of iCal file export
|
||||
*/
|
||||
export function getICalExport(
|
||||
dateSeries: SCDateSeries[],
|
||||
translator: SCThingTranslator,
|
||||
includeCancelled: boolean,
|
||||
): ICalEvent[] {
|
||||
return [
|
||||
...flatMap(dateSeries, event =>
|
||||
toICal(event, translator, {
|
||||
allowRRuleExceptions: false,
|
||||
excludeCancelledEvents: !includeCancelled,
|
||||
}),
|
||||
),
|
||||
...(includeCancelled
|
||||
? flatMap(dateSeries, event => toICalUpdates(event, translator))
|
||||
: []),
|
||||
];
|
||||
}
|
||||
28
src/app/modules/calendar/new-share.ts
Normal file
28
src/app/modules/calendar/new-share.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 Licens for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export interface NewShareData {
|
||||
files?: File[];
|
||||
title?: string;
|
||||
text?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
// web share api is relatively new
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share
|
||||
export interface NewShareNavigator {
|
||||
canShare: (options: NewShareData) => boolean;
|
||||
share: (options: NewShareData) => Promise<void>;
|
||||
}
|
||||
@@ -24,8 +24,54 @@ import {
|
||||
SCThingType,
|
||||
SCUuid,
|
||||
} from '@openstapps/core';
|
||||
import {BehaviorSubject, Subscription} from 'rxjs';
|
||||
import {BehaviorSubject, Observable, Subscription} from 'rxjs';
|
||||
import {DataProvider} from '../data/data.provider';
|
||||
import {map} from 'rxjs/operators';
|
||||
import {pick} from 'lodash-es';
|
||||
import {DateFormatPipe, DurationPipe} from 'ngx-moment';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function toDateSeriesRelevantData(
|
||||
dateSeries: SCDateSeries,
|
||||
): DateSeriesRelevantData {
|
||||
return pick(dateSeries, ...dateSeriesRelevantKeys);
|
||||
}
|
||||
|
||||
export type DateSeriesRelevantKeys =
|
||||
| 'uid'
|
||||
| 'dates'
|
||||
| 'exceptions'
|
||||
| 'repeatFrequency'
|
||||
| 'duration';
|
||||
|
||||
export const dateSeriesRelevantKeys: Array<DateSeriesRelevantKeys> = [
|
||||
'uid',
|
||||
'dates',
|
||||
'exceptions',
|
||||
'repeatFrequency',
|
||||
'duration',
|
||||
];
|
||||
|
||||
export const formatRelevantKeys: {
|
||||
[key in DateSeriesRelevantKeys]: (
|
||||
value: SCDateSeries[key],
|
||||
dateFormatter: DateFormatPipe,
|
||||
durationFormatter: DurationPipe,
|
||||
) => string;
|
||||
} = {
|
||||
uid: value => value,
|
||||
dates: (value, dateFormatter) =>
|
||||
`[${value.map(it => dateFormatter.transform(it)).join(', ')}]`,
|
||||
exceptions: (value, dateFormatter) =>
|
||||
`[${value?.map(it => dateFormatter.transform(it)).join(', ') ?? ''}]`,
|
||||
repeatFrequency: (value, _, durationFormatter) =>
|
||||
durationFormatter.transform(value),
|
||||
duration: (value, _, durationFormatter) => durationFormatter.transform(value),
|
||||
};
|
||||
|
||||
export type DateSeriesRelevantData = Pick<SCDateSeries, DateSeriesRelevantKeys>;
|
||||
|
||||
/**
|
||||
* Provider for app settings
|
||||
@@ -34,14 +80,11 @@ import {DataProvider} from '../data/data.provider';
|
||||
export class ScheduleProvider implements OnDestroy {
|
||||
// tslint:disable:prefer-function-over-method
|
||||
|
||||
/**
|
||||
* Storage key for event UUIDs
|
||||
*/
|
||||
private static uuidStorageKey = 'schedule::event_uuids';
|
||||
private static partialEventsStorageKey = 'schedule::partial_events';
|
||||
|
||||
private _uuids$?: BehaviorSubject<SCUuid[]>;
|
||||
private _partialEvents$?: BehaviorSubject<DateSeriesRelevantData[]>;
|
||||
|
||||
private _uuidSubscription?: Subscription;
|
||||
private _partialEventsSubscription?: Subscription;
|
||||
|
||||
constructor(private readonly dataProvider: DataProvider) {
|
||||
window.addEventListener('storage', this.storageListener);
|
||||
@@ -70,20 +113,42 @@ export class ScheduleProvider implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
public async restore(uuids: SCUuid[]): Promise<SCDateSeries[] | undefined> {
|
||||
if (uuids.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const dateSeries = (await this.getDateSeries(uuids)).dates;
|
||||
|
||||
this._partialEvents$?.next(dateSeries.map(toDateSeriesRelevantData));
|
||||
|
||||
return dateSeries;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
public get uuids$(): BehaviorSubject<SCUuid[]> {
|
||||
if (!this._uuids$) {
|
||||
this._uuids$ = new BehaviorSubject(
|
||||
ScheduleProvider.get<SCUuid>(ScheduleProvider.uuidStorageKey),
|
||||
public get uuids$(): Observable<SCUuid[]> {
|
||||
return this.partialEvents$.pipe(map(events => events.map(it => it.uid)));
|
||||
}
|
||||
|
||||
public get partialEvents$(): BehaviorSubject<DateSeriesRelevantData[]> {
|
||||
if (!this._partialEvents$) {
|
||||
const data = ScheduleProvider.get<DateSeriesRelevantData>(
|
||||
ScheduleProvider.partialEventsStorageKey,
|
||||
);
|
||||
|
||||
this._partialEvents$ = new BehaviorSubject(data ?? []);
|
||||
this._partialEventsSubscription = this._partialEvents$.subscribe(
|
||||
result => {
|
||||
ScheduleProvider.set(
|
||||
ScheduleProvider.partialEventsStorageKey,
|
||||
result,
|
||||
);
|
||||
},
|
||||
);
|
||||
this._uuidSubscription = this._uuids$.subscribe(result => {
|
||||
ScheduleProvider.set(ScheduleProvider.uuidStorageKey, result);
|
||||
});
|
||||
}
|
||||
|
||||
return this._uuids$;
|
||||
return this._partialEvents$;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,9 +158,9 @@ export class ScheduleProvider implements OnDestroy {
|
||||
if (
|
||||
event.newValue &&
|
||||
event.storageArea === localStorage &&
|
||||
event.key === ScheduleProvider.uuidStorageKey
|
||||
event.key === ScheduleProvider.partialEventsStorageKey
|
||||
) {
|
||||
this._uuids$?.next(JSON.parse(event.newValue));
|
||||
this._partialEvents$?.next(JSON.parse(event.newValue));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +276,7 @@ export class ScheduleProvider implements OnDestroy {
|
||||
* TODO
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this._uuidSubscription?.unsubscribe();
|
||||
this._partialEventsSubscription?.unsubscribe();
|
||||
window.removeEventListener('storage', this.storageListener);
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,8 @@ import {
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import {PopoverController} from '@ionic/angular';
|
||||
import {SCDateSeries, SCUuid} from '@openstapps/core';
|
||||
import {ModalController, PopoverController} from '@ionic/angular';
|
||||
import {SCDateSeries} from '@openstapps/core';
|
||||
import {
|
||||
difference,
|
||||
every,
|
||||
@@ -36,7 +36,14 @@ import {
|
||||
} from 'lodash-es';
|
||||
import {capitalize, last} from 'lodash-es';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {ScheduleProvider} from '../../schedule/schedule.provider';
|
||||
import {
|
||||
DateSeriesRelevantData,
|
||||
ScheduleProvider,
|
||||
toDateSeriesRelevantData,
|
||||
} from '../../calendar/schedule.provider';
|
||||
import {CalendarService} from '../../calendar/calendar.service';
|
||||
import {AddEventReviewModalComponent} from '../../calendar/add-event-review-modal.component';
|
||||
import {ThingTranslatePipe} from '../../../translation/thing-translate.pipe';
|
||||
|
||||
enum Selection {
|
||||
ON = 2,
|
||||
@@ -190,7 +197,7 @@ export class AddEventPopoverComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* Uuids
|
||||
*/
|
||||
uuids: SCUuid[];
|
||||
partialDateSeries: DateSeriesRelevantData[];
|
||||
|
||||
/**
|
||||
* Uuid Subscription
|
||||
@@ -201,6 +208,9 @@ export class AddEventPopoverComponent implements OnInit, OnDestroy {
|
||||
readonly ref: ChangeDetectorRef,
|
||||
readonly scheduleProvider: ScheduleProvider,
|
||||
readonly popoverController: PopoverController,
|
||||
readonly calendar: CalendarService,
|
||||
readonly modalController: ModalController,
|
||||
readonly thingTranslatePipe: ThingTranslatePipe,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -214,16 +224,18 @@ export class AddEventPopoverComponent implements OnInit, OnDestroy {
|
||||
* Init
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.uuidSubscription = this.scheduleProvider.uuids$.subscribe(
|
||||
this.uuidSubscription = this.scheduleProvider.partialEvents$.subscribe(
|
||||
async result => {
|
||||
this.uuids = result;
|
||||
this.partialDateSeries = result;
|
||||
|
||||
this.selection = new TreeNode(
|
||||
values(
|
||||
groupBy(
|
||||
sortBy(
|
||||
this.items.map(item => ({
|
||||
selected: this.uuids.includes(item.uid),
|
||||
selected: this.partialDateSeries.some(
|
||||
it => it.uid === item.uid,
|
||||
),
|
||||
item: item,
|
||||
})),
|
||||
it => it.item.repeatFrequency,
|
||||
@@ -237,17 +249,44 @@ export class AddEventPopoverComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
getSelection(): {
|
||||
selected: DateSeriesRelevantData[];
|
||||
unselected: DateSeriesRelevantData[];
|
||||
} {
|
||||
const selection = mapValues(
|
||||
groupBy(flatMap(this.selection.children, 'children'), 'selected'),
|
||||
value => value.map(it => toDateSeriesRelevantData(it.item)),
|
||||
);
|
||||
|
||||
return {selected: selection.true ?? [], unselected: selection.false ?? []};
|
||||
}
|
||||
|
||||
async export() {
|
||||
const modal = await this.modalController.create({
|
||||
component: AddEventReviewModalComponent,
|
||||
swipeToClose: true,
|
||||
cssClass: 'add-modal',
|
||||
componentProps: {
|
||||
dismissAction: () => {
|
||||
modal.dismiss();
|
||||
},
|
||||
dateSeries: this.items,
|
||||
},
|
||||
});
|
||||
|
||||
await modal.present();
|
||||
await modal.onWillDismiss();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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),
|
||||
const {selected, unselected} = this.getSelection();
|
||||
console.log(selected, unselected);
|
||||
this.scheduleProvider.partialEvents$.next(
|
||||
union(difference(this.partialDateSeries, unselected), selected),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -75,4 +75,14 @@
|
||||
'ok' | translate
|
||||
}}</ion-button>
|
||||
</div>
|
||||
<div class="download-button">
|
||||
<ion-button
|
||||
fill="clear"
|
||||
(click)="export()"
|
||||
[disabled]="!(selection.indeterminate || selection.checked)"
|
||||
>
|
||||
<ion-icon slot="icon-only" name="download"></ion-icon>
|
||||
<!-- {{ 'export' | translate }} -->
|
||||
</ion-button>
|
||||
</div>
|
||||
</ion-card-content>
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*!
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
::ng-deep ion-item-divider {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -5,3 +20,7 @@
|
||||
.action-buttons {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.download-button {
|
||||
float: left;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import {PopoverController} from '@ionic/angular';
|
||||
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 {ScheduleProvider} from '../../../calendar/schedule.provider';
|
||||
import {AddEventPopoverComponent} from '../add-event-popover.component';
|
||||
import {CoordinatedSearchProvider} from '../../coordinated-search.provider';
|
||||
import {
|
||||
|
||||
@@ -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.
|
||||
@@ -24,7 +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 {ScheduleProvider} from '../calendar/schedule.provider';
|
||||
import {StorageModule} from '../storage/storage.module';
|
||||
import {ActionChipListComponent} from './chips/action-chip-list.component';
|
||||
import {AddEventPopoverComponent} from './chips/add-event-popover.component';
|
||||
@@ -83,6 +83,7 @@ import {Geolocation} from '@ionic-native/geolocation/ngx';
|
||||
import {FavoriteButtonComponent} from './elements/favorite-button.component';
|
||||
import {SimpleDataListComponent} from './list/simple-data-list.component';
|
||||
import {TitleCardComponent} from './elements/title-card.component';
|
||||
import {CalendarService} from '../calendar/calendar.service';
|
||||
|
||||
/**
|
||||
* Module for handling data
|
||||
@@ -168,6 +169,7 @@ import {TitleCardComponent} from './elements/title-card.component';
|
||||
Network,
|
||||
ScheduleProvider,
|
||||
StAppsWebHttpClient,
|
||||
CalendarService,
|
||||
],
|
||||
exports: [
|
||||
DataDetailComponent,
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
materialManualFade,
|
||||
materialSharedAxisX,
|
||||
} from '../../../animation/material-motion';
|
||||
import {ScheduleProvider} from '../schedule.provider';
|
||||
import {ScheduleProvider} from '../../calendar/schedule.provider';
|
||||
import {ScheduleEvent, ScheduleResponsiveBreakpoint} from './schema/schema';
|
||||
import {SwiperComponent} from 'swiper/angular';
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import moment from 'moment';
|
||||
import {ScheduleProvider} from '../../schedule.provider';
|
||||
import {ScheduleProvider} from '../../../calendar/schedule.provider';
|
||||
import {ScheduleEvent} from '../schema/schema';
|
||||
|
||||
/**
|
||||
@@ -87,9 +87,9 @@ export class ScheduleCardComponent implements OnInit {
|
||||
*/
|
||||
removeEvent(): false {
|
||||
if (confirm('Remove event?')) {
|
||||
this.scheduleProvider.uuids$.next(
|
||||
this.scheduleProvider.uuids$.value.filter(
|
||||
it => it !== this.scheduleEvent.dateSeries.uid,
|
||||
this.scheduleProvider.partialEvents$.next(
|
||||
this.scheduleProvider.partialEvents$.value.filter(
|
||||
it => it.uid !== this.scheduleEvent.dateSeries.uid,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
import moment from 'moment';
|
||||
import {Range, ScheduleEvent} from '../schema/schema';
|
||||
import {ScheduleProvider} from '../../schedule.provider';
|
||||
import {ScheduleProvider} from '../../../calendar/schedule.provider';
|
||||
import {SCISO8601Duration, SCUuid} from '@openstapps/core';
|
||||
import {materialFade} from '../../../../animation/material-motion';
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import {flatMap, groupBy, isNil, 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 {ScheduleProvider} from '../../calendar/schedule.provider';
|
||||
import {ScheduleEvent} from './schema/schema';
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
materialManualFade,
|
||||
materialSharedAxisX,
|
||||
} from '../../../animation/material-motion';
|
||||
import {ScheduleProvider} from '../schedule.provider';
|
||||
import {ScheduleProvider} from '../../calendar/schedule.provider';
|
||||
import {CalendarViewComponent} from './calendar-view.component';
|
||||
import {SwiperComponent} from 'swiper/angular';
|
||||
|
||||
|
||||
@@ -30,11 +30,12 @@ import {ModalEventCreatorComponent} from './page/modal/modal-event-creator.compo
|
||||
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';
|
||||
import {ScheduleProvider} from '../calendar/schedule.provider';
|
||||
import {SwiperModule} from 'swiper/angular';
|
||||
import {ScheduleDayComponent} from './page/grid/schedule-day.component';
|
||||
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||
import {InfiniteSwiperComponent} from './page/grid/infinite-swiper.component';
|
||||
import {FileOpener} from '@ionic-native/file-opener/ngx';
|
||||
|
||||
const settingsRoutes: Routes = [
|
||||
{path: 'schedule', redirectTo: 'schedule/calendar/now'},
|
||||
@@ -72,6 +73,6 @@ const settingsRoutes: Routes = [
|
||||
UtilModule,
|
||||
ThingTranslateModule,
|
||||
],
|
||||
providers: [ScheduleProvider, DataProvider, DateFormatPipe],
|
||||
providers: [ScheduleProvider, DataProvider, DateFormatPipe, FileOpener],
|
||||
})
|
||||
export class ScheduleModule {}
|
||||
|
||||
21
src/app/modules/settings/page/calendar-sync-settings-keys.ts
Normal file
21
src/app/modules/settings/page/calendar-sync-settings-keys.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 Licens for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export const CALENDAR_SYNC_SETTINGS_KEY = 'calendarSettings';
|
||||
export const CALENDAR_SYNC_ENABLED_KEY = 'sync';
|
||||
export const CALENDAR_NOTIFICATIONS_ENABLED_KEY = 'notifications';
|
||||
export type CALENDAR_SYNC_KEYS =
|
||||
| typeof CALENDAR_SYNC_ENABLED_KEY
|
||||
| typeof CALENDAR_NOTIFICATIONS_ENABLED_KEY;
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {AddEventReviewModalComponent} from '../../calendar/add-event-review-modal.component';
|
||||
import {ModalController} from '@ionic/angular';
|
||||
import {ScheduleProvider} from '../../calendar/schedule.provider';
|
||||
import {map} from 'lodash-es';
|
||||
import {Directory, Encoding, Filesystem} from '@capacitor/filesystem';
|
||||
import {Share} from '@capacitor/share';
|
||||
import {Device} from '@capacitor/device';
|
||||
import {Dialog} from '@capacitor/dialog';
|
||||
import {SCUuid} from '@openstapps/core';
|
||||
import {TranslateService} from '@ngx-translate/core';
|
||||
import {StorageProvider} from '../../storage/storage.provider';
|
||||
import {ScheduleSyncService} from '../../background/schedule/schedule-sync.service';
|
||||
import {CalendarService} from '../../calendar/calendar.service';
|
||||
import {getNativeCalendarExport} from '../../calendar/ical/ical';
|
||||
import {ThingTranslateService} from '../../../translation/thing-translate.service';
|
||||
import {
|
||||
CALENDAR_NOTIFICATIONS_ENABLED_KEY,
|
||||
CALENDAR_SYNC_ENABLED_KEY,
|
||||
CALENDAR_SYNC_KEYS,
|
||||
CALENDAR_SYNC_SETTINGS_KEY,
|
||||
} from './calendar-sync-settings-keys';
|
||||
|
||||
@Component({
|
||||
selector: 'calendar-sync-settings',
|
||||
templateUrl: 'calendar-sync-settings.html',
|
||||
styleUrls: ['calendar-sync-settings.scss'],
|
||||
})
|
||||
export class CalendarSyncSettingsComponent implements OnInit {
|
||||
isWeb = true;
|
||||
|
||||
syncEnabled = false;
|
||||
|
||||
notificationsEnabled = false;
|
||||
|
||||
constructor(
|
||||
readonly modalController: ModalController,
|
||||
readonly scheduleProvider: ScheduleProvider,
|
||||
readonly translator: TranslateService,
|
||||
readonly thingTranslator: ThingTranslateService,
|
||||
readonly storageProvider: StorageProvider,
|
||||
readonly scheduleSyncService: ScheduleSyncService,
|
||||
readonly calendarService: CalendarService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
Device.getInfo().then(it => {
|
||||
this.isWeb = it.platform === 'web';
|
||||
});
|
||||
|
||||
this.getSetting(CALENDAR_SYNC_ENABLED_KEY).then(
|
||||
it => (this.syncEnabled = it),
|
||||
);
|
||||
this.getSetting(CALENDAR_NOTIFICATIONS_ENABLED_KEY).then(
|
||||
it => (this.notificationsEnabled = it),
|
||||
);
|
||||
}
|
||||
|
||||
async getSetting(key: CALENDAR_SYNC_KEYS) {
|
||||
return (await this.storageProvider.get(
|
||||
`${CALENDAR_SYNC_SETTINGS_KEY}.${key}`,
|
||||
)) as boolean;
|
||||
}
|
||||
|
||||
async syncCalendar(sync: boolean) {
|
||||
this.syncEnabled = sync;
|
||||
|
||||
if (sync) {
|
||||
const uuids = this.scheduleProvider.partialEvents$.value.map(
|
||||
it => it.uid,
|
||||
);
|
||||
const dateSeries = (await this.scheduleProvider.getDateSeries(uuids))
|
||||
.dates;
|
||||
|
||||
await this.calendarService.syncEvents(
|
||||
getNativeCalendarExport(dateSeries, this.thingTranslator.translator),
|
||||
);
|
||||
} else {
|
||||
await this.calendarService.purge();
|
||||
}
|
||||
}
|
||||
|
||||
async setSetting(settings: Partial<Record<CALENDAR_SYNC_KEYS, boolean>>) {
|
||||
await Promise.all(
|
||||
map(settings, (setting, key) =>
|
||||
this.storageProvider.put(
|
||||
`${CALENDAR_SYNC_SETTINGS_KEY}.${key}`,
|
||||
setting,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return this.scheduleSyncService.enable();
|
||||
}
|
||||
|
||||
async export() {
|
||||
const uuids = this.scheduleProvider.partialEvents$.value.map(it => it.uid);
|
||||
const dateSeries = (await this.scheduleProvider.getDateSeries(uuids)).dates;
|
||||
|
||||
const modal = await this.modalController.create({
|
||||
component: AddEventReviewModalComponent,
|
||||
swipeToClose: true,
|
||||
cssClass: 'add-modal',
|
||||
componentProps: {
|
||||
dismissAction: () => {
|
||||
modal.dismiss();
|
||||
},
|
||||
dateSeries: dateSeries,
|
||||
},
|
||||
});
|
||||
|
||||
await modal.present();
|
||||
await modal.onWillDismiss();
|
||||
}
|
||||
|
||||
async restore(event: Event) {
|
||||
// @ts-expect-error files do actually exist
|
||||
const file = event.target?.files[0] as File;
|
||||
const uuids = JSON.parse(await file.text()) as SCUuid[] | unknown;
|
||||
if (!Array.isArray(uuids) || uuids.some(it => typeof it !== 'string')) {
|
||||
return Dialog.alert({
|
||||
title: this.translator.instant(
|
||||
'settings.calendar.export.dialogs.restore.rejectFile.title',
|
||||
),
|
||||
message: this.translator.instant(
|
||||
'settings.calendar.export.dialogs.restore.rejectFile.message',
|
||||
),
|
||||
});
|
||||
}
|
||||
const dateSeries = await this.scheduleProvider.restore(uuids);
|
||||
return dateSeries
|
||||
? Dialog.confirm({
|
||||
title: this.translator.instant(
|
||||
'settings.calendar.export.dialogs.restore.success.title',
|
||||
),
|
||||
message: this.translator.instant(
|
||||
'settings.calendar.export.dialogs.restore.success.message',
|
||||
),
|
||||
})
|
||||
: Dialog.alert({
|
||||
title: this.translator.instant(
|
||||
'settings.calendar.export.dialogs.restore.error.title',
|
||||
),
|
||||
message: this.translator.instant(
|
||||
'settings.calendar.export.dialogs.restore.error.message',
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
translateWithDefault(key: string, defaultValue: string) {
|
||||
const out = this.translator.instant(key);
|
||||
return out === key ? defaultValue : out;
|
||||
}
|
||||
|
||||
async backup() {
|
||||
const uuids = JSON.stringify(
|
||||
this.scheduleProvider.partialEvents$.value.map(it => it.uid),
|
||||
);
|
||||
|
||||
const fileName = `${this.translator.instant(
|
||||
'settings.calendar.export.fileName',
|
||||
)}.json`;
|
||||
const info = await Device.getInfo();
|
||||
if (info.platform === 'web') {
|
||||
const blob = new Blob([uuids], {type: 'application/json'});
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
} else {
|
||||
const result = await Filesystem.writeFile({
|
||||
path: fileName,
|
||||
data: uuids,
|
||||
encoding: Encoding.UTF8,
|
||||
directory: Directory.Cache,
|
||||
});
|
||||
|
||||
await Share.share({
|
||||
title: this.translator.instant(
|
||||
'settings.calendar.export.dialogs.backup.save.title',
|
||||
),
|
||||
text: this.translator.instant(
|
||||
'settings.calendar.export.dialogs.backup.save.message',
|
||||
),
|
||||
url: result.uri,
|
||||
dialogTitle: this.translator.instant(
|
||||
'settings.calendar.export.dialogs.backup.save.title',
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
120
src/app/modules/settings/page/calendar-sync-settings.html
Normal file
120
src/app/modules/settings/page/calendar-sync-settings.html
Normal file
@@ -0,0 +1,120 @@
|
||||
<!--
|
||||
~ 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 Licens for
|
||||
~ more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along with
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<ion-card>
|
||||
<ion-card-header>
|
||||
<ion-card-subtitle>{{
|
||||
'settings.calendar.title' | translate
|
||||
}}</ion-card-subtitle>
|
||||
</ion-card-header>
|
||||
|
||||
<ion-card-content>
|
||||
<ion-list lines="none">
|
||||
<ion-item-group>
|
||||
<ion-item-divider>
|
||||
<ion-label>{{
|
||||
'settings.calendar.sync.title' | translate
|
||||
}}</ion-label>
|
||||
</ion-item-divider>
|
||||
<ion-item>
|
||||
<ion-toggle
|
||||
[disabled]="isWeb"
|
||||
[checked]="syncEnabled"
|
||||
(ionChange)="
|
||||
setSetting({
|
||||
sync: $event.detail.checked
|
||||
});
|
||||
syncCalendar($event.detail.checked)
|
||||
"
|
||||
>
|
||||
</ion-toggle>
|
||||
<ion-label>{{
|
||||
'settings.calendar.sync.syncWithCalendar' | translate
|
||||
}}</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-toggle
|
||||
[disabled]="isWeb"
|
||||
[checked]="notificationsEnabled"
|
||||
(ionChange)="
|
||||
setSetting({
|
||||
notifications: $event.detail.checked
|
||||
})
|
||||
"
|
||||
>
|
||||
</ion-toggle>
|
||||
<ion-label>{{
|
||||
'settings.calendar.sync.eventNotifications' | translate
|
||||
}}</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-button
|
||||
[disabled]="isWeb || !syncEnabled"
|
||||
fill="clear"
|
||||
(click)="syncCalendar(true)"
|
||||
>
|
||||
<ion-label>Sync Now</ion-label>
|
||||
<ion-icon slot="end" name="sync"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="isWeb">
|
||||
<ion-label color="medium"
|
||||
><i>{{
|
||||
'settings.calendar.sync.unavailableWeb' | translate
|
||||
}}</i></ion-label
|
||||
>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
|
||||
<ion-item-group>
|
||||
<ion-item-divider>
|
||||
<ion-label>{{
|
||||
'settings.calendar.export.title' | translate
|
||||
}}</ion-label>
|
||||
</ion-item-divider>
|
||||
<ion-item>
|
||||
<ion-button fill="clear" (click)="export()">
|
||||
<ion-label>{{
|
||||
'settings.calendar.export.exportEvents' | translate
|
||||
}}</ion-label>
|
||||
<ion-icon slot="end" name="download"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-button fill="clear" (click)="backup()">
|
||||
<ion-label>{{
|
||||
'settings.calendar.export.backup' | translate
|
||||
}}</ion-label>
|
||||
<ion-icon slot="end" name="save"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button fill="clear" (click)="restoreInput.click()">
|
||||
<ion-label>{{
|
||||
'settings.calendar.export.restore' | translate
|
||||
}}</ion-label>
|
||||
<ion-icon slot="end" name="refresh"></ion-icon>
|
||||
</ion-button>
|
||||
<!--suppress CheckEmptyScriptTag -->
|
||||
<input
|
||||
class="ion-hide"
|
||||
type="file"
|
||||
accept="application/json"
|
||||
#restoreInput
|
||||
(change)="restore($event)"
|
||||
/>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</ion-list>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
@@ -16,9 +16,11 @@
|
||||
{{
|
||||
'categories[0]'
|
||||
| thingTranslate
|
||||
: settingsCache[categoryKey]?.settings[
|
||||
objectKeys(settingsCache[categoryKey]?.settings)[0]
|
||||
]
|
||||
: $any(
|
||||
settingsCache[categoryKey]?.settings[
|
||||
objectKeys(settingsCache[categoryKey]?.settings)[0]
|
||||
]
|
||||
)
|
||||
| titlecase
|
||||
}}
|
||||
</h5>
|
||||
@@ -31,6 +33,9 @@
|
||||
></stapps-settings-item>
|
||||
</div>
|
||||
</ion-list>
|
||||
|
||||
<calendar-sync-settings></calendar-sync-settings>
|
||||
|
||||
<ion-button
|
||||
color="medium"
|
||||
expand="block"
|
||||
|
||||
@@ -25,6 +25,14 @@ import {SettingsItemComponent} from './item/settings-item.component';
|
||||
import {SettingsPageComponent} from './page/settings-page.component';
|
||||
import {SettingTranslatePipe} from './setting-translate.pipe';
|
||||
import {SettingsProvider} from './settings.provider';
|
||||
import {CalendarSyncSettingsComponent} from './page/calendar-sync-settings.component';
|
||||
import {ScheduleProvider} from '../calendar/schedule.provider';
|
||||
import {FileOpener} from '@ionic-native/file-opener/ngx';
|
||||
import {ThingTranslatePipe} from '../../translation/thing-translate.pipe';
|
||||
import {ScheduleSyncService} from '../background/schedule/schedule-sync.service';
|
||||
import {CalendarService} from '../calendar/calendar.service';
|
||||
import {CalendarModule} from '../calendar/calendar.module';
|
||||
import {BackgroundModule} from '../background/background.module';
|
||||
|
||||
const settingsRoutes: Routes = [
|
||||
{path: 'settings', component: SettingsPageComponent},
|
||||
@@ -38,16 +46,27 @@ const settingsRoutes: Routes = [
|
||||
SettingsPageComponent,
|
||||
SettingsItemComponent,
|
||||
SettingTranslatePipe,
|
||||
CalendarSyncSettingsComponent,
|
||||
],
|
||||
exports: [SettingsItemComponent, SettingTranslatePipe],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
CalendarModule,
|
||||
BackgroundModule,
|
||||
IonicModule.forRoot(),
|
||||
TranslateModule.forChild(),
|
||||
ThingTranslateModule.forChild(),
|
||||
RouterModule.forChild(settingsRoutes),
|
||||
],
|
||||
providers: [ConfigProvider, SettingsProvider],
|
||||
providers: [
|
||||
ScheduleSyncService,
|
||||
ConfigProvider,
|
||||
SettingsProvider,
|
||||
CalendarService,
|
||||
ScheduleProvider,
|
||||
FileOpener,
|
||||
ThingTranslatePipe,
|
||||
],
|
||||
})
|
||||
export class SettingsModule {}
|
||||
|
||||
@@ -26,11 +26,13 @@ import {
|
||||
SettingValuesContainer,
|
||||
STORAGE_KEY_SETTING_VALUES,
|
||||
} from './settings.provider';
|
||||
import {ScheduleSyncService} from '../background/schedule/schedule-sync.service';
|
||||
|
||||
describe('SettingsProvider', () => {
|
||||
let configProviderSpy: jasmine.SpyObj<ConfigProvider>;
|
||||
let settingsProvider: SettingsProvider;
|
||||
let storageProviderSpy: jasmine.SpyObj<StorageProvider>;
|
||||
let scheduleSyncServiceSpy: jasmine.SpyObj<ScheduleSyncService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const storageProviderMethodSpy = jasmine.createSpyObj('StorageProvider', [
|
||||
@@ -42,6 +44,10 @@ describe('SettingsProvider', () => {
|
||||
const configProviderMethodSpy = jasmine.createSpyObj('ConfigProvider', [
|
||||
'getValue',
|
||||
]);
|
||||
scheduleSyncServiceSpy = jasmine.createSpyObj('ScheduleSyncService', [
|
||||
'getDifferences',
|
||||
'postDifferencesNotification',
|
||||
]);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [],
|
||||
@@ -55,6 +61,10 @@ describe('SettingsProvider', () => {
|
||||
provide: ConfigProvider,
|
||||
useValue: configProviderMethodSpy,
|
||||
},
|
||||
{
|
||||
provide: ScheduleSyncService,
|
||||
useValue: scheduleSyncServiceSpy,
|
||||
},
|
||||
],
|
||||
});
|
||||
configProviderSpy = TestBed.get(ConfigProvider);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"ok": "ok",
|
||||
"abort": "abbrechen",
|
||||
"ok": "Ok",
|
||||
"abort": "Abbrechen",
|
||||
"export": "Exportieren",
|
||||
"share": "Teilen",
|
||||
"modal": {
|
||||
"DISMISS": "Schließen"
|
||||
},
|
||||
@@ -276,6 +278,37 @@
|
||||
}
|
||||
},
|
||||
"schedule": {
|
||||
"toCalendar": {
|
||||
"reviewModal": {
|
||||
"TITLE": "Termine bestätigen",
|
||||
"EXPORT": "Zum Kalender exportieren",
|
||||
"DOWNLOAD": "Termine herunterladen",
|
||||
"INCLUDE_CANCELLED": "Abgesagte Termine einbeziehen",
|
||||
"shareData": {
|
||||
"TITLE": "Kalender",
|
||||
"TEXT": "Enthält Termine aus der StApps App",
|
||||
"FILE_NAME": "kalender"
|
||||
},
|
||||
"dialogs": {
|
||||
"toCalendarConfirm": {
|
||||
"TITLE": "Zum Kalender exportieren",
|
||||
"DESCRIPTION": "Termine zum Gerätekalender exportieren?"
|
||||
},
|
||||
"cannotShare": {
|
||||
"TITLE": "Teilen nicht möglich",
|
||||
"DESCRIPTION": "Die Teilen Funktionalität wird von diesem Browser nicht unterstützt."
|
||||
},
|
||||
"unsupportedFileType": {
|
||||
"TITLE": "Dateityp nicht unterstützt",
|
||||
"DESCRIPTION": "Kalenderdateien können von diesem Browser aus nicht geteilt werden."
|
||||
},
|
||||
"failedShare": {
|
||||
"TITLE": "Fehler beim Teilen",
|
||||
"DESCRIPTION": "Unbekannter Fehler beim Teilen."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"recurring": "Stundenplan",
|
||||
"calendar": "Kalender",
|
||||
"single": "Einzeltermine",
|
||||
@@ -316,6 +349,44 @@
|
||||
"resetAlert.buttonCancel": "Abbrechen",
|
||||
"resetToast.message": "Einstellungen wurden zurückgesetzt",
|
||||
"title": "Einstellungen",
|
||||
"resetSettings": "Einstellungen zurücksetzen"
|
||||
"resetSettings": "Einstellungen zurücksetzen",
|
||||
"calendar": {
|
||||
"title": "Kalender",
|
||||
"sync": {
|
||||
"title": "Synchronisierung",
|
||||
"unavailableWeb": "Synchronisierung mit dem Gerätekalender wird im Web nicht unterstützt.",
|
||||
"syncWithCalendar": "Mit Gerätekalender synchronisieren",
|
||||
"eventNotifications": "Bei Terminänderungen benachrichtigen"
|
||||
},
|
||||
"export": {
|
||||
"title": "Export",
|
||||
"exportEvents": "Alle Termine exportieren",
|
||||
"backup": "Backup",
|
||||
"restore": "Wiederherstellen",
|
||||
"fileName": "kalender_backup",
|
||||
"dialogs": {
|
||||
"backup": {
|
||||
"save": {
|
||||
"title": "StApps Kalender Backup",
|
||||
"message": "Enthält eine vollständige Terminliste des Kalenders. Diese Datei kann zum wiederherstellen des Kalenders wieder importiert werden."
|
||||
}
|
||||
},
|
||||
"restore": {
|
||||
"rejectFile": {
|
||||
"title": "Ungültige Datei",
|
||||
"message": "Die ausgewählte Datei ist keine gültige StApps Kalender Backup-Datei."
|
||||
},
|
||||
"success": {
|
||||
"title": "Wiederherstellung erfolgreich",
|
||||
"message": "Der Kalender wurde erfolgreich wiederhergestellt."
|
||||
},
|
||||
"error": {
|
||||
"title": "Wiederherstellung fehlgeschlagen",
|
||||
"message": "Beim Wiederherstellen des Kalenders ist ein Fehler aufgetreten."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
{
|
||||
"ok": "ok",
|
||||
"abort": "abort",
|
||||
"ok": "Ok",
|
||||
"abort": "Abort",
|
||||
"export": "Export",
|
||||
"share": "Share",
|
||||
"modal": {
|
||||
"DISMISS": "close"
|
||||
"DISMISS": "Close"
|
||||
},
|
||||
"app": {
|
||||
"ui": {
|
||||
@@ -276,6 +278,37 @@
|
||||
}
|
||||
},
|
||||
"schedule": {
|
||||
"toCalendar": {
|
||||
"reviewModal": {
|
||||
"TITLE": "Confirm events",
|
||||
"EXPORT": "Export to calendar",
|
||||
"DOWNLOAD": "Download events",
|
||||
"INCLUDE_CANCELLED": "Include cancelled events",
|
||||
"shareData": {
|
||||
"TITLE": "Calendar",
|
||||
"TEXT": "Contains events from StApps",
|
||||
"FILE_NAME": "calendar"
|
||||
},
|
||||
"dialogs": {
|
||||
"toCalendarConfirm": {
|
||||
"TITLE": "Export to calendar",
|
||||
"DESCRIPTION": "Do you want to export the selected events to your device calendar?"
|
||||
},
|
||||
"cannotShare": {
|
||||
"TITLE": "Sharing failed",
|
||||
"DESCRIPTION": "Your browser does not support sharing."
|
||||
},
|
||||
"unsupportedFileType": {
|
||||
"TITLE": "Unsupported file type",
|
||||
"DESCRIPTION": "Your browser does not support sharing calendar files."
|
||||
},
|
||||
"failedShare": {
|
||||
"TITLE": "Sharing failed",
|
||||
"DESCRIPTION": "Sharing failed. Please try again."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"recurring": "Recurring",
|
||||
"calendar": "Calendar",
|
||||
"single": "Single Events",
|
||||
@@ -317,6 +350,44 @@
|
||||
"resetAlert.buttonCancel": "cancel",
|
||||
"resetToast.message": "Settings reset",
|
||||
"title": "Settings",
|
||||
"resetSettings": "Reset Settings"
|
||||
"resetSettings": "Reset Settings",
|
||||
"calendar": {
|
||||
"title": "Calendar",
|
||||
"sync": {
|
||||
"title": "Sync",
|
||||
"unavailableWeb": "Sync with device calendar is not available in web version.",
|
||||
"syncWithCalendar": "Sync with device calendar",
|
||||
"eventNotifications": "Event update notifications"
|
||||
},
|
||||
"export": {
|
||||
"title": "Export",
|
||||
"exportEvents": "Export all events",
|
||||
"backup": "Backup",
|
||||
"restore": "Restore",
|
||||
"fileName": "calendar_backup",
|
||||
"dialogs": {
|
||||
"backup": {
|
||||
"save": {
|
||||
"title": "StApps Calendar Backup",
|
||||
"message": "Contains a list of all events in the calendar. You can import this file to restore your calendar."
|
||||
}
|
||||
},
|
||||
"restore": {
|
||||
"rejectFile": {
|
||||
"title": "Invalid file",
|
||||
"message": "The file you selected is not a valid backup file."
|
||||
},
|
||||
"success": {
|
||||
"title": "Restore successful",
|
||||
"message": "Calendar has been restored successfully."
|
||||
},
|
||||
"error": {
|
||||
"title": "Restore failed",
|
||||
"message": "Backup file is corrupted."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user