Start offline support
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Garrett Mills 2020-10-21 13:54:18 -05:00
parent a72fc72c83
commit f6168b6b7c
Signed by: garrettmills
GPG Key ID: D2BF5FBA8298F246
11 changed files with 694 additions and 85 deletions

180
package-lock.json generated
View File

@ -735,6 +735,88 @@
} }
} }
}, },
"@angular/pwa": {
"version": "0.1001.7",
"resolved": "https://registry.npmjs.org/@angular/pwa/-/pwa-0.1001.7.tgz",
"integrity": "sha512-/vGJ/Z6lY8qT3fT1DzJ4D2iz0WYVxJSnmXAhXwltxJQwKHkgJZFnEzOudPpIVMVs9LO68KLnoM6wshU4ZyFgkg==",
"requires": {
"@angular-devkit/core": "10.1.7",
"@angular-devkit/schematics": "10.1.7",
"@schematics/angular": "10.1.7",
"parse5-html-rewriting-stream": "6.0.1"
},
"dependencies": {
"@angular-devkit/core": {
"version": "10.1.7",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-10.1.7.tgz",
"integrity": "sha512-RRyDkN2FByA+nlnRx/MzUMK1FXwj7+SsrzJcvZfWx4yA5rfKmJiJryXQEzL44GL1aoaXSuvOYu3H72wxZADN8Q==",
"requires": {
"ajv": "6.12.4",
"fast-json-stable-stringify": "2.1.0",
"magic-string": "0.25.7",
"rxjs": "6.6.2",
"source-map": "0.7.3"
}
},
"@angular-devkit/schematics": {
"version": "10.1.7",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-10.1.7.tgz",
"integrity": "sha512-nk9RXA09b+7uq59HS/gyztNzUGHH/eQAUQhWHdDYSCG6v1lhJVCKx1HgDPELVxmeq9f+HArkAW7Y7c+ccdNQ7A==",
"requires": {
"@angular-devkit/core": "10.1.7",
"ora": "5.0.0",
"rxjs": "6.6.2"
}
},
"@schematics/angular": {
"version": "10.1.7",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-10.1.7.tgz",
"integrity": "sha512-jcyLWDSbpgHvB/BNVSsV4uLJpC2qRx9Z5+rcQpBB1BerqIPS/1cTQg7TViHZtcqnZqWvzHR3jfqzDUSOCZpuJQ==",
"requires": {
"@angular-devkit/core": "10.1.7",
"@angular-devkit/schematics": "10.1.7",
"jsonc-parser": "2.3.0"
}
},
"ajv": {
"version": "6.12.4",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz",
"integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==",
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
},
"magic-string": {
"version": "0.25.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
"integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
"requires": {
"sourcemap-codec": "^1.4.4"
}
},
"rxjs": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz",
"integrity": "sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==",
"requires": {
"tslib": "^1.9.0"
}
}
}
},
"@angular/router": { "@angular/router": {
"version": "10.1.5", "version": "10.1.5",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-10.1.5.tgz", "resolved": "https://registry.npmjs.org/@angular/router/-/router-10.1.5.tgz",
@ -3624,7 +3706,6 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
"integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
"dev": true,
"requires": { "requires": {
"restore-cursor": "^3.1.0" "restore-cursor": "^3.1.0"
} }
@ -3632,8 +3713,7 @@
"cli-spinners": { "cli-spinners": {
"version": "2.5.0", "version": "2.5.0",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.5.0.tgz", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.5.0.tgz",
"integrity": "sha512-PC+AmIuK04E6aeSs/pUccSujsTzBhu4HzC2dL+CfJB/Jcc2qTRbEwZQDfIUpt2Xl8BodYBEq8w4fc0kU2I9DjQ==", "integrity": "sha512-PC+AmIuK04E6aeSs/pUccSujsTzBhu4HzC2dL+CfJB/Jcc2qTRbEwZQDfIUpt2Xl8BodYBEq8w4fc0kU2I9DjQ=="
"dev": true
}, },
"cli-width": { "cli-width": {
"version": "3.0.0", "version": "3.0.0",
@ -4651,7 +4731,6 @@
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
"integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
"dev": true,
"requires": { "requires": {
"clone": "^1.0.2" "clone": "^1.0.2"
}, },
@ -4659,8 +4738,7 @@
"clone": { "clone": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
"integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4="
"dev": true
} }
} }
}, },
@ -4813,6 +4891,11 @@
"integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==",
"dev": true "dev": true
}, },
"dexie": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/dexie/-/dexie-3.0.2.tgz",
"integrity": "sha512-go4FnIoAhcUiCdxutfIZRxnSaSyDgfEq+GH7N0I8nTCJbC2FmeBj+0FrETa3ln5ix+VQMOPsFeYHlgE/8SZWwQ=="
},
"dezalgo": { "dezalgo": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz",
@ -7038,8 +7121,7 @@
"is-interactive": { "is-interactive": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
"integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="
"dev": true
}, },
"is-negative-zero": { "is-negative-zero": {
"version": "2.0.0", "version": "2.0.0",
@ -7493,8 +7575,7 @@
"json-schema-traverse": { "json-schema-traverse": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
"dev": true
}, },
"json-stringify-safe": { "json-stringify-safe": {
"version": "5.0.1", "version": "5.0.1",
@ -7520,8 +7601,7 @@
"jsonc-parser": { "jsonc-parser": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.0.tgz", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.0.tgz",
"integrity": "sha512-b0EBt8SWFNnixVdvoR2ZtEGa9ZqLhbJnOjezn+WP+8kspFm+PFYDN8Z4Bc7pRlDjvuVcADSUkroIuTWWn/YiIA==", "integrity": "sha512-b0EBt8SWFNnixVdvoR2ZtEGa9ZqLhbJnOjezn+WP+8kspFm+PFYDN8Z4Bc7pRlDjvuVcADSUkroIuTWWn/YiIA=="
"dev": true
}, },
"jsonfile": { "jsonfile": {
"version": "4.0.0", "version": "4.0.0",
@ -8055,7 +8135,6 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz",
"integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==",
"dev": true,
"requires": { "requires": {
"chalk": "^4.0.0" "chalk": "^4.0.0"
}, },
@ -8064,7 +8143,6 @@
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": { "requires": {
"color-convert": "^2.0.1" "color-convert": "^2.0.1"
} }
@ -8073,7 +8151,6 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
"supports-color": "^7.1.0" "supports-color": "^7.1.0"
@ -8083,7 +8160,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": { "requires": {
"color-name": "~1.1.4" "color-name": "~1.1.4"
} }
@ -8091,20 +8167,17 @@
"color-name": { "color-name": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
"dev": true
}, },
"has-flag": { "has-flag": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
"dev": true
}, },
"supports-color": { "supports-color": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": { "requires": {
"has-flag": "^4.0.0" "has-flag": "^4.0.0"
} }
@ -8412,8 +8485,7 @@
"mimic-fn": { "mimic-fn": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
"dev": true
}, },
"mini-css-extract-plugin": { "mini-css-extract-plugin": {
"version": "0.10.0", "version": "0.10.0",
@ -8653,8 +8725,7 @@
"mute-stream": { "mute-stream": {
"version": "0.0.8", "version": "0.0.8",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
"dev": true
}, },
"nanomatch": { "nanomatch": {
"version": "1.2.13", "version": "1.2.13",
@ -8700,6 +8771,14 @@
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
"dev": true "dev": true
}, },
"ng-connection-service": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/ng-connection-service/-/ng-connection-service-1.0.4.tgz",
"integrity": "sha512-WrZfK+hUzrJS77ItxXI08rUN6Av77W3+LsaJEPufyo2wRe7Tn8xG18FHHEbbgqKkJeDT/yGJBH2xOaT+1jb22g==",
"requires": {
"tslib": "^1.9.0"
}
},
"ngx-markdown": { "ngx-markdown": {
"version": "10.1.1", "version": "10.1.1",
"resolved": "https://registry.npmjs.org/ngx-markdown/-/ngx-markdown-10.1.1.tgz", "resolved": "https://registry.npmjs.org/ngx-markdown/-/ngx-markdown-10.1.1.tgz",
@ -9239,7 +9318,6 @@
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dev": true,
"requires": { "requires": {
"mimic-fn": "^2.1.0" "mimic-fn": "^2.1.0"
} }
@ -9293,7 +9371,6 @@
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/ora/-/ora-5.0.0.tgz", "resolved": "https://registry.npmjs.org/ora/-/ora-5.0.0.tgz",
"integrity": "sha512-s26qdWqke2kjN/wC4dy+IQPBIMWBJlSU/0JZhk30ZDBLelW25rv66yutUWARMigpGPzcXHb+Nac5pNhN/WsARw==", "integrity": "sha512-s26qdWqke2kjN/wC4dy+IQPBIMWBJlSU/0JZhk30ZDBLelW25rv66yutUWARMigpGPzcXHb+Nac5pNhN/WsARw==",
"dev": true,
"requires": { "requires": {
"chalk": "^4.1.0", "chalk": "^4.1.0",
"cli-cursor": "^3.1.0", "cli-cursor": "^3.1.0",
@ -9308,14 +9385,12 @@
"ansi-regex": { "ansi-regex": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
"dev": true
}, },
"ansi-styles": { "ansi-styles": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": { "requires": {
"color-convert": "^2.0.1" "color-convert": "^2.0.1"
} }
@ -9324,7 +9399,6 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
"supports-color": "^7.1.0" "supports-color": "^7.1.0"
@ -9334,7 +9408,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": { "requires": {
"color-name": "~1.1.4" "color-name": "~1.1.4"
} }
@ -9342,20 +9415,17 @@
"color-name": { "color-name": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
"dev": true
}, },
"has-flag": { "has-flag": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
"dev": true
}, },
"strip-ansi": { "strip-ansi": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dev": true,
"requires": { "requires": {
"ansi-regex": "^5.0.0" "ansi-regex": "^5.0.0"
} }
@ -9364,7 +9434,6 @@
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": { "requires": {
"has-flag": "^4.0.0" "has-flag": "^4.0.0"
} }
@ -9693,8 +9762,16 @@
"parse5": { "parse5": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
"dev": true },
"parse5-html-rewriting-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-6.0.1.tgz",
"integrity": "sha512-vwLQzynJVEfUlURxgnf51yAJDQTtVpNyGD8tKi2Za7m+akukNHxCcUQMAa/mUGLhCeicFdpy7Tlvj8ZNKadprg==",
"requires": {
"parse5": "^6.0.1",
"parse5-sax-parser": "^6.0.1"
}
}, },
"parse5-htmlparser2-tree-adapter": { "parse5-htmlparser2-tree-adapter": {
"version": "6.0.1", "version": "6.0.1",
@ -9705,6 +9782,14 @@
"parse5": "^6.0.1" "parse5": "^6.0.1"
} }
}, },
"parse5-sax-parser": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-6.0.1.tgz",
"integrity": "sha512-kXX+5S81lgESA0LsDuGjAlBybImAChYRMT+/uKCEXFBFOeEhS52qUCydGhU3qLRD8D9DVjaUo821WK7DM4iCeg==",
"requires": {
"parse5": "^6.0.1"
}
},
"parseqs": { "parseqs": {
"version": "0.0.5", "version": "0.0.5",
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
@ -10968,8 +11053,7 @@
"punycode": { "punycode": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
"dev": true
}, },
"q": { "q": {
"version": "1.4.1", "version": "1.4.1",
@ -11438,7 +11522,6 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
"integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
"dev": true,
"requires": { "requires": {
"onetime": "^5.1.0", "onetime": "^5.1.0",
"signal-exit": "^3.0.2" "signal-exit": "^3.0.2"
@ -11940,8 +12023,7 @@
"signal-exit": { "signal-exit": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
"dev": true
}, },
"simple-swizzle": { "simple-swizzle": {
"version": "0.2.2", "version": "0.2.2",
@ -12316,8 +12398,7 @@
"source-map": { "source-map": {
"version": "0.7.3", "version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
"dev": true
}, },
"source-map-loader": { "source-map-loader": {
"version": "1.0.2", "version": "1.0.2",
@ -12389,8 +12470,7 @@
"sourcemap-codec": { "sourcemap-codec": {
"version": "1.4.8", "version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
"dev": true
}, },
"spdx-correct": { "spdx-correct": {
"version": "3.1.1", "version": "3.1.1",
@ -13432,7 +13512,6 @@
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz",
"integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==",
"dev": true,
"requires": { "requires": {
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
@ -13886,7 +13965,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
"integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
"dev": true,
"requires": { "requires": {
"defaults": "^1.0.3" "defaults": "^1.0.3"
} }

View File

@ -30,7 +30,9 @@
"ag-grid-angular": "^22.1.1", "ag-grid-angular": "^22.1.1",
"ag-grid-community": "^22.1.1", "ag-grid-community": "^22.1.1",
"core-js": "^2.5.4", "core-js": "^2.5.4",
"dexie": "^3.0.2",
"moment": "^2.24.0", "moment": "^2.24.0",
"ng-connection-service": "^1.0.4",
"ngx-markdown": "^10.1.1", "ngx-markdown": "^10.1.1",
"ngx-monaco-editor": "^8.1.1", "ngx-monaco-editor": "^8.1.1",
"rxjs": "~6.6.3", "rxjs": "~6.6.3",

View File

@ -21,6 +21,7 @@ import {SessionService} from './service/session.service';
import {SearchComponent} from './components/search/Search.component'; import {SearchComponent} from './components/search/Search.component';
import {NodeTypeIcons} from './structures/node-types'; import {NodeTypeIcons} from './structures/node-types';
import {NavigationService} from './service/navigation.service'; import {NavigationService} from './service/navigation.service';
import {DatabaseService} from './service/db/database.service';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@ -79,6 +80,7 @@ export class AppComponent implements OnInit {
protected hasSearchOpen = false; protected hasSearchOpen = false;
protected versionInterval?: any; protected versionInterval?: any;
protected showedNewVersionAlert = false; protected showedNewVersionAlert = false;
protected showedOfflineAlert = false;
protected initialized$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); protected initialized$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
constructor( constructor(
@ -94,11 +96,14 @@ export class AppComponent implements OnInit {
protected loading: LoadingController, protected loading: LoadingController,
protected navService: NavigationService, protected navService: NavigationService,
protected toasts: ToastController, protected toasts: ToastController,
protected db: DatabaseService,
) { ) {
this.initializeApp(); this.initializeApp();
} }
_doInit() { async _doInit() {
await this.db.createSchemata();
this.reloadMenuItems().subscribe(() => { this.reloadMenuItems().subscribe(() => {
this.ready$.next(true); this.ready$.next(true);
setTimeout(() => { setTimeout(() => {
@ -402,17 +407,16 @@ export class AppComponent implements OnInit {
reloadMenuItems() { reloadMenuItems() {
return new Observable(sub => { return new Observable(sub => {
this.api.get('/menu/items').subscribe(result => { this.api.getMenuItems().then(nodes => {
this.nodes = result.data; this.nodes = nodes;
setTimeout(() => {
sub.next(); sub.next();
sub.complete(); sub.complete();
}, 0);
}); });
}); });
} }
async initializeApp() { async initializeApp() {
console.log('app', this);
this.loader = await this.loading.create({ this.loader = await this.loading.create({
message: 'Starting up...', message: 'Starting up...',
cssClass: 'noded-loading-mask', cssClass: 'noded-loading-mask',
@ -420,8 +424,24 @@ export class AppComponent implements OnInit {
}); });
await this.loader.present(); await this.loader.present();
await this.platform.ready(); await this.platform.ready();
let toast: any;
this.api.offline$.subscribe(async isOffline => {
if ( isOffline && !this.showedOfflineAlert ) {
toast = await this.toasts.create({
cssClass: 'compat-toast-container',
message: 'Uh, oh! It looks like you\'re offline. Some features might not work as expected...',
});
this.showedOfflineAlert = true;
await toast.present();
} else if ( !isOffline && this.showedOfflineAlert ) {
await toast.dismiss();
this.showedOfflineAlert = false;
}
});
const stat: any = await this.session.stat(); const stat: any = await this.session.stat();
if ( !stat.authenticated_user ) { if ( !stat.authenticated_user ) {

View File

@ -15,6 +15,7 @@ import {AgGridModule} from 'ag-grid-angular';
import {MonacoEditorModule} from 'ngx-monaco-editor'; import {MonacoEditorModule} from 'ngx-monaco-editor';
import { APP_BASE_HREF, PlatformLocation } from '@angular/common'; import { APP_BASE_HREF, PlatformLocation } from '@angular/common';
import { MarkdownModule } from 'ngx-markdown'; import { MarkdownModule } from 'ngx-markdown';
import {ConnectionServiceModule} from 'ng-connection-service';
/** /**
* This function is used internal to get a string instance of the `<base href="" />` value from `index.html`. * This function is used internal to get a string instance of the `<base href="" />` value from `index.html`.
@ -44,6 +45,7 @@ export function getBaseHref(platformLocation: PlatformLocation): string {
AgGridModule.withComponents([]), AgGridModule.withComponents([]),
MonacoEditorModule.forRoot(), MonacoEditorModule.forRoot(),
MarkdownModule.forRoot(), MarkdownModule.forRoot(),
ConnectionServiceModule,
], ],
providers: [ providers: [
StatusBar, StatusBar,

View File

@ -1,8 +1,11 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {environment} from '../../environments/environment'; import {environment} from '../../environments/environment';
import {HttpClient} from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import {Observable} from 'rxjs'; import {BehaviorSubject, Observable} from 'rxjs';
import ApiResponse from '../structures/ApiResponse'; import ApiResponse from '../structures/ApiResponse';
import {MenuItem} from './db/MenuItem';
import {DatabaseService} from './db/database.service';
import {ConnectionService} from 'ng-connection-service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -11,10 +14,36 @@ export class ApiService {
protected baseEndpoint: string = environment.backendBase; protected baseEndpoint: string = environment.backendBase;
protected statUrl: string = environment.statUrl; protected statUrl: string = environment.statUrl;
protected versionUrl: string = environment.versionUrl; protected versionUrl: string = environment.versionUrl;
protected offline = false;
public readonly offline$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
get isOffline() {
return this.offline;
}
constructor( constructor(
protected http: HttpClient, protected http: HttpClient,
) { } protected db: DatabaseService,
protected connection: ConnectionService,
) {
connection.monitor().subscribe(isConnected => {
if ( !isConnected ) {
this.makeOffline();
} else {
this.makeOnline(); // TODO add checks for server.
}
});
}
public makeOffline() {
this.offline = true;
this.offline$.next(true);
}
public makeOnline() {
this.offline = false;
this.offline$.next(false);
}
public get(endpoint, params = {}): Observable<ApiResponse> { public get(endpoint, params = {}): Observable<ApiResponse> {
return this.request(endpoint, params, 'get'); return this.request(endpoint, params, 'get');
@ -29,14 +58,55 @@ export class ApiService {
} }
public stat(): Observable<ApiResponse> { public stat(): Observable<ApiResponse> {
return this._request(this.statUrl); return new Observable<ApiResponse>(sub => {
(async () => {
const statKV = await this.db.getKeyValue('host_stat');
// If offline, look up the last stored stat for information
if ( this.isOffline ) {
if ( typeof statKV !== 'object' ) {
throw new Error('No locally stored host stat found.');
}
sub.next(new ApiResponse(statKV.data));
sub.complete();
}
// Otherwise, fetch the stat and cache it locally
this._request(this.statUrl).subscribe(apiResponse => {
statKV.data = {status: apiResponse.status, message: apiResponse.message, data: apiResponse.data};
statKV.save().then(() => {
sub.next(statKV.data);
sub.complete();
});
});
})();
});
} }
public version(): Promise<string> { public version(): Promise<string> {
return new Promise((res, rej) => { return new Promise(async (res, rej) => {
const versionKV = await this.db.getKeyValue('app_version');
// If offline, look up the local app version.
if ( this.isOffline ) {
if ( versionKV ) {
return res(versionKV.data);
} else {
return rej(new Error('No local app version found.'));
}
}
// Otherwise, look up the app version and store it locally
this._request(this.versionUrl).subscribe({ this._request(this.versionUrl).subscribe({
next: result => { next: async result => {
res(result.data.text.trim()); const version = result.data.text.trim();
versionKV.data = version;
await versionKV.save();
res(version);
}, },
error: rej, error: rej,
}); });
@ -76,4 +146,79 @@ export class ApiService {
return `${this.baseEndpoint.endsWith('/') ? this.baseEndpoint.slice(0, -1) : this.baseEndpoint}${endpoint}`; return `${this.baseEndpoint.endsWith('/') ? this.baseEndpoint.slice(0, -1) : this.baseEndpoint}${endpoint}`;
} }
public getMenuItems(): Promise<any[]> {
return new Promise(async (res, rej) => {
await this.db.createSchemata();
// If offline, fetch the menu from the database
if ( this.isOffline ) {
const items = await this.db.menuItems.toArray();
const nodes = MenuItem.inflateTree(items as MenuItem[]);
return res(nodes);
}
// Download the latest menu items
const tree: any[] = await new Promise(res2 => {
this.get('/menu/items').subscribe({
next: async result => {
const nodes = result.data as any[];
const items = MenuItem.deflateTree(nodes);
// Update the locally stored nodes
await this.db.menuItems.clear();
await Promise.all(items.map(item => item.save()));
res2(nodes);
},
error: rej,
});
});
res(tree);
});
}
public getSessionData(): Promise<any> {
return new Promise(async (res, rej) => {
const sessionKV = await this.db.getKeyValue('session_data');
// If offline, just return the locally cached session data
if ( this.isOffline ) {
if ( typeof sessionKV.data !== 'object' ) {
return rej(new Error('No locally cached session data found.'));
}
return res(sessionKV.data);
}
// Otherwise, fetch the session data from the server and cache it locally
this.get('/session').subscribe(async result => {
sessionKV.data = result.data;
await sessionKV.save();
res(sessionKV.data);
});
});
}
public saveSessionData(data: any): Promise<void> {
return new Promise(async (res, rej) => {
// Update the local session data
const sessionKV = await this.db.getKeyValue('session_data');
sessionKV.data = data;
await sessionKV.save();
// If we're not offline, then update the data on the server
if ( !this.isOffline ) {
await new Promise(res2 => {
this.post('/session', data || {}).subscribe({
next: res2,
error: rej,
});
});
}
res();
});
}
} }

View File

@ -0,0 +1,65 @@
import {Model} from './Model';
export interface IKeyValue {
id?: number;
key: string;
value: string;
json: boolean;
}
export class KeyValue extends Model<IKeyValue> implements IKeyValue {
id?: number;
key: string;
value: string;
json: boolean;
public static getTableName() {
return 'keyValues';
}
public static getSchema() {
return '++id, key, value, json';
}
constructor(key: string, value: string, json: boolean, id?: number) {
super();
this.key = key;
this.value = value;
this.json = json;
if ( id ) {
this.id = id;
}
}
get data(): any {
if ( this.json ) {
return JSON.parse(this.value);
}
return this.value;
}
set data(val: any) {
if ( typeof val === 'string' ) {
this.json = false;
this.value = val;
} else {
this.json = true;
this.value = JSON.stringify(val);
}
}
public getSaveRecord(): any {
return {
...(this.id ? { id: this.id } : {}),
key: this.key,
value: this.value,
json: this.json,
};
}
public getDatabase(): Dexie.Table<IKeyValue, number> {
return this.staticClass().dbService.table('keyValues') as Dexie.Table<IKeyValue, number>;
}
}

View File

@ -0,0 +1,171 @@
import {Model} from './Model';
import Dexie from 'dexie';
export interface IMenuItem {
id?: number;
serverId: string;
name: string;
childIds?: string[];
noDelete?: boolean;
noChildren?: boolean;
virtual?: boolean;
type?: string;
shared?: boolean;
needsServerUpdate?: boolean;
}
export class MenuItem extends Model<IMenuItem> implements IMenuItem {
id?: number;
serverId: string;
name: string;
childIds?: string[];
noDelete?: boolean;
noChildren?: boolean;
virtual?: boolean;
type?: string;
shared?: boolean;
needsServerUpdate?: boolean;
public static getTableName() {
return 'menuItems';
}
public static getSchema() {
return '++id, serverId, name, childIds, noDelete, noChildren, virtual, type, shared, needsServerUpdate';
}
public static deflateTree(nodes: any[]): MenuItem[] {
let items = [];
for ( const node of nodes ) {
const childIds = node.children ? node.children.map(x => x.id) : [];
const item = new MenuItem(node.name, node.id, childIds, node.noDelete, node.noChildren, node.virtual, node.type, node.shared);
items.push(item);
if ( node.children ) {
items = items.concat(...this.deflateTree(node.children));
}
}
return items;
}
public static inflateTree(items: MenuItem[]) {
const serverIdXItems: { [key: string]: MenuItem[] } = {};
for ( const item of items ) {
if ( !serverIdXItems[item.serverId] ) {
serverIdXItems[item.serverId] = [];
}
serverIdXItems[item.serverId].push(item);
}
const inflateNode = (item, alreadyChildren = [], seen = []) => {
const node: any = item.getSaveRecord();
seen.push(item);
node.id = node.serverId;
node.children = [];
if ( item.childIds ) {
for ( const childId of item.childIds ) {
if ( serverIdXItems[childId] ) {
const children = serverIdXItems[childId].filter(x => {
if ( x.type !== 'page' && item.serverId !== x.serverId ) {
return false;
}
return x !== item && !alreadyChildren.includes(x) && !seen.includes(x);
});
node.children = node.children.concat(...children.map(x => inflateNode(x, children, seen)));
}
}
}
const pageChildren = [];
const otherChildren = [];
for ( const child of node.children ) {
if ( child.type === 'page' ) {
pageChildren.push(child);
} else {
otherChildren.push(child);
}
}
node.children = [...otherChildren, ...pageChildren];
return node;
};
const topLevelItems = items.filter(x => String(x.serverId) === '0');
return topLevelItems.map(x => inflateNode(x));
}
constructor(
name: string,
serverId: string,
childIds?: string[],
noDelete?: boolean,
noChildren?: boolean,
virtual?: boolean,
type?: string,
shared?: boolean,
needsServerUpdate?: boolean,
id?: number
) {
super();
this.name = name;
this.serverId = serverId;
if ( childIds ) {
this.childIds = childIds;
}
if ( typeof noDelete !== 'undefined' ) {
this.noDelete = noDelete;
}
if ( typeof noChildren !== 'undefined' ) {
this.noChildren = noChildren;
}
if ( typeof virtual !== 'undefined' ) {
this.virtual = virtual;
}
if ( type ) {
this.type = type;
}
if ( typeof shared !== 'undefined' ) {
this.shared = shared;
}
if ( typeof needsServerUpdate !== 'undefined' ) {
this.needsServerUpdate = needsServerUpdate;
}
if ( id ) {
this.id = id;
}
}
public getDatabase(): Dexie.Table<IMenuItem, number> {
return this.staticClass().dbService.table('menuItems') as Dexie.Table<IMenuItem, number>;
}
public getSaveRecord(): any {
return {
...(this.id ? { id: this.id } : {}),
serverId: this.serverId,
name: this.name,
...(typeof this.childIds !== 'undefined' ? { childIds: this.childIds } : {}),
...(typeof this.noDelete !== 'undefined' ? { noDelete: this.noDelete } : {}),
...(typeof this.noChildren !== 'undefined' ? { noChildren: this.noChildren } : {}),
...(typeof this.virtual !== 'undefined' ? { virtual: this.virtual } : {}),
...(typeof this.type !== 'undefined' ? { type: this.type } : {}),
...(typeof this.shared !== 'undefined' ? { shared: this.shared } : {}),
...(typeof this.needsServerUpdate !== 'undefined' ? { needsServerUpdate: this.needsServerUpdate } : {}),
};
}
}

View File

@ -0,0 +1,43 @@
import {Model} from './Model';
export interface IMigration {
id?: number;
uuid: string;
applied: boolean;
}
export class Migration extends Model<IMigration> implements IMigration {
id?: number;
uuid: string;
applied: boolean;
public static getTableName() {
return 'migrations';
}
public static getSchema() {
return '++id, uuid, applied';
}
constructor(uuid: string, applied: boolean, id?: number) {
super();
this.uuid = uuid;
this.applied = applied;
if ( id ) {
this.id = id;
}
}
public getSaveRecord(): any {
return {
...(this.id ? { id: this.id } : {}),
uuid: this.uuid,
applied: this.applied,
};
}
public getDatabase(): Dexie.Table<IMigration, number> {
return this.staticClass().dbService.table('migrations') as Dexie.Table<IMigration, number>;
}
}

View File

@ -0,0 +1,31 @@
import Dexie from 'dexie';
import {DatabaseService} from './database.service';
export abstract class Model<InterfaceType> {
public static dbService?: DatabaseService;
public id?: number;
public static getSchema(): string {
throw new TypeError('Child class must implement.');
}
public static getTableName(): string {
throw new TypeError('Child class must implement.');
}
public abstract getDatabase(): Dexie.Table<InterfaceType, number>;
public abstract getSaveRecord(): any;
public staticClass() {
return (this.constructor as typeof Model);
}
public exists() {
return !!this.id;
}
public async save() {
this.id = await this.getDatabase().put(this.getSaveRecord());
}
}

View File

@ -0,0 +1,67 @@
import { Injectable } from '@angular/core';
import Dexie from 'dexie';
import {IMigration, Migration} from './Migration';
import {IMenuItem, MenuItem} from './MenuItem';
import {KeyValue, IKeyValue} from './KeyValue';
@Injectable({
providedIn: 'root'
})
export class DatabaseService extends Dexie {
protected static registeredModels = [Migration, MenuItem, KeyValue];
protected initialized = false;
migrations!: Dexie.Table<IMigration, number>;
menuItems!: Dexie.Table<IMenuItem, number>;
keyValues!: Dexie.Table<IKeyValue, number>;
constructor(
) {
super('NodedLocalDatabase');
}
public async getKeyValue(key: string): Promise<KeyValue> {
const matches = await this.keyValues.where({ key }).toArray();
if ( matches.length > 0 ) {
return matches[0] as KeyValue;
}
return new KeyValue(key, '', false);
}
public async createSchemata() {
if ( this.initialized ) {
return;
}
this.initialized = true;
console.log('db', this);
const staticClass = this.constructor as typeof DatabaseService;
const schema: any = {};
for ( const ModelClass of staticClass.registeredModels ) {
ModelClass.dbService = this;
schema[ModelClass.getTableName()] = ModelClass.getSchema();
}
await this.version(3).stores(schema);
await this.open();
this.migrations = this.table('migrations');
this.migrations.mapToClass(Migration);
this.menuItems = this.table('menuItems');
this.menuItems.mapToClass(MenuItem);
this.keyValues = this.table('keyValues');
this.keyValues.mapToClass(KeyValue);
// await new Promise(res => {
// setTimeout(() => {
// res();
// }, 1000);
// });
}
}

View File

@ -50,28 +50,13 @@ export class SessionService {
} }
async initialize() { async initialize() {
return new Promise((res, rej) => { this.data = await this.api.getSessionData();
this.api.get('/session').subscribe(response => {
this.data = response.data;
res();
});
});
} }
async save() { async save() {
this.saving = true; this.saving = true;
return new Promise((res, rej) => { await this.api.saveSessionData(this.data);
this.api.post('/session', this.data || {}).subscribe({
next: result => {
res();
this.saving = false; this.saving = false;
},
error: (e) => {
this.saving = false;
rej(e);
},
});
});
} }
buildAppUrl(...parts: string[]): string { buildAppUrl(...parts: string[]): string {