-
-
-
-
-
-
- {{ translateKeyForType("loading") | translate:{value: null} }}
-
-
- {{ translateKeyForType("loading_subtitle") | translate:{value: null} }}
+
+
+
+
+ {{ translateKeyForType("loading") | translate }}
+
+
+ {{ translateKeyForType("loading_subtitle") | translate }}
-
-
-
- A new loading Animation with every Version:
Candles Animation by Akhil Sai Ram (2018)
+
+
+
+
+
+ A new loading Animation with every Version:
Candles Animation by Akhil Sai Ram
+ (2018)
diff --git a/src/app/fallback/loading/loading.scss b/src/app/fallback/loading/loading.scss
new file mode 100644
index 00000000..466140aa
--- /dev/null
+++ b/src/app/fallback/loading/loading.scss
@@ -0,0 +1,640 @@
+ // colors
+$lightsOnBg: #FEF4AD;
+$lightsOutBg: #F8AE39;
+$candleColor:#FFFFFD;
+$candleShadow:#673C63;
+$floorColor:#AD9598;
+$fireColor:#FF9800;
+$fireColor2:#FFC107;
+$fireShadow: #E7C980;
+$angerColor:#F44336;
+$primaryColor: #FEF4AD;
+$secondaryColor:rgb(0,0,0);
+$candleInsetShadow:#95c6f2;
+$smokeColor:grey;
+//sizes
+$floorWidth: 350px;
+$floorHeight:5px;
+$candleWidth:35px;
+$candleHeight:100px;
+$stickWidth:3px;
+$stickHeight:15px;
+
+body
+{
+ background-color:$lightsOnBg;
+ animation:change-background 3s infinite linear;
+}
+
+ion-progress-bar
+{
+ --progress-background: var(--ion-color-primary-contrast);
+ --buffer-background: var(--ion-color-primary-contrast);
+}
+
+.wrapper
+{
+ position:absolute;
+ left:50%;
+ top:70%;
+ transform:scale(1.5,1.5) translate(-50%,-50%);
+}
+.floor
+{
+ position:absolute;
+ left:50%;
+ top:50%;
+ width:$floorWidth;
+ height:$floorHeight;
+ background:$candleShadow;
+ transform:translate(-50%,-50%);
+ box-shadow:0px 2px 5px #111;
+ z-index:2;
+}
+.candles
+{
+ position:absolute;
+ left:50%;
+ top:50%;
+ width:250px;
+ height:150px;
+ // background:$secondaryColor;
+ // opacity:0.8;
+ transform:translate(-50%,-100%);
+ z-index:1;
+}
+.candle1
+{
+ position:absolute;
+ left:50%;
+ top:50%;
+ width:$candleWidth;
+ height:$candleHeight;
+ background:#fff;
+ border:3px solid $candleShadow;
+ border-bottom:0px;
+ border-radius:3px;
+ transform-origin:center right;
+ transform:translate(60%,-25%);
+ box-shadow: -2px 0px 0px $candleInsetShadow inset;
+ animation:expand-body 3s infinite linear;
+}
+.candle1__stick,.candle2__stick
+{
+ position:absolute;
+ left:50%;
+ top:0%;
+ width:$stickWidth;
+ height:$stickHeight;
+ background:$candleShadow;
+ border-radius:8px;
+ transform:translate(-50%,-100%);
+}
+.candle2__stick
+{
+ height:$stickHeight*0.8;
+ transform-origin: bottom center;
+ animation:stick-animation 3s infinite linear;
+}
+.candle1__eyes,.candle2__eyes
+{
+ position:absolute;
+ left:50%;
+ top:0%;
+ width:$candleWidth;
+ height:30px;
+ transform:translate(-50%,0%);
+}
+.candle1__eyes-one
+{
+ position:absolute;
+ left:30%;
+ top:20%;
+ width:5px;
+ height:5px;
+ border-radius:100%;
+ background:$candleShadow;
+ transform:translate(-70%,0%);
+ animation:blink-eyes 3s infinite linear;
+}
+.candle1__eyes-two
+{
+ position:absolute;
+ left:70%;
+ top:20%;
+ width:5px;
+ height:5px;
+ border-radius:100%;
+ background:$candleShadow;
+ transform:translate(-70%,0%);
+ animation:blink-eyes 3s infinite linear;
+}
+.candle1__mouth
+{
+ position:absolute;
+ left:40%;
+ top:20%;
+ width:0px;
+ height:0px;
+ border-radius:20px;
+ background:$candleShadow;
+ transform:translate(-50%,-50%);
+ animation: uff 3s infinite linear;
+}
+.candle__smoke-one
+{
+ position:absolute;
+ left:30%;
+ top:50%;
+ width:30px;
+ height:3px;
+ background:$smokeColor;
+ transform:translate(-50%,-50%);
+ animation:move-left 3s infinite linear;
+}
+.candle__smoke-two
+{
+ position:absolute;
+ left:30%;
+ top:40%;
+ width:10px;
+ height:10px;
+ border-radius:10px;
+ background:$smokeColor;
+ transform:translate(-50%,-50%);
+ animation:move-top 3s infinite linear;
+}
+
+.candle2
+{
+ position:absolute;
+ left:20%;
+ top:65%;
+ width:$candleWidth*1.20;
+ height:$candleHeight*0.60;
+ background:#fff;
+ border:3px solid $candleShadow;
+ border-bottom:0px;
+ border-radius:3px;
+ transform:translate(60%,-15%);
+ transform-origin:center right;
+ box-shadow: -2px 0px 0px $candleInsetShadow inset;
+ animation: shake-left 3s infinite linear;
+}
+.candle2__eyes-one
+{
+ position:absolute;
+ left:30%;
+ top:50%;
+ width:5px;
+ height:5px;
+ display:inline-block;
+ border:0px solid $candleShadow;
+ border-radius:100%;
+ float:left;
+ background:$candleShadow;
+ transform:translate(-80%,0%);
+ animation:changeto-lower 3s infinite linear;
+}
+.candle2__eyes-two
+{
+ position:absolute;
+ left:70%;
+ top:50%;
+ width:5px;
+ height:5px;
+ display:inline-block;
+ border:0px solid $candleShadow;
+ border-radius:100%;
+ float:left;
+ background:$candleShadow;
+ transform:translate(-80%,0%);
+ animation:changeto-greater 3s infinite linear;
+
+}
+.light__wave
+{
+ position:absolute;
+ top:35%;
+ left:35%;
+ width:75px;
+ height:75px;
+ border-radius:100%;
+ z-index:0;
+ transform:translate(-25%,-50%) scale(2.5,2.5);
+ border:2px solid rgba(255,255,255,0.2);
+ animation:expand-light 3s infinite linear;
+}
+.candle2__fire
+{
+ position:absolute;
+ top:50%;
+ left:40%;
+ display: block;
+ width: 16px;
+ height: 20px;
+ background-color: red;
+ border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
+ background:$fireColor;
+ transform:translate(-50%,-50%);
+ animation: dance-fire 3s infinite linear;
+}
+
+//animations
+
+//animation for blinking eyes
+@keyframes blink-eyes{
+ 0%,35%
+ {
+ opacity:1;
+ transform:translate(-70%,0%);
+ }
+ 36%,39%
+ {
+ opacity:0;
+ transform:translate(-70%,0%);
+ }
+ 40%
+ {
+ opacity:1;
+ transform:translate(-70%,0%);
+ }
+ 50%,65%
+ {
+ transform:translate(-140%,0%);
+ }
+ 66%
+ {
+ transform:translate(-70%,0%);
+ }
+}
+@keyframes expand-body
+{
+0%,40%
+{
+ transform:scale(1,1) translate(60%,-25%);
+}
+45%,55%
+{
+ transform:scale(1.1,1.1) translate(60%,-28%);
+}
+60%
+{
+ transform:scale(0.89,0.89) translate(60%,-25%);
+}
+65%
+{
+ transform:scale(1,1) translate(60%,-25%);
+
+}
+70%
+{
+ transform:scale(0.95,0.95) translate(60%,-25%);
+}
+75%
+{
+ transform:scale(1,1) translate(60%,-25%);
+}
+
+}
+
+@keyframes uff{
+0%,40%{
+ width:0px;
+ height:0px;
+}
+50%,54%
+{
+ width:15px;
+ height:15px;
+ left:30%;
+}
+59%
+{
+ width:5px;
+ height:5px;
+ left:20%;
+}
+62%
+{
+ width:2px;
+ height:2px;
+ left:20%;
+}
+67%
+{
+ width:0px;
+ height:0px;
+ left:30%;
+}
+
+}
+
+@keyframes change-background
+{
+0%,59%,98%,100%{
+ background:$lightsOnBg;
+}
+61%,97%
+{
+ background:$lightsOutBg;
+}
+
+}
+@keyframes move-left{
+0%,59%,100%
+{
+ width:0px;
+ left:40%;
+}
+60%
+{
+ width:30px;
+ left:30%;
+}
+68%
+{
+ width:0px;
+ left:20%;
+}
+}
+
+@keyframes move-top{
+0%,64%,100%
+{
+ width:0px;
+ height:0px;
+ top:0%;
+}
+65%
+{
+ width:10px;
+ height:10px;
+ top:40%;
+ left:40%;
+}
+80%
+{
+ width:0px;
+ height:0px;
+ top:20%;
+}
+}
+@keyframes shake-left{
+0%,40%{
+ left:20%;
+ transform:translate(60%,-15%);
+}
+50%,54%
+{
+ left:20%;
+ transform:translate(60%,-15%);
+}
+59%
+{
+ left:20%;
+ transform:translate(60%,-15%);
+}
+62%
+{
+ left:18%;
+ transform:translate(60%,-15%);
+}
+65%
+{
+ left:21%;
+ transform:translate(60%,-15%);
+}
+67%
+{
+ left:20%;
+ transform:translate(60%,-15%);
+}
+75%
+{
+ left:20%;
+ transform:scale(1.15,0.85) translate(60%,-15%);
+ background:#fff;
+ border-color:$candleShadow;
+
+}
+
+// 80%
+// {
+// background:$angerColor;
+// alpha:0.5;
+// border-color:$angerColor;
+// }
+
+91%
+{
+ left:20%;
+ transform:scale(1.18,0.82) translate(60%,-10%);
+ background:$angerColor;
+ border-color:$angerColor;
+ box-shadow: -2px 0px 0px $angerColor inset;
+}
+92%
+{
+ left:20%;
+ transform:scale(0.85,1.15) translate(60%,-15%);
+}
+95%
+{
+ left:20%;
+ transform:scale(1.05,0.95) translate(60%,-15%);
+}
+97%
+{
+ left:20%;
+ transform:scale(1.0,1.0) translate(60%,-15%);
+}
+
+}
+@keyframes stick-animation{
+ 0%,40%{
+ left:50%;
+ top:0%;
+ transform:translate(-50%,-100%);
+}
+50%,54%
+{
+ left:50%;
+ top:0%;
+ transform:translate(-50%,-100%);
+}
+59%
+{
+ left:50%;
+ top:0%;
+ transform:translate(-50%,-100%);
+}
+62%
+{
+ left:50%;
+ top:0%;
+ transform:rotateZ(-15deg) translate(-50%,-100%);
+}
+65%
+{
+ left:50%;
+ top:0%;
+ transform:rotateZ(15deg) translate(-50%,-100%);
+}
+70%
+{
+ left:50%;
+ top:0%;
+ transform:rotateZ(-5deg) translate(-50%,-100%);
+}
+72%
+{
+ left:50%;
+ top:0%;
+ transform:rotateZ(5deg) translate(-50%,-100%);
+}
+74%,84%
+{
+ left:50%;
+ top:0%;
+ transform:rotateZ(0deg) translate(-50%,-100%);
+}
+85%
+{
+ transform:rotateZ(180deg) translate(0%,120%);
+}
+92%
+{
+ left:50%;
+ top:0%;
+ transform:translate(-50%,-100%);
+}
+
+}
+@keyframes expand-light
+{
+10%,29%,59%,89%
+{
+ transform:translate(-25%,-50%) scale(0,0);
+ border:2px solid rgba(255,255,255,0);
+}
+90%,20%,50%
+{
+ transform:translate(-25%,-50%) scale(1,1);
+}
+95%,96%,26%,27%,56%,57%
+{
+ transform:translate(-25%,-50%) scale(2.0,2.0);
+ border:2px solid rgba(255,255,255,0.5);
+}
+0%,28%,58%,100%
+{
+ transform:translate(-25%,-50%) scale(2.5,2.5);
+ border:2px solid rgba(255,255,255,0.2);
+}
+
+}
+@keyframes dance-fire
+{
+59%,89%
+{
+ left:40%;
+ width:0px;
+ height:0px;
+}
+90%,0%,7%,15%,23%,31%,39%,47%,55%
+{
+ left:40.8%;
+ width:16px;
+ height:20px;
+ background:$fireColor2;
+}
+94%,3%,11%,19%,27%,35%,43%,51%,58%
+{
+ left:41.2%;
+ width:16px;
+ height:20px;
+ background:$fireColor;
+}
+}
+@keyframes changeto-lower
+{
+0%,70%,90%
+{
+ padding:0px;
+ display:inline-block;
+ border-radius:100%;
+ background:$candleShadow;
+ border-width:0 0 0 0;
+ border:0px solid $candleShadow;
+ transform:translate(-90%,0%);
+}
+71%,89%
+{
+ background:none;
+ border: solid $candleShadow;
+ border-radius:0px;
+ border-width: 0 2px 2px 0;
+ display:inline-block;
+ padding: 1px;
+ float:left;
+ transform-origin:bottom left;
+ transform: rotate(-45deg) translate(-50%,-65%);
+ -webkit-transform: rotate(-45deg) translate(-50%,-65%);
+}
+}
+
+@keyframes changeto-greater
+{
+0%,70%,90%
+{
+top:50%;
+ padding:0px;
+ display:inline-block;
+ border-radius:100%;
+ background:$candleShadow;
+ border-width:0 0 0 0;
+ border:0px solid $candleShadow;
+ transform:translate(-80%,0%);
+}
+71%,89%
+{
+ top:30%;
+ background:none;
+ border: solid $candleShadow;
+ border-radius:0px;
+ border-width: 0 2px 2px 0;
+ display:inline-block;
+ padding: 1px;
+ float:left;
+ transform-origin:bottom left;
+ transform: rotate(135deg) translate(-80%,20%);
+ -webkit-transform: rotate(135deg) translate(-80%,20%);
+}
+}
+
+#marker-icon {
+ padding-top: 25%;
+ height: auto !important;
+ width: 20vh !important;
+ margin: auto !important;
+ display: block !important;
+
+}
+
+#middle-button {
+ top: 10%;
+ margin: auto !important;
+ display: block !important;
+}
+
+.big-spinner {
+ stroke: #FFFFFF;
+ fill: #FFFFFF;
+}
+
+.loading-subtitle {
+ color: var(--ion-color-primary-contrast);
+}
+
+
diff --git a/src/app/fallback/loading/loading.service.ts b/src/app/fallback/loading/loading.service.ts
new file mode 100644
index 00000000..84a037bf
--- /dev/null
+++ b/src/app/fallback/loading/loading.service.ts
@@ -0,0 +1,34 @@
+import { Injectable } from "@angular/core";
+import { Observable, ReplaySubject } from "rxjs";
+import { map } from "rxjs/operators";
+
+@Injectable({
+ providedIn: "root"
+})
+export class LoadingService {
+ private readonly MAX: number = 1;
+ private readonly MIN: number = 0;
+ private readonly _progress: ReplaySubject
= new ReplaySubject();
+ readonly progress: Observable = this._progress.asObservable();
+
+ constructor() {
+ this.progress = this._progress
+ .asObservable()
+ .pipe(
+ map((it) => Math.max(Math.min(this.MIN, it), this.MAX))
+ );
+ this.start();
+ }
+
+ start(): void {
+ this._progress.next(this.MIN);
+ }
+
+ set(step: number): void {
+ this._progress.next(step);
+ }
+
+ finish(): void {
+ this._progress.next(this.MAX);
+ }
+}
diff --git a/src/app/fallback/location/location-fallback.component.ts b/src/app/fallback/location/location-fallback.component.ts
index d6092168..90e54fc1 100644
--- a/src/app/fallback/location/location-fallback.component.ts
+++ b/src/app/fallback/location/location-fallback.component.ts
@@ -3,7 +3,8 @@ import {Diagnostic} from "@ionic-native/diagnostic/ngx";
import {ModalController} from "@ionic/angular";
@Component({
- templateUrl: "location-fallbackscreen.html"
+ templateUrl: "location-fallbackscreen.html",
+ styleUrls: ["location-fallbackscreen.scss"]
})
export class LocationFallbackScreen {
diff --git a/src/app/fallback/location/location-fallbackscreen.scss b/src/app/fallback/location/location-fallbackscreen.scss
new file mode 100644
index 00000000..1b722bf1
--- /dev/null
+++ b/src/app/fallback/location/location-fallbackscreen.scss
@@ -0,0 +1,18 @@
+
+#marker-icon {
+ padding-top: 25%;
+ height: auto !important;
+ width: 20vh !important;
+ margin: auto !important;
+ display: block !important;
+
+}
+
+#middle-button {
+ top: 10%;
+ // height: auto !important;
+ // width: 20vh !important;
+ margin: auto !important;
+ display: block !important;
+
+}
diff --git a/src/app/fallback/open-browser/leave-app.dialog.html b/src/app/fallback/open-browser/leave-app.dialog.html
index 00a32f89..6f8a0c53 100644
--- a/src/app/fallback/open-browser/leave-app.dialog.html
+++ b/src/app/fallback/open-browser/leave-app.dialog.html
@@ -1,28 +1,23 @@
-
-
-
-
-
-
-
-
+
-
+
-
+
-
-
-
- {{'fallback.browser' | translate:{value: null} }}
-
-
-
- {{'fallback.browser_ok' | translate:{value: null} }}
-
-
- {{'fallback.browser_no' | translate:{value: null} }}
-
+
+
+
+ {{'fallback.browser' | translate:{app_name: (this.appName | async)} }}
+
+
+
+ {{'fallback.browser_ok' | translate }}
+
+
+ {{'fallback.browser_no' | translate }}
+
diff --git a/src/app/fallback/open-browser/leave-app.dialog.ts b/src/app/fallback/open-browser/leave-app.dialog.ts
index 691f6353..01e42c43 100644
--- a/src/app/fallback/open-browser/leave-app.dialog.ts
+++ b/src/app/fallback/open-browser/leave-app.dialog.ts
@@ -1,5 +1,6 @@
/** angular */
import {Component} from "@angular/core";
+import { AppVersion } from "@ionic-native/app-version/ngx";
import {ModalController, NavParams} from "@ionic/angular";
/** ionic-native */
import {InAppBrowserOptions} from "@ionic-native/in-app-browser";
@@ -9,47 +10,47 @@ import {Logging} from "../../services/logging/logging.service";
/** misc */
import {TranslateService} from "@ngx-translate/core";
import {CssStyleService} from "../../services/theme/css-style.service";
+import { ViewController } from "@ionic/core";
@Component({
- templateUrl: "leave-app.dialog.html"
+ templateUrl: "leave-app.dialog.html",
+ styleUrls: ["leave-app.scss"]
})
export class LeaveAppDialog {
- private readonly log: Logger = Logging.getLogger(LeaveAppDialog.name);
+ private readonly log: Logger = Logging.getLogger("LeaveAppDialog");
private readonly params: LeaveAppDialogNavParams;
+ readonly appName: Promise;
themeIonicContrastColor: string;
constructor(
private readonly nav: NavParams,
private readonly modalCtrl: ModalController,
- private readonly translate: TranslateService
+ private readonly appVersion: AppVersion,
+ private readonly cssStyle: CssStyleService,
) {
this.params = nav.data;
+ this.appName = this.appVersion.getAppName();
}
ionViewWillEnter(): void {
this.themeIonicContrastColor = "light";
- if(CssStyleService.customIsSet) {
- this.themeIonicContrastColor = CssStyleService.customColorContrast ? "light" : "dark";
+ if(this.cssStyle.customIsSet) {
+ this.themeIonicContrastColor = this.cssStyle.customColorContrast ? "light" : "dark";
}
}
- dismiss(): void {
+ async dismiss(): Promise {
this.log.trace(() => "User action -> dismiss");
- this.modalCtrl.dismiss();
+ await this.modalCtrl.dismiss({}, "cancel");
}
- leaveApp(): void {
+ async leaveApp(): Promise {
this.log.trace(() => "User action -> leave app");
- const options: InAppBrowserOptions = {
- location: "yes",
- clearcache: "yes",
- clearsessioncache: "yes"
- };
+ // The leave app function is responsible for closing the modal, because it somehow does not work in here
this.params.leaveApp();
- this.modalCtrl.dismiss();
}
}
diff --git a/src/app/fallback/open-browser/leave-app.scss b/src/app/fallback/open-browser/leave-app.scss
new file mode 100644
index 00000000..0b30e4ec
--- /dev/null
+++ b/src/app/fallback/open-browser/leave-app.scss
@@ -0,0 +1,27 @@
+
+#webr-icon {
+ height: auto !important;
+ width: 60% !important;
+
+ @media screen and (orientation:landscape) {
+ height: 40% !important;
+ width: auto !important;
+ }
+
+ margin: auto !important;
+ display: block !important;
+}
+
+.return-to-app-row {
+ @media screen and (orientation:landscape) {
+ padding-top: 0;
+ }
+}
+
+h1 {
+ color: var(--ion-color-primary-contrast);
+}
+
+a {
+ color: var(--ion-color-primary-contrast);
+}
diff --git a/src/app/fallback/open-browser/leave-app.service.ts b/src/app/fallback/open-browser/leave-app.service.ts
new file mode 100644
index 00000000..522a90c1
--- /dev/null
+++ b/src/app/fallback/open-browser/leave-app.service.ts
@@ -0,0 +1,27 @@
+import { Injectable } from "@angular/core";
+import { ModalController } from "@ionic/angular";
+import { LeaveAppDialog, LeaveAppDialogNavParams } from "./leave-app.dialog";
+
+@Injectable({
+ providedIn: "root"
+})
+export class LeaveAppDialogService {
+
+
+ constructor(
+ private readonly modalCtrl: ModalController,
+ ) {
+ }
+
+ async present(): Promise {
+ const modal: HTMLIonModalElement = await this.modalCtrl.create({
+ component: LeaveAppDialog,
+ componentProps: {
+ leaveApp: (): Promise => modal.dismiss()
+ },
+ cssClass: "modal-fullscreen"
+ });
+
+ await modal.present();
+ }
+}
diff --git a/src/app/learningmodule/actions/open-learning-module-action.ts b/src/app/learningmodule/actions/open-html-learning-module-action.ts
similarity index 58%
rename from src/app/learningmodule/actions/open-learning-module-action.ts
rename to src/app/learningmodule/actions/open-html-learning-module-action.ts
index 9d4e9e90..a8f46d63 100644
--- a/src/app/learningmodule/actions/open-learning-module-action.ts
+++ b/src/app/learningmodule/actions/open-html-learning-module-action.ts
@@ -2,26 +2,30 @@ import {ILIASObjectAction, ILIASObjectActionAlert, ILIASObjectActionNoMessage, I
import {ModalController, NavController} from "@ionic/angular";
import {InjectionToken} from "@angular/core";
import {LoadingPage, LoadingPageType} from "../../fallback/loading/loading.component";
-import {LearningModuleLoader} from "../services/learning-module-loader";
import {InAppBrowser, InAppBrowserOptions} from "@ionic-native/in-app-browser/ngx";
+import { LeaveAppDialogService } from "../../fallback/open-browser/leave-app.service";
import {User} from "../../models/user";
import {AuthenticationProvider} from "../../providers/authentication.provider";
-import {LearningModule} from "../../models/learning-module";
-import {ILIASObject} from "../../models/ilias-object";
+import { Logger } from "../../services/logging/logging.api";
+import { Logging } from "../../services/logging/logging.service";
+import {LearningModule} from "../models/learning-module";
import {LearningModulePathBuilder} from "../services/learning-module-path-builder";
import {TranslateService} from "@ngx-translate/core";
+import {LearningModuleManager} from "../services/learning-module-manager";
-export class OpenLearningModuleAction extends ILIASObjectAction {
+export class OpenHtmlLearningModuleAction extends ILIASObjectAction {
+
+ private readonly log: Logger = Logging.getLogger("OpenHtmlLearningModuleAction");
constructor(
- private readonly loader: LearningModuleLoader,
private readonly nav: NavController,
private readonly learningModuleObjectId: number,
- private readonly learningModuleName: string,
private readonly modal: ModalController,
private readonly browser: InAppBrowser,
- private readonly pathBuilder: LearningModulePathBuilder,
private readonly translate: TranslateService,
+ private readonly pathBuilder: LearningModulePathBuilder,
+ private readonly learningModuleManager: LearningModuleManager,
+ private readonly leaveAppDialogService: LeaveAppDialogService,
) {super()}
async execute(): Promise {
@@ -34,31 +38,21 @@ export class OpenLearningModuleAction extends ILIASObjectAction {
await loadingPage.present();
try {
const user: User = AuthenticationProvider.getUser();
- const obj: ILIASObject = await ILIASObject.findByObjIdAndUserId(this.learningModuleObjectId, user.id);
- const alreadyLoaded: boolean = await obj.objectIsUnderFavorite();
- if(!alreadyLoaded) await this.loader.load(this.learningModuleObjectId);
+ await this.learningModuleManager.checkAndDownload(this.learningModuleObjectId, user.id);
this.openHTMLModule();
await loadingPage.dismiss();
return new ILIASObjectActionNoMessage();
} catch (error) {
await loadingPage.dismiss();
- throw error;
+ await this.leaveAppDialogService.present();
}
}
async openHTMLModule(): Promise {
- console.log("opening learning module");
+ this.log.info(() => "Opening HTLM learning module");
const user: User = AuthenticationProvider.getUser();
const lm: LearningModule = await LearningModule.findByObjIdAndUserId(this.learningModuleObjectId, user.id);
- const url: string = await lm.getLocalStartFileUrl(this.pathBuilder);
- const browserOptions: InAppBrowserOptions = {
- location: "no",
- clearsessioncache: "yes",
- clearcache: "yes",
- footer:"yes",
- closebuttoncaption: this.translate.instant("close")
- };
- this.browser.create(url, "_blank", browserOptions);
+ await this.nav.navigateForward(["/learningmodule", "htlm", lm.objId]);
}
alert(): ILIASObjectActionAlert | undefined {
@@ -66,16 +60,13 @@ export class OpenLearningModuleAction extends ILIASObjectAction {
}
}
-export interface OpenLearningModuleActionFunction {
+export interface OpenHtmlLearningModuleActionFunction {
(
nav: NavController,
learningModuleObjectId: number,
- learningModuleName: string,
- modalController: ModalController,
- browser: InAppBrowser,
pathBuilder: LearningModulePathBuilder,
- translate: TranslateService,
- ): OpenLearningModuleAction
+ translate: TranslateService
+ ): OpenHtmlLearningModuleAction
}
-export const OPEN_LEARNING_MODULE_ACTION_FACTORY: InjectionToken = new InjectionToken("token for open learning module action factory");
+export const OPEN_HTML_LEARNING_MODULE_ACTION_FACTORY: InjectionToken = new InjectionToken("token for opening html learning module action factory");
diff --git a/src/app/learningmodule/actions/open-scorm-learning-module-action.ts b/src/app/learningmodule/actions/open-scorm-learning-module-action.ts
new file mode 100644
index 00000000..9c624610
--- /dev/null
+++ b/src/app/learningmodule/actions/open-scorm-learning-module-action.ts
@@ -0,0 +1,60 @@
+import { InjectionToken } from "@angular/core";
+import { ModalController, NavController } from "@ionic/angular";
+import { ILIASObjectAction, ILIASObjectActionAlert, ILIASObjectActionNoMessage, ILIASObjectActionResult } from "../../actions/object-action";
+import { LoadingPage, LoadingPageType } from "../../fallback/loading/loading.component";
+import { LeaveAppDialogService } from "../../fallback/open-browser/leave-app.service";
+import { User } from "../../models/user";
+import { AuthenticationProvider } from "../../providers/authentication.provider";
+import { Logger } from "../../services/logging/logging.api";
+import { Logging } from "../../services/logging/logging.service";
+import { LearningModuleManager } from "../services/learning-module-manager";
+
+export class OpenScormLearningModuleAction extends ILIASObjectAction {
+
+ private readonly log: Logger = Logging.getLogger("OpenScormLearningModuleAction");
+
+ constructor(
+ private readonly learningModuleObjectId: number,
+ private readonly modal: ModalController,
+ private readonly navCtrl: NavController,
+ private readonly learningModuleManager: LearningModuleManager,
+ private readonly leaveAppDialogService: LeaveAppDialogService
+ ) {super()}
+
+ async execute(): Promise {
+ const loadingPage: HTMLIonModalElement = await this.modal.create({
+ component: LoadingPage,
+ cssClass: "modal-fullscreen"
+ });
+ LoadingPage.type = LoadingPageType.learningmodule;
+ await loadingPage.present();
+ try {
+ const user: User = AuthenticationProvider.getUser();
+ await this.learningModuleManager.checkAndDownload(this.learningModuleObjectId, user.id);
+ this.openSCORMModule();
+ await loadingPage.dismiss();
+ return new ILIASObjectActionNoMessage();
+ } catch (error) {
+ await loadingPage.dismiss();
+ await this.leaveAppDialogService.present();
+ }
+ }
+
+ async openSCORMModule(): Promise {
+ this.log.info(() => "Opening SCORM learning module");
+ await this.navCtrl.navigateForward(["/learningmodule", "sahs", this.learningModuleObjectId]);
+ }
+
+ alert(): ILIASObjectActionAlert | undefined {
+ return undefined;
+ }
+}
+
+export interface OpenScormLearningModuleActionFunction {
+ (
+ learningModuleObjectId: number,
+ navCtrl: NavController,
+ ): OpenScormLearningModuleAction
+}
+
+export const OPEN_SCORM_LEARNING_MODULE_ACTION_FACTORY: InjectionToken = new InjectionToken("token for open learning module action factory");
diff --git a/src/app/learningmodule/learning-module.module.ts b/src/app/learningmodule/learning-module.module.ts
new file mode 100644
index 00000000..d007a125
--- /dev/null
+++ b/src/app/learningmodule/learning-module.module.ts
@@ -0,0 +1,21 @@
+import {CommonModule} from "@angular/common";
+import {NgModule} from "@angular/core";
+import {FormsModule, ReactiveFormsModule} from "@angular/forms";
+import {RouterModule, Routes} from "@angular/router";
+import {IonicModule} from "@ionic/angular";
+
+const routes: Routes = [
+ {path: "sahs", loadChildren: "./pages/scorm/scorm.module#ScormPageModule"},
+ {path: "htlm", loadChildren: "./pages/htlm/htlm.module#HtlmPageModule"},
+];
+
+@NgModule({
+ imports: [
+ CommonModule,
+ FormsModule,
+ ReactiveFormsModule,
+ IonicModule,
+ RouterModule.forChild(routes),
+ ]
+})
+export class LearningModuleModule {}
diff --git a/src/app/models/learning-module.ts b/src/app/learningmodule/models/learning-module.ts
similarity index 85%
rename from src/app/models/learning-module.ts
rename to src/app/learningmodule/models/learning-module.ts
index ab0ef752..b2333898 100644
--- a/src/app/models/learning-module.ts
+++ b/src/app/learningmodule/models/learning-module.ts
@@ -1,8 +1,8 @@
-import {ActiveRecord, SQLiteConnector} from "./active-record";
-import {SQLiteDatabaseService} from "../services/database.service";
-import {LearningModulePathBuilder} from "../learningmodule/services/learning-module-path-builder";
+import {ActiveRecord, SQLiteConnector} from "../../models/active-record";
+import {SQLiteDatabaseService} from "../../services/database.service";
+import {LearningModulePathBuilder} from "../services/learning-module-path-builder";
-export class LearningModule extends ActiveRecord {
+export class LearningModule extends ActiveRecord {
/**
* objId of the ILIAS object corresponding to the lm
*/
@@ -39,7 +39,7 @@ export class LearningModule extends ActiveRecord {
const db: SQLiteDatabaseService = await SQLiteDatabaseService.instance();
const response: any = await db.query("SELECT * FROM learning_modules WHERE objId = ? AND userId = ?", [objId, userId]);
const lm: LearningModule = new LearningModule();
- if (response.rows.length == 0) {
+ if (response.rows.length === 0) {
lm.objId = objId;
lm.userId = userId;
return lm;
diff --git a/src/app/learningmodule/pages/htlm/htlm.html b/src/app/learningmodule/pages/htlm/htlm.html
new file mode 100644
index 00000000..56624278
--- /dev/null
+++ b/src/app/learningmodule/pages/htlm/htlm.html
@@ -0,0 +1,16 @@
+
+
+
+
+ {{title}}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/learningmodule/pages/htlm/htlm.module.ts b/src/app/learningmodule/pages/htlm/htlm.module.ts
new file mode 100644
index 00000000..2568ced1
--- /dev/null
+++ b/src/app/learningmodule/pages/htlm/htlm.module.ts
@@ -0,0 +1,27 @@
+/** angular */
+import {IonicModule} from "@ionic/angular";
+import {NgModule} from "@angular/core";
+import {CommonModule} from "@angular/common";
+import {FormsModule} from "@angular/forms";
+import {Routes, RouterModule} from "@angular/router";
+/** misc */
+import {HtlmPage} from "./htlm";
+import { TranslateModule } from "@ngx-translate/core";
+
+
+const routes: Routes = [
+ {path: ":id", component: HtlmPage, pathMatch: "full"}
+];
+
+@NgModule({
+ imports: [
+ CommonModule,
+ FormsModule,
+ IonicModule,
+ TranslateModule,
+ RouterModule.forChild(routes)
+ ],
+ declarations: [HtlmPage]
+})
+
+export class HtlmPageModule {}
diff --git a/src/app/learningmodule/pages/htlm/htlm.ts b/src/app/learningmodule/pages/htlm/htlm.ts
new file mode 100644
index 00000000..9c2538c9
--- /dev/null
+++ b/src/app/learningmodule/pages/htlm/htlm.ts
@@ -0,0 +1,52 @@
+import { Component, ElementRef, Inject, Injectable, ViewChild } from "@angular/core";
+import { DomSanitizer, SafeResourceUrl, SafeUrl } from "@angular/platform-browser";
+import {ActivatedRoute, ParamMap} from "@angular/router";
+import { WebView } from "@ionic-native/ionic-webview/ngx";
+import { Observable, Subject } from "rxjs";
+import { Logger } from "../../../services/logging/logging.api";
+import { Logging } from "../../../services/logging/logging.service";
+import {LEARNING_MODULE_PATH_BUILDER, LearningModulePathBuilder} from "../../services/learning-module-path-builder";
+import {AuthenticationProvider} from "../../../providers/authentication.provider";
+import {LearningModule} from "../../models/learning-module";
+import {User} from "../../../models/user";
+import {ILIASObject} from "../../../models/ilias-object";
+
+@Component({
+ selector: "page-htlm",
+ templateUrl: "htlm.html"
+})
+export class HtlmPage {
+
+ private readonly safeEntryPoint: Subject = new Subject();
+ title: string = "";
+ entryPoint: Observable = this.safeEntryPoint.asObservable();
+
+ private readonly log: Logger = Logging.getLogger("ScormPage");
+
+ constructor(
+ private readonly route: ActivatedRoute,
+ @Inject(LEARNING_MODULE_PATH_BUILDER) private readonly pathBuilder: LearningModulePathBuilder,
+ private readonly sanitizer: DomSanitizer,
+ private readonly webView: WebView
+ ) {}
+
+ async ionViewDidEnter(): Promise {
+ // get data for the lm
+ const params: ParamMap = this.route.snapshot.paramMap;
+ const lmId: number = parseInt(params.get("id"), 10);
+ const user: User = AuthenticationProvider.getUser();
+ const lm: LearningModule = await LearningModule.findByObjIdAndUserId(lmId, user.id);
+ const obj: ILIASObject = await ILIASObject.findByObjIdAndUserId(lm.objId, user.id);
+ this.title = obj.title;
+
+ // get manifest
+ const url: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(
+ this.webView.convertFileSrc(
+ await lm.getLocalStartFileUrl(this.pathBuilder)
+ )
+ );
+ this.safeEntryPoint.next(url);
+ this.safeEntryPoint.complete();
+ }
+
+}
diff --git a/src/app/learningmodule/pages/scorm/scorm.css b/src/app/learningmodule/pages/scorm/scorm.css
new file mode 100644
index 00000000..419152c7
--- /dev/null
+++ b/src/app/learningmodule/pages/scorm/scorm.css
@@ -0,0 +1,18 @@
+* { margin: 0px; }
+html, body, iframe { height:100%; }
+html, body { overflow-y:hidden; font-family:Helvetica, "Helvetica Neue", Arial; font-size:12px; }
+iframe { display:block; width:100%; }
+
+html, body, iframe , a, img, table, tbody, tr, td, table td, table th {
+ border : 0px none;
+ padding: 0px;
+}
+
+a:link { color: #0000FF; }
+a:visited { color: #0000FF; }
+a:hover { color: #000080; }
+a:active { color: #0000FF; }
+
+#btnExit {margin-left:5px;}
+#btnAbandon {margin-left:5px;}
+#btnSuspendAll {margin-left:5px;}
diff --git a/src/app/learningmodule/pages/scorm/scorm.html b/src/app/learningmodule/pages/scorm/scorm.html
new file mode 100644
index 00000000..37a7d945
--- /dev/null
+++ b/src/app/learningmodule/pages/scorm/scorm.html
@@ -0,0 +1,32 @@
+
+
+
+
+ {{title}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+ |
+
+
+ |
+
+
+
diff --git a/src/app/learningmodule/pages/scorm/scorm.module.ts b/src/app/learningmodule/pages/scorm/scorm.module.ts
new file mode 100644
index 00000000..c1926399
--- /dev/null
+++ b/src/app/learningmodule/pages/scorm/scorm.module.ts
@@ -0,0 +1,27 @@
+/** angular */
+import {IonicModule} from "@ionic/angular";
+import {NgModule} from "@angular/core";
+import {CommonModule} from "@angular/common";
+import {FormsModule} from "@angular/forms";
+import {Routes, RouterModule} from "@angular/router";
+/** misc */
+import {ScormPage} from "./scorm";
+import { TranslateModule } from "@ngx-translate/core";
+
+
+const routes: Routes = [
+ {path: ":id", component: ScormPage, pathMatch: "full"}
+];
+
+@NgModule({
+ imports: [
+ CommonModule,
+ FormsModule,
+ IonicModule,
+ TranslateModule,
+ RouterModule.forChild(routes)
+ ],
+ declarations: [ScormPage]
+})
+
+export class ScormPageModule {}
diff --git a/src/app/learningmodule/pages/scorm/scorm.ts b/src/app/learningmodule/pages/scorm/scorm.ts
new file mode 100644
index 00000000..f55f0c6b
--- /dev/null
+++ b/src/app/learningmodule/pages/scorm/scorm.ts
@@ -0,0 +1,45 @@
+import { Component, ElementRef, Inject, Injectable, ViewChild } from "@angular/core";
+import {ActivatedRoute, ParamMap} from "@angular/router";
+import { Logger } from "../../../services/logging/logging.api";
+import { Logging } from "../../../services/logging/logging.service";
+import {LEARNING_MODULE_PATH_BUILDER, LearningModulePathBuilder} from "../../services/learning-module-path-builder";
+import {AuthenticationProvider} from "../../../providers/authentication.provider";
+import {LearningModule} from "../../models/learning-module";
+import {User} from "../../../models/user";
+import {ILIASObject} from "../../../models/ilias-object";
+
+@Component({
+ selector: "page-scorm",
+ templateUrl: "scorm.html",
+ styleUrls: ["./scorm.css"]
+})
+export class ScormPage {
+
+ title: string = "";
+
+ private readonly log: Logger = Logging.getLogger("ScormPage");
+
+ constructor(
+ private readonly route: ActivatedRoute,
+ @Inject(LEARNING_MODULE_PATH_BUILDER) private readonly pathBuilder: LearningModulePathBuilder,
+ ) {}
+
+ async ionViewDidEnter(): Promise {
+ // get data for the lm
+ const params: ParamMap = this.route.snapshot.paramMap;
+ const lmId: number = parseInt(params.get("id"), 10);
+ const user: User = AuthenticationProvider.getUser();
+ const lm: LearningModule = await LearningModule.findByObjIdAndUserId(lmId, user.id);
+ const obj: ILIASObject = await ILIASObject.findByObjIdAndUserId(lm.objId, user.id);
+ this.title = obj.title;
+
+ // get manifest
+ let manifest: string = await lm.getLocalStartFileUrl(this.pathBuilder);
+ manifest = manifest.replace("file://", "_app_file_");
+ this.log.info(() => `got manifest file at ${manifest}`);
+
+ //@ts-ignore
+ window.Run.ManifestByURL(manifest, true);
+ }
+
+}
diff --git a/src/app/learningmodule/services/learning-module-loader.ts b/src/app/learningmodule/services/learning-module-loader.ts
index 8985b2e8..36359589 100644
--- a/src/app/learningmodule/services/learning-module-loader.ts
+++ b/src/app/learningmodule/services/learning-module-loader.ts
@@ -1,16 +1,18 @@
-import {Inject, Injectable, InjectionToken} from "@angular/core";
-import {ILIAS_REST, ILIASRequestOptions, ILIASRest} from "../../providers/ilias/ilias.rest";
-import {HttpResponse} from "../../providers/http";
-import {AuthenticationProvider} from "../../providers/authentication.provider";
-import {ILIASObject} from "../../models/ilias-object";
-import {User} from "../../models/user";
-import {UserStorageService} from "../../services/filesystem/user-storage.service";
-import {LINK_BUILDER, LinkBuilder} from "../../services/link/link-builder.service";
-import {DownloadRequestOptions, FILE_DOWNLOADER, FileDownloader} from "../../providers/file-transfer/file-download";
-import {Zip} from "@ionic-native/zip/ngx";
-import {LearningModule} from "../../models/learning-module";
-import {LEARNING_MODULE_PATH_BUILDER, LearningModulePathBuilder} from "./learning-module-path-builder";
-import {LoadingPage} from "../../fallback/loading/loading.component";
+import { Inject, Injectable, InjectionToken } from "@angular/core";
+import { Zip } from "@ionic-native/zip/ngx";
+import { LoadingService } from "../../fallback/loading/loading.service";
+import { ILIASObject } from "../../models/ilias-object";
+import { User } from "../../models/user";
+import { AuthenticationProvider } from "../../providers/authentication.provider";
+import { DownloadRequestOptions, FILE_DOWNLOADER, FileDownloader } from "../../providers/file-transfer/file-download";
+import { HttpResponse } from "../../providers/http";
+import { ILIAS_REST, ILIASRequestOptions, ILIASRest } from "../../providers/ilias/ilias.rest";
+import { FileStorageService } from "../../services/filesystem/file-storage.service";
+import { UserStorageMamager } from "../../services/filesystem/user-storage.mamager";
+import { LINK_BUILDER, LinkBuilder } from "../../services/link/link-builder.service";
+import { LearningModule } from "../models/learning-module";
+import { LEARNING_MODULE_PATH_BUILDER, LearningModulePathBuilder } from "./learning-module-path-builder";
+import { LearningModuleStorageUtilisation } from "./learning-module-storage-utilisation";
export interface LearningModuleLoader {
/**
@@ -28,8 +30,11 @@ export const LEARNING_MODULE_LOADER: InjectionToken = new
})
export class RestLearningModuleLoader implements LearningModuleLoader {
constructor(
- protected readonly zip: Zip,
- protected readonly userStorage: UserStorageService,
+ private readonly storageUtilisation: LearningModuleStorageUtilisation,
+ private readonly zip: Zip,
+ private readonly fileStorage: FileStorageService,
+ private readonly userStorageManager: UserStorageMamager,
+ private readonly loadingService: LoadingService,
@Inject(ILIAS_REST) private readonly iliasRest: ILIASRest,
@Inject(FILE_DOWNLOADER) private readonly downloader: FileDownloader,
@Inject(LINK_BUILDER) private readonly linkBuilder: LinkBuilder,
@@ -37,16 +42,16 @@ export class RestLearningModuleLoader implements LearningModuleLoader {
) {}
async load(objId: number): Promise {
- LoadingPage.progress.next(0);
+ this.loadingService.start();
// get data for the learning module
const user: User = AuthenticationProvider.getUser();
const obj: ILIASObject = await ILIASObject.findByObjIdAndUserId(objId, user.id);
const request: LearningModuleData = await this.getLearningModuleData(obj.refId);
- LoadingPage.progress.next(.2);
+ this.loadingService.set(.2);
const lm: LearningModule = await LearningModule.findByObjIdAndUserId(objId, user.id);
// path to the tmp directory for downloading
- const localTmpZipDir: string = await this.pathBuilder.inLocalLmDir("tmp", true);
+ const localTmpZipDir: string = await this.pathBuilder.dirInLocalLmDir("tmp", true);
// name of the zip file containing the learning module
const tmpZipFile: string = `tmp_${objId}.zip`;
// url to get the zip file containing the learning module
@@ -55,7 +60,10 @@ export class RestLearningModuleLoader implements LearningModuleLoader {
// return if the module did not change and is already loaded
const lmLoaded: boolean = await LearningModule.existsByObjIdAndUserId(objId, user.id);
const lmUpToDate: boolean = lm.timestamp >= request.timestamp;
- if(lmLoaded && lmUpToDate) return;
+ if(lmLoaded && lmUpToDate) {
+ this.loadingService.finish();
+ return;
+ }
// download the zip file
const downloadOptions: DownloadRequestOptions = {
@@ -68,15 +76,19 @@ export class RestLearningModuleLoader implements LearningModuleLoader {
};
// user-dependant path to all learning modules
- const localAllLmsDir: string = await this.pathBuilder.inLocalLmDir("", true);
+ const localAllLmsDir: string = await this.pathBuilder.dirInLocalLmDir("", true);
// extract the zip file, place the lm in a specific directory, then delete the zip file
await this.downloader.download(downloadOptions);
- LoadingPage.progress.next(.6);
+ this.loadingService.set(.6);
+ console.log(`UNZIPPING in ${localTmpZipDir} file ${tmpZipFile} => dir ${request.zipDirName}`);
await this.zip.unzip(`${localTmpZipDir}${tmpZipFile}`, localTmpZipDir);
- LoadingPage.progress.next(.9);
- await this.userStorage.moveAndReplaceDir(localTmpZipDir, request.zipDirName, localAllLmsDir, this.pathBuilder.lmDirName(objId));
- await this.userStorage.removeFileIfExists(localTmpZipDir, tmpZipFile);
- LoadingPage.progress.next(1);
+ this.loadingService.set(.9);
+
+ // Remove object because the app would report wrong storage numbers after the update got removed by the user.
+ await this.userStorageManager.removeObjectFromUserStorage(user.id, objId, this.storageUtilisation);
+ await this.fileStorage.moveAndReplaceDir(localTmpZipDir, request.zipDirName, localAllLmsDir, this.pathBuilder.lmDirName(objId));
+ await this.fileStorage.removeFileIfExists(localTmpZipDir, tmpZipFile);
+ this.loadingService.finish();
// save the lm in the local database
lm.relativeStartFile = request.startFile;
diff --git a/src/app/learningmodule/services/learning-module-manager.ts b/src/app/learningmodule/services/learning-module-manager.ts
index 3ba26497..a86bc8ed 100644
--- a/src/app/learningmodule/services/learning-module-manager.ts
+++ b/src/app/learningmodule/services/learning-module-manager.ts
@@ -4,26 +4,40 @@
* @author mschneiter
* @version 1.0.0
*/
-import {Inject, Injectable, InjectionToken} from "@angular/core";
-import {UserStorageService} from "../../services/filesystem/user-storage.service";
-import {LEARNING_MODULE_PATH_BUILDER, LearningModulePathBuilder} from "./learning-module-path-builder";
-import {LearningModule} from "../../models/learning-module";
-import {User} from "../../models/user";
-import {AuthenticationProvider} from "../../providers/authentication.provider";
-import {File, DirectoryEntry} from "@ionic-native/file/ngx";
+import { Inject, Injectable, InjectionToken } from "@angular/core";
+import { ILIASObject } from "../../models/ilias-object";
+import { User } from "../../models/user";
+import { AuthenticationProvider } from "../../providers/authentication.provider";
+import { FileStorageService } from "../../services/filesystem/file-storage.service";
+import { UserStorageMamager } from "../../services/filesystem/user-storage.mamager";
+import { Logger } from "../../services/logging/logging.api";
+import { Logging } from "../../services/logging/logging.service";
+import { LearningModule } from "../models/learning-module";
+import { LEARNING_MODULE_LOADER, LearningModuleLoader } from "./learning-module-loader";
+import { LEARNING_MODULE_PATH_BUILDER, LearningModulePathBuilder } from "./learning-module-path-builder";
+import { LearningModuleStorageUtilisation } from "./learning-module-storage-utilisation";
export interface LearningModuleManager {
/**
- * Removes the learning module with the given id.
- * All stored files of the module will be removed as well.
+ * Loads all relevant data of the learning module
+ * the given {@code objectId} and stores them.
+ *
+ * @param {number} objectId - ILIAS object id of the learning module
*/
- remove(objectId: number, userId: number): Promise;
+ load(objectId: number): Promise;
/**
- * Calculates the used storage of the learning module. The used storage within the sqlite database is not included.
+ * Checks whether the learning module is available on the mobile device
+ * and downloads it if this is not the case
*/
- storageSpaceUsage(objectId: number, userId: number): Promise;
+ checkAndDownload(objectId: number, userId: number): Promise;
+
+ /**
+ * Removes the learning module with the given id.
+ * All stored files of the module will be removed as well.
+ */
+ remove(objectId: number, userId: number): Promise;
}
export const LEARNING_MODULE_MANAGER: InjectionToken = new InjectionToken("token for learning module manager.");
@@ -36,28 +50,46 @@ export const LEARNING_MODULE_MANAGER: InjectionToken = ne
*/
@Injectable()
export class LearningModuleManagerImpl implements LearningModuleManager {
+
+ private readonly log: Logger = Logging.getLogger("LearningModuleManagerImpl");
+
constructor(
- protected readonly fileSystem: File,
- protected readonly userStorage: UserStorageService,
+ private readonly storageUtilisation: LearningModuleStorageUtilisation,
+ private readonly fileStorage: FileStorageService,
+ private readonly userStorageManager: UserStorageMamager,
+ @Inject(LEARNING_MODULE_LOADER) private readonly loader: LearningModuleLoader,
@Inject(LEARNING_MODULE_PATH_BUILDER) private readonly pathBuilder: LearningModulePathBuilder,
) {}
+ async checkAndDownload(objectId: number, userId: number): Promise {
+ const obj: ILIASObject = await ILIASObject.findByObjIdAndUserId(objectId, userId);
+
+ this.log.debug(() => `Learning module needs download: ${obj.needsDownload} -> ${!!obj.needsDownload}`);
+ if (obj.needsDownload !== false) {
+ await this.load(objectId);
+ obj.needsDownload = false;
+ await obj.save();
+ }
+ }
+
+ async load(objectId: number): Promise {
+ await this.loader.load(objectId);
+ const user: User = AuthenticationProvider.getUser();
+ await this.userStorageManager.addObjectToUserStorage(user.id, objectId, this.storageUtilisation);
+ }
+
async remove(objId: number, userId: number): Promise {
+ this.log.debug(() => `Remove learning module object: "${objId}", user: "${userId}"`);
+ await this.userStorageManager.removeObjectFromUserStorage(userId, objId, this.storageUtilisation);
+
// remove from database
- const user: User = AuthenticationProvider.getUser();
- const lm: LearningModule = await LearningModule.findByObjIdAndUserId(objId, user.id);
+ const lm: LearningModule = await LearningModule.findByObjIdAndUserId(objId, userId);
await lm.destroy();
+
// remove from file system
- const localLmDir: string = await this.pathBuilder.inLocalLmDir("", false);
+ const localLmDir: string = await this.pathBuilder.dirInLocalLmDir("", false);
const lmDirName: string = this.pathBuilder.lmDirName(objId);
- await this.userStorage.removeDir(localLmDir, lmDirName);
- }
-
- async storageSpaceUsage(objId: number, userId: number): Promise {
- const lmDirPath: string = await this.pathBuilder.getLmDirByObjId(objId);
- const dir: DirectoryEntry = await this.fileSystem.resolveDirectoryUrl(lmDirPath);
- let size: number = -1;
- dir.getMetadata(it => size = it.size);
- return size;
+ this.log.debug(() => `Remove learning module dir: "${lmDirName}", Path: "${localLmDir}"`);
+ await this.fileStorage.removeDir(localLmDir, lmDirName);
}
}
diff --git a/src/app/learningmodule/services/learning-module-path-builder.ts b/src/app/learningmodule/services/learning-module-path-builder.ts
index 1894c40b..a197ca3b 100644
--- a/src/app/learningmodule/services/learning-module-path-builder.ts
+++ b/src/app/learningmodule/services/learning-module-path-builder.ts
@@ -1,7 +1,7 @@
import {Injectable, InjectionToken} from "@angular/core";
import {File} from "@ionic-native/file/ngx";
import {Platform} from "@ionic/angular";
-import {UserStorageService} from "../../services/filesystem/user-storage.service";
+import {FileStorageService} from "../../services/filesystem/file-storage.service";
/**
* Builds directory paths for learning module.
@@ -23,12 +23,17 @@ export interface LearningModulePathBuilder {
/**
* constructs the absolute path for a location relative to the root directory (with ending /)
*/
- inLocalLmDir(location: string, createRecursive: boolean): Promise;
+ dirInLocalLmDir(location: string, createRecursive: boolean): Promise;
/**
* constructs the absolute path to the directory containing the contents of the learning module (with ending /)
*/
getLmDirByObjId(objId: number): Promise;
+
+ /**
+ * Constructs the absolute base path of the learning modules, without an ending slash.
+ */
+ absoluteBasePath(): Promise;
}
export const LEARNING_MODULE_PATH_BUILDER: InjectionToken = new InjectionToken("token for learning module path builder");
@@ -40,20 +45,26 @@ export class LearningModulePathBuilderImpl implements LearningModulePathBuilder
constructor(
private readonly file: File,
private readonly platform: Platform,
- private readonly userStorage: UserStorageService,
+ private readonly fileStorage: FileStorageService,
) {}
lmDirName(objId: number): string {
return `lm_${objId}/`;
}
- async inLocalLmDir(path: string, createRecursive: boolean): Promise {
+ async absoluteBasePath(): Promise {
+ const baseDir: string = this.withoutEndingSlash(this.lmsBaseDirName);
+ return this.fileStorage.dirForUser(baseDir, true);
+ }
+
+ async dirInLocalLmDir(path: string = "", createRecursive: boolean = false): Promise {
path = this.withoutEndingSlash(path);
- return this.userStorage.dirForUser(`${this.lmsBaseDirName}${path}`, createRecursive);
+ const baseDir: string = path.length ? this.lmsBaseDirName : this.withoutEndingSlash(this.lmsBaseDirName);
+ return this.fileStorage.dirForUser(`${baseDir}${path}`, createRecursive);
}
async getLmDirByObjId(objId: number): Promise {
- return this.inLocalLmDir(this.lmDirName(objId), false);
+ return this.dirInLocalLmDir(this.lmDirName(objId), false);
}
/**
diff --git a/src/app/learningmodule/services/learning-module-storage-utilisation.ts b/src/app/learningmodule/services/learning-module-storage-utilisation.ts
new file mode 100644
index 00000000..6e3e2cf6
--- /dev/null
+++ b/src/app/learningmodule/services/learning-module-storage-utilisation.ts
@@ -0,0 +1,39 @@
+import { Inject, Injectable } from "@angular/core";
+import { DirectoryEntry, File } from "@ionic-native/file/ngx";
+import { StorageUtilization } from "../../services/filesystem/user-storage.mamager";
+import { UserStorageService } from "../../services/filesystem/user-storage.service";
+import { Logger } from "../../services/logging/logging.api";
+import { Logging } from "../../services/logging/logging.service";
+import { LEARNING_MODULE_PATH_BUILDER, LearningModulePathBuilder } from "./learning-module-path-builder";
+
+@Injectable({
+ providedIn: "root"
+})
+export class LearningModuleStorageUtilisation implements StorageUtilization {
+
+ private readonly log: Logger = Logging.getLogger("LearningModuleStorageUtilisation");
+
+ constructor(
+ private readonly fileSystem: File,
+ @Inject(LEARNING_MODULE_PATH_BUILDER) private readonly pathBuilder: LearningModulePathBuilder,
+ ) {
+ }
+
+ async getUsedStorage(objectId: number, userId: number): Promise {
+ try {
+ const lmDirPath: string = await this.pathBuilder.getLmDirByObjId(objectId);
+ const dir: DirectoryEntry = await this.fileSystem.resolveDirectoryUrl(lmDirPath);
+ return UserStorageService.getDirSizeRecursive(dir.nativeURL, this.fileSystem);
+ } catch (error) {
+ // Cordova file plugin throws its own error objects. Code 1 means NOT_FOUND_ERR.
+ // See: https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-file/#list-of-error-codes-and-meanings
+ if (error.code === 1) {
+ this.log.debug(() => `Directory of learning module with objId: ${objectId}, of user: ${userId} does not exist assume size 0 Byte`);
+ return 0;
+ }
+
+ this.log.error(() => `Failed to calculate size for learning module with objId: ${objectId}, for user: ${userId}, reason: ${error.message}`);
+ throw error;
+ }
+ }
+}
diff --git a/src/app/learnplace/actions/open-learnplace-action.ts b/src/app/learnplace/actions/open-learnplace-action.ts
index d4262da3..cf3d1b1d 100644
--- a/src/app/learnplace/actions/open-learnplace-action.ts
+++ b/src/app/learnplace/actions/open-learnplace-action.ts
@@ -1,9 +1,12 @@
+import { UserEntity } from "../../entity/user.entity";
import {LoadingPage, LoadingPageType} from "../../fallback/loading/loading.component";
import {ILIASObjectAction, ILIASObjectActionAlert, ILIASObjectActionNoMessage, ILIASObjectActionResult} from "../../actions/object-action";
-import {LearnplaceLoader} from "../services/loader/learnplace";
import {ModalController, NavController} from "@ionic/angular";
-import {InjectionToken} from "@angular/core";
+import { Inject, InjectionToken } from "@angular/core";
+import { USER_REPOSITORY, UserRepository } from "../../providers/repository/repository.user";
import {LearnplaceNavParams} from "../pages/learnplace-tabs/learnplace.nav-params";
+import {LearnplaceManager} from "../services/learnplace.management";
+import {ILIASObject} from "../../models/ilias-object";
/**
* Opens a learnplace. A learnplace has its own view and content.
@@ -14,11 +17,12 @@ import {LearnplaceNavParams} from "../pages/learnplace-tabs/learnplace.nav-param
export class OpenLearnplaceAction extends ILIASObjectAction {
constructor(
- private readonly loader: LearnplaceLoader,
+ private readonly manager: LearnplaceManager,
private readonly nav: NavController,
private readonly learnplaceObjectId: number,
private readonly learnplaceName: string,
- private readonly modal: ModalController
+ private readonly modal: ModalController,
+ private readonly userRepository: UserRepository
) {super()}
async execute(): Promise {
@@ -30,7 +34,13 @@ export class OpenLearnplaceAction extends ILIASObjectAction {
LoadingPage.type = LoadingPageType.learnplace;
await loadingPage.present();
try {
- await this.loader.load(this.learnplaceObjectId);
+ // load the learnplace if not contained in favorites
+ // TODO how to handle changes of ILIAS object?
+ const user: UserEntity = (await this.userRepository.findAuthenticatedUser()).get();
+ const ilObj: ILIASObject = await ILIASObject.findByObjIdAndUserId(this.learnplaceObjectId, user.id);
+ if(!ilObj.needsDownload)
+ await this.manager.load(this.learnplaceObjectId);
+ // open page for learnplace
LearnplaceNavParams.learnplaceObjectId = this.learnplaceObjectId;
LearnplaceNavParams.learnplaceName = this.learnplaceName;
await this.nav.navigateForward(["learnplace", this.learnplaceObjectId]);
diff --git a/src/app/learnplace/directives/accordion/accordion.directive.ts b/src/app/learnplace/directives/accordion/accordion.directive.ts
index 09bfa7b2..81ff855e 100644
--- a/src/app/learnplace/directives/accordion/accordion.directive.ts
+++ b/src/app/learnplace/directives/accordion/accordion.directive.ts
@@ -26,7 +26,7 @@ export class AccordionBlock implements OnInit, OnDestroy {
@Input("value")
readonly accordion: AccordionBlockModel;
- private expanded: boolean = false;
+ expanded: boolean = false;
private accordionSubscription?: Subscription;
diff --git a/src/app/learnplace/directives/linkblock/link-block.html b/src/app/learnplace/directives/linkblock/link-block.html
index 3a732229..12e821f7 100644
--- a/src/app/learnplace/directives/linkblock/link-block.html
+++ b/src/app/learnplace/directives/linkblock/link-block.html
@@ -2,7 +2,7 @@
- {{linkLabel}}
+ {{!!linkLabel ? linkLabel : ("learnplace.block.link.title_unknown" | translate)}}
diff --git a/src/app/learnplace/directives/pictureblock/pictureblock.directive.ts b/src/app/learnplace/directives/pictureblock/pictureblock.directive.ts
index d1d60df4..2edd0081 100644
--- a/src/app/learnplace/directives/pictureblock/pictureblock.directive.ts
+++ b/src/app/learnplace/directives/pictureblock/pictureblock.directive.ts
@@ -15,7 +15,7 @@ import {WebView} from "@ionic-native/ionic-webview/ngx";
export class PictureBlock implements OnInit {
@Input("value")
- readonly pictureBlock: PictureBlockModel;
+ pictureBlock: PictureBlockModel;
embeddedSrc?: SafeUrl;
diff --git a/src/app/learnplace/pages/content/content.component.ts b/src/app/learnplace/pages/content/content.component.ts
index 35d5cb58..7671ecfa 100644
--- a/src/app/learnplace/pages/content/content.component.ts
+++ b/src/app/learnplace/pages/content/content.component.ts
@@ -1,37 +1,45 @@
-import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject} from "@angular/core";
-import {ViewDidLeave, ViewWillEnter, ViewDidEnter} from "ionic-lifecycle-interface";
-import {NEVER, Observable} from "rxjs";
-import {shareReplay, tap} from "rxjs/operators";
-import {BlockModel} from "../../services/block.model";
-import {BLOCK_SERVICE, BlockService} from "../../services/block.service";
-import {LearnplaceNavParams} from "../learnplace-tabs/learnplace.nav-params";
+import { Component, Inject, NgZone, OnDestroy } from "@angular/core";
+import { ViewDidLeave, ViewWillEnter } from "ionic-lifecycle-interface";
+import { ReplaySubject, Subject } from "rxjs";
+import { takeUntil } from "rxjs/operators";
+import { BlockModel } from "../../services/block.model";
+import { BLOCK_SERVICE, BlockService } from "../../services/block.service";
+import { LearnplaceNavParams } from "../learnplace-tabs/learnplace.nav-params";
@Component({
- templateUrl: "content.html"
+ templateUrl: "content.html",
+ styleUrls: ["content.scss"]
})
-export class ContentPage implements ViewWillEnter, ViewDidEnter, ViewDidLeave {
+export class ContentPage implements ViewWillEnter, ViewDidLeave, OnDestroy {
- blockList: Observable> = NEVER;
+ private readonly dispose$: Subject = new Subject();
+ readonly blockList: ReplaySubject> = new ReplaySubject>(1);
constructor(
@Inject(BLOCK_SERVICE) private readonly blockService: BlockService,
- private readonly detectorRef: ChangeDetectorRef,
+ private readonly zone: NgZone,
) { }
ionViewWillEnter(): void {
// we detect property changes, when a block list is emitted to update the UI with the new block list
- this.blockList = this.blockService.getBlockList(LearnplaceNavParams.learnplaceObjectId)
+ this.blockService.getBlockList(LearnplaceNavParams.learnplaceObjectId)
.pipe(
- tap(() => this.detectorRef.detectChanges()),
- shareReplay(1)
- );
- }
-
- ionViewDidEnter(): void {
- this.detectorRef.detectChanges();
+ takeUntil(this.dispose$)
+ ).subscribe((it) => {
+ console.log("Block List: ", it);
+ this.zone.run(() => this.blockList.next(it));
+ });
}
+ // Ionic won't call this callback if the entire learnplace tab nav gets popped.
ionViewDidLeave(): void {
+ this.dispose$.next();
this.blockService.shutdown();
}
+
+ ngOnDestroy(): void {
+ this.ionViewDidLeave();
+ this.dispose$.complete();
+ this.blockList.complete();
+ }
}
diff --git a/src/app/learnplace/pages/content/content.html b/src/app/learnplace/pages/content/content.html
index 0533561f..3df3cceb 100644
--- a/src/app/learnplace/pages/content/content.html
+++ b/src/app/learnplace/pages/content/content.html
@@ -1,15 +1,9 @@
-
-
+
-
-
-
-
-
-
+
- {{"learnplace.block.no_content" | translate}}
+ {{"learnplace.block.no_content" | translate}}
diff --git a/src/app/learnplace/pages/content/content.scss b/src/app/learnplace/pages/content/content.scss
index 1d94db70..c67730ec 100644
--- a/src/app/learnplace/pages/content/content.scss
+++ b/src/app/learnplace/pages/content/content.scss
@@ -1,16 +1,16 @@
-ion-content{
-
- h1{
- text-align:left;
+ion-content {
+ h1 {
+ text-align: left;
}
- h2{
- text-align:left;
- padding-top: .67em;
+
+ h2 {
+ text-align: left;
+ padding-top: .67em;
}
p {
- font-size: 1.8em;
- padding-top: .25em;
- text-align: left;
+ font-size: 1.8em;
+ padding-top: .25em;
+ text-align: left;
}
}
diff --git a/src/app/learnplace/pages/learnplace-tabs/learnplace-tabs.component.ts b/src/app/learnplace/pages/learnplace-tabs/learnplace-tabs.component.ts
index fb41872b..3c8f2dac 100644
--- a/src/app/learnplace/pages/learnplace-tabs/learnplace-tabs.component.ts
+++ b/src/app/learnplace/pages/learnplace-tabs/learnplace-tabs.component.ts
@@ -7,6 +7,7 @@ import {LearnplaceNavParams} from "./learnplace.nav-params";
@Component({
templateUrl: "learnplace-tabs.html",
+ styleUrls: ["learnplace-tabs.scss"]
})
export class LearnplaceTabsPage implements ViewWillEnter, ViewDidLeave {
diff --git a/src/app/learnplace/pages/learnplace-tabs/learnplace-tabs.html b/src/app/learnplace/pages/learnplace-tabs/learnplace-tabs.html
index 3913d6ea..8cc1a752 100644
--- a/src/app/learnplace/pages/learnplace-tabs/learnplace-tabs.html
+++ b/src/app/learnplace/pages/learnplace-tabs/learnplace-tabs.html
@@ -11,16 +11,17 @@
-
-
-
-
-
- Map
-
-
-
- Content
-
-
-
+
+
+
+
+
+ {{"map" | translate}}
+
+
+
+ {{"content" | translate}}
+
+
+
+
diff --git a/src/app/learnplace/pages/learnplace-tabs/learnplace-tabs.scss b/src/app/learnplace/pages/learnplace-tabs/learnplace-tabs.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/learnplace/pages/map/map.component.ts b/src/app/learnplace/pages/map/map.component.ts
index 9609a792..7136a820 100644
--- a/src/app/learnplace/pages/map/map.component.ts
+++ b/src/app/learnplace/pages/map/map.component.ts
@@ -1,5 +1,4 @@
-import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, ViewChild} from "@angular/core";
-import {IonContent} from "@ionic/angular";
+import { ChangeDetectorRef, Component, Inject, OnDestroy, ViewChild } from "@angular/core";
import {ViewDidLeave, ViewWillEnter, ViewDidEnter} from "ionic-lifecycle-interface";
import {Subscription} from "rxjs";
import {Logger} from "../../../services/logging/logging.api";
@@ -11,9 +10,10 @@ import {LearnplaceNavParams} from "../learnplace-tabs/learnplace.nav-params";
@Component({
selector: "map",
- templateUrl: "map.html"
+ templateUrl: "map.html",
+ styleUrls: ["map.scss"]
})
-export class MapPage implements ViewWillEnter, ViewDidEnter, ViewDidLeave {
+export class MapPage implements ViewWillEnter, ViewDidEnter, ViewDidLeave, OnDestroy {
@ViewChild("map", {"static": false}) mapElement: Element;
@@ -48,11 +48,22 @@ export class MapPage implements ViewWillEnter, ViewDidEnter, ViewDidLeave {
this.detectorRef.detectChanges();
}
+ // Ionic won't call this callback if the entire learnplace tab nav gets popped.
ionViewDidLeave(): void {
this.mapService.shutdown();
this.mapSubscription.unsubscribe();
- while (this.mapElement.firstChild) {
- this.mapElement.removeChild(this.mapElement.firstChild);
+ if (!!this.mapElement) {
+ while (this.mapElement.firstChild) {
+ this.mapElement.removeChild(this.mapElement.firstChild);
+ }
+ }
+ this.mapSubscription = undefined;
+ }
+
+ ngOnDestroy(): void {
+ // workaround
+ if (this.mapSubscription !== undefined) {
+ this.ionViewDidLeave();
}
}
diff --git a/src/app/learnplace/pages/map/map.html b/src/app/learnplace/pages/map/map.html
index d7320ea9..4fab1bd8 100644
--- a/src/app/learnplace/pages/map/map.html
+++ b/src/app/learnplace/pages/map/map.html
@@ -1,17 +1,11 @@
-
-
-
-
-
-
-
+
-
+
-
{{map?.getDescriptionLangVar() | translate}}
+ {{map?.getDescriptionLangVar() | translate}}
diff --git a/src/app/learnplace/pages/map/map.scss b/src/app/learnplace/pages/map/map.scss
new file mode 100644
index 00000000..a737a5c5
--- /dev/null
+++ b/src/app/learnplace/pages/map/map.scss
@@ -0,0 +1,10 @@
+
+#map {
+ height: 100%;
+ width: 100%;
+ position: absolute !important;
+ top: 0 !important;
+ left: 0 !important;
+ right: 0 !important;
+ z-index: 0;
+}
\ No newline at end of file
diff --git a/src/app/learnplace/providers/rest/json.schema.ts b/src/app/learnplace/providers/rest/json.schema.ts
index b328365c..15b19e3e 100644
--- a/src/app/learnplace/providers/rest/json.schema.ts
+++ b/src/app/learnplace/providers/rest/json.schema.ts
@@ -228,3 +228,177 @@ export const journalEntriesJsonSchema: object = {
"required": ["userId", "timestamp"]
}
};
+
+export const iliasObjectJsonSchema: object = {
+ "$schema": "http://json-schema.org/draft-07/schema",
+ "$id": "http://example.com/example.json",
+ "type": "object",
+ "title": "The root schema",
+ "description": "The root schema comprises the entire JSON document.",
+ "default": {},
+ "examples": [
+ {
+ "objId": "1917",
+ "title": "test obj",
+ "description": "",
+ "hasPageLayout": false,
+ "hasTimeline": false,
+ "permissionType": "read",
+ "refId": "919",
+ "parentRefId": "176",
+ "type": "htlm",
+ "link": "https://test.studer-raimann.ch/pegasus-ilias54-php7/goto.php?target=htlm_919&client_id=default",
+ "repoPath": [
+ "ILIAS",
+ "Demo srag",
+ "test obj"
+ ]
+ }
+ ],
+ "required": [
+ "objId",
+ "title",
+ "description",
+ "hasPageLayout",
+ "hasTimeline",
+ "permissionType",
+ "refId",
+ "parentRefId",
+ "type",
+ "link",
+ "repoPath"
+ ],
+ "properties": {
+ "objId": {
+ "$id": "#/properties/objId",
+ "type": "string",
+ "title": "The objId schema",
+ "description": "An explanation about the purpose of this instance.",
+ "default": "",
+ "examples": [
+ "1917"
+ ]
+ },
+ "title": {
+ "$id": "#/properties/title",
+ "type": "string",
+ "title": "The title schema",
+ "description": "An explanation about the purpose of this instance.",
+ "default": "",
+ "examples": [
+ "obj example"
+ ]
+ },
+ "description": {
+ "$id": "#/properties/description",
+ "type": "string",
+ "title": "The description schema",
+ "description": "An explanation about the purpose of this instance.",
+ "default": "",
+ "examples": [
+ ""
+ ]
+ },
+ "hasPageLayout": {
+ "$id": "#/properties/hasPageLayout",
+ "type": "boolean",
+ "title": "The hasPageLayout schema",
+ "description": "An explanation about the purpose of this instance.",
+ "default": false,
+ "examples": [
+ false
+ ]
+ },
+ "hasTimeline": {
+ "$id": "#/properties/hasTimeline",
+ "type": "boolean",
+ "title": "The hasTimeline schema",
+ "description": "An explanation about the purpose of this instance.",
+ "default": false,
+ "examples": [
+ false
+ ]
+ },
+ "permissionType": {
+ "$id": "#/properties/permissionType",
+ "type": "string",
+ "title": "The permissionType schema",
+ "description": "An explanation about the purpose of this instance.",
+ "default": "",
+ "examples": [
+ "read"
+ ]
+ },
+ "refId": {
+ "$id": "#/properties/refId",
+ "type": "string",
+ "title": "The refId schema",
+ "description": "An explanation about the purpose of this instance.",
+ "default": "",
+ "examples": [
+ "919"
+ ]
+ },
+ "parentRefId": {
+ "$id": "#/properties/parentRefId",
+ "type": "string",
+ "title": "The parentRefId schema",
+ "description": "An explanation about the purpose of this instance.",
+ "default": "",
+ "examples": [
+ "176"
+ ]
+ },
+ "type": {
+ "$id": "#/properties/type",
+ "type": "string",
+ "title": "The type schema",
+ "description": "An explanation about the purpose of this instance.",
+ "default": "",
+ "examples": [
+ "htlm"
+ ]
+ },
+ "link": {
+ "$id": "#/properties/link",
+ "type": "string",
+ "title": "The link schema",
+ "description": "An explanation about the purpose of this instance.",
+ "default": "",
+ "examples": [
+ "https://test.studer-raimann.ch/pegasus-ilias54-php7/goto.php?target=htlm_919&client_id=default"
+ ]
+ },
+ "repoPath": {
+ "$id": "#/properties/repoPath",
+ "type": "array",
+ "title": "The repoPath schema",
+ "description": "An explanation about the purpose of this instance.",
+ "default": [],
+ "examples": [
+ [
+ "ILIAS",
+ "Demo srag"
+ ]
+ ],
+ "additionalItems": true,
+ "items": {
+ "$id": "#/properties/repoPath/items",
+ "anyOf": [
+ {
+ "$id": "#/properties/repoPath/items/anyOf/0",
+ "type": "string",
+ "title": "The first anyOf schema",
+ "description": "An explanation about the purpose of this instance.",
+ "default": "",
+ "examples": [
+ "ILIAS",
+ "Demo srag"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "additionalProperties": true
+}
diff --git a/src/app/learnplace/providers/rest/learnplace.api.ts b/src/app/learnplace/providers/rest/learnplace.api.ts
index 0ffe4132..3a139c0a 100644
--- a/src/app/learnplace/providers/rest/learnplace.api.ts
+++ b/src/app/learnplace/providers/rest/learnplace.api.ts
@@ -1,10 +1,11 @@
-import {BlockObject, JournalEntry, LearnPlace} from "./learnplace.pojo";
-import {ILIAS_REST, ILIASRequestOptions, ILIASRest} from "../../../providers/ilias/ilias.rest";
-import {Inject, Injectable, InjectionToken} from "@angular/core";
-import {HttpResponse} from "../../../providers/http";
-import {blocksJsonSchema, journalEntriesJsonSchema, learnplaceJsonSchema} from "./json.schema";
-import {Logger} from "../../../services/logging/logging.api";
-import {Logging} from "../../../services/logging/logging.service";
+import { Inject, Injectable, InjectionToken } from "@angular/core";
+import { IliasObjectService } from "src/app/services/ilias-object.service";
+import { HttpResponse } from "../../../providers/http";
+import { ILIAS_REST, ILIASRequestOptions, ILIASRest } from "../../../providers/ilias/ilias.rest";
+import { Logger } from "../../../services/logging/logging.api";
+import { Logging } from "../../../services/logging/logging.service";
+import { blocksJsonSchema, journalEntriesJsonSchema, learnplaceJsonSchema } from "./json.schema";
+import { BlockObject, ILIASLinkBlock, JournalEntry, LearnPlace } from "./learnplace.pojo";
const DEFAULT_REQUEST_OPTIONS: ILIASRequestOptions =
{accept: "application/json"};
@@ -70,7 +71,8 @@ export class ILIASLearnplaceAPI implements LearnplaceAPI {
private log: Logger = Logging.getLogger(ILIASLearnplaceAPI.name);
constructor(
- @Inject(ILIAS_REST) private readonly iliasRest: ILIASRest
+ @Inject(ILIAS_REST) private readonly iliasRest: ILIASRest,
+ private readonly ilObjService: IliasObjectService
) {}
/**
@@ -143,8 +145,31 @@ export class ILIASLearnplaceAPI implements LearnplaceAPI {
const response: HttpResponse = await this.iliasRest.get(`/v2/ilias-app/learnplace/${learnplaceObjectId}/blocks`, DEFAULT_REQUEST_OPTIONS);
- return response.handle(it =>
+ const blocks: BlockObject = response.handle(it =>
it.json(blocksJsonSchema)
);
+
+ await this.downloadLinkBlockRelatedILIASObject(blocks);
+ return blocks;
+ }
+
+ /**
+ * Downloads the link block related ILIAS object.
+ *
+ * @param {BlockObject} blocks - The blocks of the current learnplace, which are used to fetch the related ilias objects
+ * @private
+ */
+ private async downloadLinkBlockRelatedILIASObject(blocks: BlockObject): Promise {
+ const linkBlocks: Array = blocks.iliasLink.concat(
+ blocks.accordion.reduceRight(
+ (prev, curr) => prev.concat(curr.iliasLink),
+ new Array())
+ );
+
+ if (linkBlocks.length === 0) {
+ return;
+ }
+
+ await this.ilObjService.downloadIlObjByRefID(linkBlocks.map(block => block.refId));
}
}
diff --git a/src/app/learnplace/services/learnplace.management.ts b/src/app/learnplace/services/learnplace.management.ts
index 2f7b7c21..e408ee65 100644
--- a/src/app/learnplace/services/learnplace.management.ts
+++ b/src/app/learnplace/services/learnplace.management.ts
@@ -7,6 +7,10 @@ import {VideoBlockEntity} from "../entity/videoblock.entity";
import {LEARNPLACE_REPOSITORY, LearnplaceRepository} from "../providers/repository/learnplace.repository";
import {File, FileEntry, RemoveResult} from "@ionic-native/file/ngx";
import {LEARNPLACE_PATH_BUILDER, LearnplacePathBuilder} from "./loader/resource";
+import {StorageUtilization, UserStorageMamager} from "../../services/filesystem/user-storage.mamager";
+import {LEARNPLACE_LOADER, LearnplaceLoader} from "./loader/learnplace";
+import {AuthenticationProvider} from "../../providers/authentication.provider";
+import {User} from "../../models/user";
/**
* Describes a service to manage learnplaces.
@@ -16,6 +20,16 @@ import {LEARNPLACE_PATH_BUILDER, LearnplacePathBuilder} from "./loader/resource"
*/
export interface LearnplaceManager {
+ /**
+ * Loads all relevant data of the learnplace matching
+ * the given {@code objectId} and stores them.
+ *
+ * @param {number} objectId - ILIAS object id of the learnplace
+ *
+ * @throws {LearnplaceLoadingError} if the learnplace could not be loaded
+ */
+ load(objectId: number): Promise;
+
/**
* Removes the learnplace with the given id.
* All stored files of the learnplace will be removed as well.
@@ -37,7 +51,7 @@ export interface LearnplaceManager {
*
* @returns {Promise} The used storage in bytes.
*/
- storageSpaceUsage(objectId: number, userId: number): Promise;
+ getUsedStorage(objectId: number, userId: number): Promise;
}
export const LEARNPLACE_MANAGER: InjectionToken = new InjectionToken("token for learnplace manager.");
@@ -49,18 +63,26 @@ export const LEARNPLACE_MANAGER: InjectionToken = new Injecti
* @version 1.0.0
*/
@Injectable()
-export class LearnplaceManagerImpl implements LearnplaceManager{
+export class LearnplaceManagerImpl implements LearnplaceManager, StorageUtilization{
private log: Logger = Logging.getLogger(LearnplaceManagerImpl.name);
constructor(
private readonly file: File,
+ private readonly userStorageManager: UserStorageMamager,
@Inject(LEARNPLACE_REPOSITORY) private readonly learnplaceRepository: LearnplaceRepository,
- @Inject(LEARNPLACE_PATH_BUILDER) private readonly pathBuilder: LearnplacePathBuilder
+ @Inject(LEARNPLACE_PATH_BUILDER) private readonly pathBuilder: LearnplacePathBuilder,
+ @Inject(LEARNPLACE_LOADER) private readonly loader: LearnplaceLoader
){}
+ async load(objectId: number): Promise {
+ await this.loader.load(objectId);
+ const user: User = AuthenticationProvider.getUser();
+ await this.userStorageManager.addObjectToUserStorage(user.id, objectId, this);
+ }
async remove(objectId: number, userId: number): Promise {
+ await this.userStorageManager.removeObjectFromUserStorage(userId, objectId, this);
return (await this.learnplaceRepository.findByObjectIdAndUserId(objectId, userId)).ifPresent(async(it) => {
@@ -105,9 +127,9 @@ export class LearnplaceManagerImpl implements LearnplaceManager{
return [dirPath, filename];
}
- async storageSpaceUsage(objectId: number, userId: number): Promise {
- let size: number = 0;
- await (await this.learnplaceRepository.findByObjectIdAndUserId(objectId, userId)).ifPresent(async(it) => {
+ async getUsedStorage(objectId: number, userId: number): Promise {
+ let size: number = 0;
+ await (await this.learnplaceRepository.findByObjectIdAndUserId(objectId, userId)).ifPresent(async(it) => {
const paths: Array = [];
const basePath: string = await this.pathBuilder.getStorageLocation();
@@ -118,6 +140,16 @@ export class LearnplaceManagerImpl implements LearnplaceManager{
paths.push(`${basePath}${picture.thumbnail}`);
});
+ it.accordionBlocks.forEach((acc) => {
+ acc.videoBlocks.forEach((video: VideoBlockEntity) => paths.push(`${basePath}${video.url}`));
+ acc.pictureBlocks.forEach((picture: PictureBlockEntity) => {
+ paths.push(`${basePath}${picture.url}`);
+ paths.push(`${basePath}${picture.thumbnail}`);
+ });
+ });
+
+ this.log.debug(() => `Learnplace ${objectId} of user ${userId} contains ${paths.length} files`);
+
for(const fullPath of paths) {
const [path, filename]: [string, string] = this.splitIntoPathNamePair(fullPath);
const result: FileEntry = await this.file.getFile(await this.file.resolveDirectoryUrl(path), filename, {create: false});
@@ -125,6 +157,8 @@ export class LearnplaceManagerImpl implements LearnplaceManager{
}
});
- return size;
+ this.log.debug(() => `Total size of learnplace ${objectId} owned by user ${userId}: ${size}`);
+
+ return size;
}
}
diff --git a/src/app/learnplace/services/loader/learnplace.ts b/src/app/learnplace/services/loader/learnplace.ts
index 3b247405..99204a2a 100644
--- a/src/app/learnplace/services/loader/learnplace.ts
+++ b/src/app/learnplace/services/loader/learnplace.ts
@@ -161,8 +161,7 @@ export class RestLearnplaceLoader implements LearnplaceLoader {
this.videoBlocks = videoBlocks;
this.accordionBlocks = accordionBlocks;
})
- )
- .pipe(
+ ).pipe(
mergeMap(it => from(this.learnplaceRepository.save(it))),
mergeMap(_ => EMPTY), // we want to emit void, so we map the save observable to an empty one
catchError((error, _) => {
diff --git a/src/app/learnplace/services/visibility/visibility.strategy.ts b/src/app/learnplace/services/visibility/visibility.strategy.ts
index 8e295d77..c92aaa1d 100644
--- a/src/app/learnplace/services/visibility/visibility.strategy.ts
+++ b/src/app/learnplace/services/visibility/visibility.strategy.ts
@@ -1,20 +1,19 @@
-
-import {Subscription, Observable, Subscriber, from, of, forkJoin, TeardownLogic } from "rxjs";
-import {map, filter, withLatestFrom, takeWhile, mergeMap, tap, finalize, mergeAll, shareReplay } from "rxjs/operators";
-import {VisibilityAware} from "./visibility.context";
-import {Inject, Injectable} from "@angular/core";
-import {LEARNPLACE_REPOSITORY, LearnplaceRepository} from "../../providers/repository/learnplace.repository";
-import {LearnplaceEntity} from "../../entity/learnplace.entity";
-import {NoSuchElementError} from "../../../error/errors";
-import {Geolocation} from "../../../services/device/geolocation/geolocation.service";
-import {IliasCoordinates} from "../geodesy";
-import {LEARNPLACE_API, LearnplaceAPI} from "../../providers/rest/learnplace.api";
-import {isDefined} from "../../../util/util.function";
-import {VisitJournalEntity} from "../../entity/visit-journal.entity";
-import {USER_REPOSITORY, UserRepository} from "../../../providers/repository/repository.user";
-import {UserEntity} from "../../../entity/user.entity";
-import {Logger} from "../../../services/logging/logging.api";
-import {Logging} from "../../../services/logging/logging.service";
+import { Inject, Injectable } from "@angular/core";
+import { combineLatest, defer, forkJoin, from, Observable, of, Subject, Subscriber, Subscription, TeardownLogic } from "rxjs";
+import { filter, finalize, map, mergeAll, mergeMap, shareReplay, takeUntil, tap } from "rxjs/operators";
+import { UserEntity } from "../../../entity/user.entity";
+import { NoSuchElementError } from "../../../error/errors";
+import { USER_REPOSITORY, UserRepository } from "../../../providers/repository/repository.user";
+import { Geolocation } from "../../../services/device/geolocation/geolocation.service";
+import { Logger } from "../../../services/logging/logging.api";
+import { Logging } from "../../../services/logging/logging.service";
+import { isDefined } from "../../../util/util.function";
+import { LearnplaceEntity } from "../../entity/learnplace.entity";
+import { VisitJournalEntity } from "../../entity/visit-journal.entity";
+import { LEARNPLACE_REPOSITORY, LearnplaceRepository } from "../../providers/repository/learnplace.repository";
+import { LEARNPLACE_API, LearnplaceAPI } from "../../providers/rest/learnplace.api";
+import { IliasCoordinates } from "../geodesy";
+import { VisibilityAware } from "./visibility.context";
/**
* Enumerator for available strategies.
@@ -148,7 +147,7 @@ export class OnlyAtPlaceStrategy implements MembershipAwareStrategy, ShutdownVis
private membershipId: string = "";
- private watch: Subscription | undefined = undefined;
+ private readonly dispose$: Subject = new Subject();
private readonly log: Logger = Logging.getLogger(OnlyAtPlaceStrategy.name);
@@ -186,17 +185,19 @@ export class OnlyAtPlaceStrategy implements MembershipAwareStrategy, ShutdownVis
subscriber.next(object);
- const learnplaceCoordinates: Observable<[IliasCoordinates, LearnplaceEntity]> = from(this.learnplaceRepository.find(this.membershipId))
- .pipe(
+ console.log("Watch position for visibility 'Only at Place' membership id: ", this.membershipId);
+
+ const learnplaceCoordinates: Observable<[IliasCoordinates, LearnplaceEntity]> =
+ defer(() => this.learnplaceRepository.find(this.membershipId)).pipe(
map((it) => it.orElseThrow(() => new NoSuchElementError(`No learnplace found: id=${this.membershipId}`))),
map((it): [IliasCoordinates, LearnplaceEntity] => [new IliasCoordinates(it.location.latitude, it.location.longitude), it])
);
this.log.trace(() => "Watch position for visibility 'Only at Place'");
- this.watch = this.geolocation.watchPosition()
+ const watch: Subscription = combineLatest([this.geolocation.watchPosition(), learnplaceCoordinates])
.pipe(
- filter(it => isDefined(it.coords)),
- withLatestFrom(learnplaceCoordinates)
+ filter(it => isDefined(it[0].coords)),
+ takeUntil(this.dispose$)
)
.subscribe({
next: (location): void => {
@@ -212,7 +213,7 @@ export class OnlyAtPlaceStrategy implements MembershipAwareStrategy, ShutdownVis
complete: (): void => subscriber.complete()
});
- return (): void => this.watch.unsubscribe();
+ return (): void => watch.unsubscribe();
});
}
@@ -220,8 +221,7 @@ export class OnlyAtPlaceStrategy implements MembershipAwareStrategy, ShutdownVis
* Stops watching the device's location.
*/
shutdown(): void {
- if (isDefined(this.watch))
- this.watch.unsubscribe();
+ this.dispose$.next();
}
}
@@ -236,7 +236,7 @@ export class AfterVisitPlaceStrategy implements MembershipAwareStrategy, Shutdow
private membershipId: string = "";
- private running: boolean = false;
+ private readonly dispose$: Subject = new Subject();
constructor(
@Inject(LEARNPLACE_REPOSITORY) private readonly learnplaceRepository: LearnplaceRepository,
@@ -274,7 +274,6 @@ export class AfterVisitPlaceStrategy implements MembershipAwareStrategy, Shutdow
*/
on(object: T): Observable {
- this.running = true;
const learnplace: Observable = from(this.learnplaceRepository.find(this.membershipId))
.pipe(
map(it => it.orElseThrow(() => new NoSuchElementError(`No learnplace found with id: ${this.membershipId}`))),
@@ -298,7 +297,6 @@ export class AfterVisitPlaceStrategy implements MembershipAwareStrategy, Shutdow
return of(
of(object),
this.geolocation.watchPosition().pipe(
- takeWhile(() => this.running),
filter(it => isDefined(it.coords)),
filter(it => {
const currentCoordinates: IliasCoordinates = new IliasCoordinates(it.coords.latitude, it.coords.longitude);
@@ -323,7 +321,8 @@ export class AfterVisitPlaceStrategy implements MembershipAwareStrategy, Shutdow
object.visible = true;
this.shutdown();
return object;
- })
+ }),
+ takeUntil(this.dispose$),
)
).pipe(mergeAll());
}), mergeAll());
@@ -333,6 +332,6 @@ export class AfterVisitPlaceStrategy implements MembershipAwareStrategy, Shutdow
* Stops watching the device's location.
*/
shutdown(): void {
- this.running = false;
+ this.dispose$.next();
}
}
diff --git a/src/app/learnplace/services/visitjournal.service.ts b/src/app/learnplace/services/visitjournal.service.ts
index 278d545d..bf461460 100644
--- a/src/app/learnplace/services/visitjournal.service.ts
+++ b/src/app/learnplace/services/visitjournal.service.ts
@@ -1,5 +1,5 @@
-import {mergeMap, filter, takeWhile, map, tap, catchError, withLatestFrom } from "rxjs/operators";
+import { mergeMap, filter, takeWhile, map, tap, catchError, withLatestFrom, takeUntil } from "rxjs/operators";
import {Inject, Injectable, InjectionToken} from "@angular/core";
import {LEARNPLACE_API, LearnplaceAPI} from "../providers/rest/learnplace.api";
import {VISIT_JOURNAL_REPOSITORY, VisitJournalRepository} from "../providers/repository/visitjournal.repository";
@@ -15,7 +15,7 @@ import {IllegalStateError} from "../../error/errors";
import {IliasCoordinates} from "./geodesy";
import {LearnplaceEntity} from "../entity/learnplace.entity";
import {UserEntity} from "../../entity/user.entity";
-import {Observable, from, combineLatest, of } from "rxjs";
+import { Observable, from, combineLatest, of, Subject } from "rxjs";
/**
* Describes a synchronization that manages un-synchronized visit journal entries.
@@ -108,10 +108,9 @@ export const VISIT_JOURNAL_WATCH: InjectionToken = new Inject
@Injectable()
export class SynchronizedVisitJournalWatch implements VisitJournalWatch {
+ private readonly dispose$: Subject = new Subject();
private learnplaceObjectId: number | undefined = undefined;
- private running: boolean = false;
-
private readonly log: Logger = Logging.getLogger(SynchronizedVisitJournalWatch.name);
constructor(
@@ -139,19 +138,19 @@ export class SynchronizedVisitJournalWatch implements VisitJournalWatch {
throw new IllegalStateError(`Can not start ${SynchronizedVisitJournalWatch.name} without learnplace id`);
}
- this.running = true;
-
const user: Observable = from(this.userRepository.findAuthenticatedUser()).pipe(map(it => it.get()));
const learnplace: Observable = user.pipe(
mergeMap(it => this.learnplaceRepository.findByObjectIdAndUserId(this.learnplaceObjectId, it.id)),
- map(it => it.get()),);
+ map(it => it.get())
+ );
this.log.trace(() => "Start watching the device's location");
const position: Observable = this.geolocation.watchPosition().pipe(
filter(it => isDefined(it.coords)), // filter errors
map(it => new IliasCoordinates(it.coords.latitude, it.coords.longitude)),
- takeWhile(_ => this.running),);
+ takeUntil(this.dispose$)
+ );
combineLatest([user, learnplace, position])
.pipe(
@@ -176,7 +175,8 @@ export class SynchronizedVisitJournalWatch implements VisitJournalWatch {
withLatestFrom(learnplace, (visitJournal: VisitJournalEntity, learnplace: LearnplaceEntity): LearnplaceEntity =>
learnplace.applies(function(): void {
this.visitJournal.push(visitJournal);
- }))
+ })),
+ takeUntil(this.dispose$)
)
.subscribe((it: LearnplaceEntity) => {
this.learnplaceRepository.save(it);
@@ -187,7 +187,7 @@ export class SynchronizedVisitJournalWatch implements VisitJournalWatch {
* Stops watching the device's location.
*/
stop(): void {
- this.running = false;
+ this.dispose$.next();
}
/**
diff --git a/src/app/migrations/V__11-total-user-storage.ts b/src/app/migrations/V__11-total-user-storage.ts
new file mode 100644
index 00000000..0d618a8b
--- /dev/null
+++ b/src/app/migrations/V__11-total-user-storage.ts
@@ -0,0 +1,27 @@
+/**
+ * Adds additional attributes on the users and settings tables
+ *
+ * @author mschneiter
+ * @version 1.0.0
+ */
+import {Migration, MigrationVersion} from "../services/migration/migration.api";
+import {QueryRunner} from "typeorm/browser";
+
+export class TotalUserStorage implements Migration {
+
+ readonly version: MigrationVersion = new MigrationVersion("V__11");
+
+ async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ "ALTER TABLE users " +
+ "ADD totalUsedStorage INTEGER"
+ );
+ }
+
+ async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ "ALTER TABLE users " +
+ "DROP COLUMN totalUsedStorage"
+ );
+ }
+}
diff --git a/src/app/migrations/V__12-remove-legacy-objects-fields.ts b/src/app/migrations/V__12-remove-legacy-objects-fields.ts
new file mode 100644
index 00000000..aec0deae
--- /dev/null
+++ b/src/app/migrations/V__12-remove-legacy-objects-fields.ts
@@ -0,0 +1,64 @@
+/**
+ * Removes unused legacy fields from the object table:
+ * - offlineAvailableOwner
+ * - isNew
+ * - isUpdated
+ *
+ * @author nschaefli
+ * @version 1.0.0
+ */
+import {Migration, MigrationVersion} from "../services/migration/migration.api";
+import {QueryRunner} from "typeorm/browser";
+
+export class RemoveLegacyObjectsFields implements Migration {
+
+ readonly version: MigrationVersion = new MigrationVersion("V__12");
+
+ async up(queryRunner: QueryRunner): Promise {
+ try {
+ // sqlite does not support the ALTER TABLE "" DROP COLUMN ""; statement ...
+ await queryRunner.startTransaction();
+ await queryRunner.query("CREATE TABLE IF NOT EXISTS migration_objects ( " +
+ "id INTEGER PRIMARY KEY AUTOINCREMENT," +
+ "userId INTEGER," +
+ "objId INTEGER," +
+ "refId INTEGER," +
+ "parentRefId INTEGER," +
+ "type TEXT," +
+ "title TEXT," +
+ "description TEXT," +
+ "link TEXT," +
+ "isOfflineAvailable INTEGER," +
+ "isFavorite INTEGER," +
+ "data TEXT," +
+ "repoPath TEXT," +
+ "needsDownload INTEGER," +
+ "hasPageLayout BOOLEAN NOT NULL DEFAULT 0 CHECK (hasPageLayout IN (0,1))," +
+ "hasTimeline BOOLEAN NOT NULL DEFAULT 0 CHECK (hasTimeline IN (0,1))," +
+ "permissionType TEXT NOT NULL DEFAULT 'visible'," +
+ "createdAt DATETIME DEFAULT CURRENT_TIMESTAMP," +
+ "updatedAt DATETIME" +
+ ");");
+
+ await queryRunner.query("" +
+ "INSERT INTO migration_objects(id, userId, objId, refId, parentRefId, type, title, description, link, isOfflineAvailable, isFavorite, data, repoPath, needsDownload, hasPageLayout, hasTimeline, permissionType, createdAt, updatedAt) " +
+ "SELECT id, userId, objId, refId, parentRefId, type, title, description, link, isOfflineAvailable, isFavorite, data, repoPath, needsDownload, hasPageLayout, hasTimeline, permissionType, createdAt, updatedAt " +
+ "FROM objects;");
+
+ await queryRunner.query("DROP TABLE objects;");
+ await queryRunner.query("ALTER TABLE migration_objects RENAME TO objects;");
+
+ await queryRunner.commitTransaction();
+ } catch (error) {
+ await queryRunner.rollbackTransaction();
+ throw error;
+ }
+ }
+
+ async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ "ALTER TABLE objects " +
+ "ADD totalUsedStorage TEXT"
+ );
+ }
+}
diff --git a/src/app/models/active-record.ts b/src/app/models/active-record.ts
index 8a7de44e..4ba51eb7 100644
--- a/src/app/models/active-record.ts
+++ b/src/app/models/active-record.ts
@@ -1,7 +1,9 @@
/** logging */
-import {Log} from "../services/log.service";
+import { EntityManager } from "typeorm/browser";
/** misc */
-import {DatabaseService, SQLiteDatabaseService} from "../services/database.service";
+import { SQLiteDatabaseService } from "../services/database.service";
+import { Logger } from "../services/logging/logging.api";
+import { Logging } from "../services/logging/logging.service";
export interface DatabaseConnector {
/**
@@ -19,14 +21,22 @@ export interface DatabaseConnector {
* @param sql
* @param params
*/
- query(sql: string, params?: Array);
+ query(sql: string, params?: Array): Promise