From 4181d4227e57c20508b7a7c36b0f2acfab77b994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20Scha=CC=88fli?= Date: Thu, 26 Apr 2018 10:37:28 +0200 Subject: [PATCH 01/10] Fix basepath --- docs/_config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_config.yml b/docs/_config.yml index be6fdcc5..cca096e2 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -19,7 +19,7 @@ description: >- # this means to ignore newlines until "baseurl:" Write an awesome description for your new site here. You can edit this line in _config.yml. It will appear in your document head meta (for Google search results) and in your feed.xml site description. -baseurl: "" # the subpath of your site, e.g. /blog +baseurl: "/ILIAS-Pegasus" # the subpath of your site, e.g. /blog url: "" # the base hostname & protocol for your site, e.g. http://example.com twitter_username: jekyllrb github_username: jekyll From 0dee8fdb767a2f64276d5208a9071afdd6428275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20Scha=CC=88fli?= Date: Thu, 26 Apr 2018 10:47:19 +0200 Subject: [PATCH 02/10] Increment documentation number --- docs/_data/metadata.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/metadata.yaml b/docs/_data/metadata.yaml index ef20e834..4386b88a 100644 --- a/docs/_data/metadata.yaml +++ b/docs/_data/metadata.yaml @@ -1,2 +1,2 @@ title: "ILIAS Pegasus Documentation" -version: 2.0.0 +version: 2.0.1 From 6ff449057ae57aeab9656f978e06757a7ab18720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20Scha=CC=88fli?= Date: Thu, 26 Apr 2018 18:32:26 +0200 Subject: [PATCH 03/10] Fix html tags in news entries --- src/app/app.component.ts | 37 +++++++++++++++------------------- src/app/app.module.ts | 11 ++-------- src/pages/news/news.html | 2 +- src/pages/news/news.module.ts | 34 +++++++++++++++++++++++++++++++ src/pages/news/news.ts | 19 ++++++++++------- src/services/news/news.feed.ts | 15 +++++++++----- 6 files changed, 75 insertions(+), 43 deletions(-) create mode 100644 src/pages/news/news.module.ts diff --git a/src/app/app.component.ts b/src/app/app.component.ts index e663a334..1d0d5cd1 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,33 +1,28 @@ import {Component, Inject, ViewChild} from "@angular/core"; -import { - Platform, MenuController, Nav, Events, ToastController, Toast, - ToastOptions, Modal, ModalController, Config -} from "ionic-angular"; +import {Network} from "@ionic-native/network"; +import {SplashScreen} from "@ionic-native/splash-screen"; +import {SQLite} from "@ionic-native/sqlite"; import {StatusBar} from "@ionic-native/status-bar"; -import {LoginPage} from "../pages/login/login"; -import {SettingsPage} from "../pages/settings/settings"; +import {Config, Events, MenuController, ModalController, Nav, Platform, Toast, ToastController, ToastOptions} from "ionic-angular"; +import {TranslateService} from "ng2-translate/src/translate.service"; +import {PEGASUS_CONNECTION_NAME} from "../config/typeORM-config"; +import {Settings} from "../models/settings"; +import {User} from "../models/user"; import {FavoritesPage} from "../pages/favorites/favorites"; import {InfoPage} from "../pages/info/info"; -import {ObjectListPage} from "../pages/object-list/object-list"; -import {FooterToolbarService, Job} from "../services/footer-toolbar.service"; +import {LoginPage} from "../pages/login/login"; import {NewObjectsPage} from "../pages/new-objects/new-objects"; -import {Settings} from "../models/settings"; -import {User} from "../models/user"; -import {Network} from "@ionic-native/network"; -import {TranslateService} from "ng2-translate/src/translate.service"; -import {SynchronizationService} from "../services/synchronization.service"; +import {ObjectListPage} from "../pages/object-list/object-list"; +import {SettingsPage} from "../pages/settings/settings"; import {SQLiteDatabaseService} from "../services/database.service"; -import {SQLite} from "@ionic-native/sqlite"; -import {PEGASUS_CONNECTION_NAME} from "../config/typeORM-config"; -import {SplashScreen} from "@ionic-native/splash-screen"; import {Database} from "../services/database/database"; -import {DB_MIGRATION, DBMigration} from "../services/migration/migration.api"; +import {FooterToolbarService, Job} from "../services/footer-toolbar.service"; import {Logger} from "../services/logging/logging.api"; import {Logging} from "../services/logging/logging.service"; -import getMessage = Logging.getMessage; -import {HardwareFeaturePage} from "../pages/test-hardware-feature/test-hardware-feature"; -import {NewsPage} from "../pages/news/news"; +import {DB_MIGRATION, DBMigration} from "../services/migration/migration.api"; +import {SynchronizationService} from "../services/synchronization.service"; import {LoadingPage} from "./fallback/loading/loading.component"; +import getMessage = Logging.getMessage; @Component({ templateUrl: "app.html" @@ -44,7 +39,7 @@ export class MyApp { settingsPage: object = SettingsPage; infoPage: object = InfoPage; loginPage: object = LoginPage; - newsPage: object = NewsPage; + newsPage: string = "NewsPage"; //needs to be string in order to get lazy loaded LoadingPage: object = LoadingPage; loggedIn: boolean = false; /** diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 80d0a093..5dc1afb5 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -64,7 +64,6 @@ import {InfoPage} from "../pages/info/info"; import {LoginPage} from "../pages/login/login"; import {ModalPage} from "../pages/modal/modal"; import {NewObjectsPage} from "../pages/new-objects/new-objects"; -import {NewsPage} from "../pages/news/news"; import {ObjectDetailsPage} from "../pages/object-details/object-details"; import {ObjectListPage} from "../pages/object-list/object-list"; import {SettingsPage} from "../pages/settings/settings"; @@ -105,7 +104,6 @@ import {RESOURCE_LINK_BUILDER, ResourceLinkBuilder, ResourceLinkBuilderImpl} fro import {TIMELINE_LINK_BUILDER, TimelineLinkBuilder, TimelineLinkBuilderImpl} from "../services/link/timeline.builder"; import {DB_MIGRATION, MIGRATION_SUPPLIER} from "../services/migration/migration.api"; import {SimpleMigrationSupplier, TypeOrmDbMigration} from "../services/migration/migration.service"; -import {NEWS_FEED, NewsFeedImpl} from "../services/news/news.feed"; import {NEWS_SYNCHRONIZATION, NewsSynchronizationImpl} from "../services/news/news.synchronization"; import {SynchronizationService} from "../services/synchronization.service"; import {MyApp} from "./app.component"; @@ -131,7 +129,6 @@ import {HTTP} from "@ionic-native/http"; FileSizePipe, SyncFinishedModal, ModalPage, - NewsPage, LoadingPage, /* from src/learnplace */ @@ -157,6 +154,7 @@ import {HTTP} from "@ionic-native/http"; IonicModule.forRoot(MyApp), BrowserModule, BrowserAnimationsModule, + HttpModule, HttpClientModule, TranslateModule.forRoot({ @@ -176,7 +174,7 @@ import {HTTP} from "@ionic-native/http"; ObjectDetailsPage, LoginPage, SyncFinishedModal, - NewsPage, + //NewsPage, LoadingPage, /* from src/learnplace */ @@ -248,11 +246,6 @@ import {HTTP} from "@ionic-native/http"; DatabaseConnectionRegistry, Database, - /* from src/services/news/news.feed */ - { - provide: NEWS_FEED, - useClass: NewsFeedImpl - }, /* from src/services/news/news.synchronization */ { provide: NEWS_SYNCHRONIZATION, diff --git a/src/pages/news/news.html b/src/pages/news/news.html index 2e7c1ed5..ddc26f8b 100644 --- a/src/pages/news/news.html +++ b/src/pages/news/news.html @@ -35,7 +35,7 @@

{{newsPresenter[0].title}}

{{newsPresenter[0].subtitle}}

-

{{newsPresenter[0].content}}

+

{{newsPresenter[0].updateDate | date:"dd. MMM yyyy HH:mm"}} diff --git a/src/pages/news/news.module.ts b/src/pages/news/news.module.ts new file mode 100644 index 00000000..ed1f8041 --- /dev/null +++ b/src/pages/news/news.module.ts @@ -0,0 +1,34 @@ +import {NgModule} from "@angular/core"; +import {Http} from "@angular/http"; + +import {IonicPageModule} from "ionic-angular"; +import {TranslateLoader, TranslateModule, TranslateStaticLoader} from "ng2-translate"; +import {NEWS_FEED, NewsFeedImpl} from "../../services/news/news.feed"; +import {NewsPage} from "./news"; +import {CommonModule} from "@angular/common"; + +@NgModule({ + declarations: [ + NewsPage + ], + imports: [ + IonicPageModule.forChild(NewsPage), + CommonModule, + TranslateModule.forRoot({ + provide: TranslateLoader, + useFactory: (http: Http): TranslateStaticLoader => new TranslateStaticLoader(http, "./assets/i18n", ".json"), + deps: [Http] + }) + ], + providers: [ + /* from src/services/news/news.feed */ + { + provide: NEWS_FEED, + useClass: NewsFeedImpl + } + ], + entryComponents: [ + NewsPage + ] +}) +export class NewsPageModule {} diff --git a/src/pages/news/news.ts b/src/pages/news/news.ts index 4f7c3f43..15af95bc 100644 --- a/src/pages/news/news.ts +++ b/src/pages/news/news.ts @@ -1,5 +1,5 @@ -import {AfterViewInit, Component, Inject} from "@angular/core"; -import {Modal, ModalController, Refresher} from "ionic-angular"; +import {AfterViewInit, Component, Inject, SecurityContext, OnInit} from "@angular/core"; +import {Modal, ModalController, Refresher, IonicPage} from "ionic-angular"; import {TranslateService} from "ng2-translate/src/translate.service"; import {ILIASObjectAction} from "../../actions/object-action"; import {OPEN_OBJECT_IN_ILIAS_ACTION_FACTORY, OpenObjectInILIASAction} from "../../actions/open-object-in-ilias-action"; @@ -21,11 +21,12 @@ import {SyncFinishedModal} from "../sync-finished-modal/sync-finished-modal"; * See https://angular.io/api/core/Component for more info on Angular * Components. */ +@IonicPage() @Component({ selector: "newsPresenters", templateUrl: "news.html" }) -export class NewsPage implements AfterViewInit { +export class NewsPage implements OnInit { newsPresenters: Array<[NewsItemModel, ILIASObjectPresenter]>; private readonly log: Logger = Logging.getLogger(NewsPage.name); @@ -42,11 +43,9 @@ export class NewsPage implements AfterViewInit { @Inject(LINK_BUILDER) private readonly linkBuilder: LinkBuilder ) {} - - ngAfterViewInit(): void { + ngOnInit(): void { this.log.debug(() => "News view initialized."); - this.fetchPresenterNewsTuples().then( - (newsPresenterItems: Array<[NewsItemModel, ILIASObjectPresenter]>) => {this.newsPresenters = newsPresenterItems}); + this.reloadView(); } openNews(id: number, context: number): void { @@ -68,6 +67,12 @@ export class NewsPage implements AfterViewInit { async startSync(refresher: Refresher): Promise { await this.executeSync(); refresher.complete(); + this.reloadView(); + } + + reloadView(): void { + this.fetchPresenterNewsTuples().then( + (newsPresenterItems: Array<[NewsItemModel, ILIASObjectPresenter]>) => {this.newsPresenters = newsPresenterItems}); } // ------------------- object-list duplicate---------------------------- diff --git a/src/services/news/news.feed.ts b/src/services/news/news.feed.ts index ee5a047a..cd29290e 100644 --- a/src/services/news/news.feed.ts +++ b/src/services/news/news.feed.ts @@ -1,4 +1,5 @@ import {Inject, Injectable, InjectionToken} from "@angular/core"; +import {SafeHtml, DomSanitizer} from "@angular/platform-browser"; import {USER_REPOSITORY, UserRepository} from "../../providers/repository/repository.user"; import {User} from "../../models/user"; import {UserEntity} from "../../entity/user.entity"; @@ -28,6 +29,7 @@ export class NewsItemModel { readonly newsContext: number, readonly title: string, readonly subtitle: string = "", + //readonly content: SafeHtml = "", readonly content: string = "", readonly createDate: Date = new Date(Date.now()), readonly updateDate: Date = new Date(Date.now()) @@ -45,15 +47,17 @@ export class NewsFeedImpl implements NewsFeed { private static readonly UNIX_TIME_MULTIPLIER_MILIS: number = 1000; - constructor( - @Inject(USER_REPOSITORY) private readonly userRepository: UserRepository, - ) {} + constructor( + private readonly sanitizer: DomSanitizer, + @Inject(USER_REPOSITORY) private readonly userRepository: UserRepository + ) {} async fetchAllForCurrentUser(): Promise> { const activeUser: User = await User.findActiveUser(); const user: UserEntity = (await this.userRepository.find(activeUser.id)).get(); - return user.news.map(this.mapToModel); + const mapToModel: (entity: NewsEntity) => NewsItemModel = this.mapToModel.bind(this); + return user.news.map(mapToModel); } private mapToModel(entity: NewsEntity): NewsItemModel { @@ -62,7 +66,8 @@ export class NewsFeedImpl implements NewsFeed { entity.newsContext, entity.title, entity.subtitle, - entity.content, + // this.sanitizer.bypassSecurityTrustHtml(entity.content), + entity.content, new Date(entity.createDate * NewsFeedImpl.UNIX_TIME_MULTIPLIER_MILIS), new Date(entity.updateDate * NewsFeedImpl.UNIX_TIME_MULTIPLIER_MILIS) ); From c977ea829fe28b655555a4e2596ac2a38a49c926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20Scha=CC=88fli?= Date: Thu, 26 Apr 2018 20:00:04 +0200 Subject: [PATCH 04/10] Add trust secure html to mitigate removal of style attributes ... --- src/services/news/news.feed.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/services/news/news.feed.ts b/src/services/news/news.feed.ts index cd29290e..a32e9783 100644 --- a/src/services/news/news.feed.ts +++ b/src/services/news/news.feed.ts @@ -29,8 +29,7 @@ export class NewsItemModel { readonly newsContext: number, readonly title: string, readonly subtitle: string = "", - //readonly content: SafeHtml = "", - readonly content: string = "", + readonly content: SafeHtml = "", readonly createDate: Date = new Date(Date.now()), readonly updateDate: Date = new Date(Date.now()) ){} @@ -66,8 +65,7 @@ export class NewsFeedImpl implements NewsFeed { entity.newsContext, entity.title, entity.subtitle, - // this.sanitizer.bypassSecurityTrustHtml(entity.content), - entity.content, + this.sanitizer.bypassSecurityTrustHtml(entity.content), new Date(entity.createDate * NewsFeedImpl.UNIX_TIME_MULTIPLIER_MILIS), new Date(entity.updateDate * NewsFeedImpl.UNIX_TIME_MULTIPLIER_MILIS) ); From 9da3a414c7e5e5f9d438beb2d046a3eb905338fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20M=C3=A4rchy?= Date: Tue, 1 May 2018 11:02:35 +0200 Subject: [PATCH 05/10] Fix accordion display and ordering bug More than one accordion block could not be displayed. Furthermore, the order of the blocks was not implemented. --- .../accordion/accordion.directive.ts | 15 +- .../directives/accordion/accordion.html | 36 ++-- .../linkblock/link-block.directive.ts | 107 ++++++----- .../pictureblock/pictureblock.directive.ts | 92 ++++------ .../textblock/textblock.directive.ts | 12 +- .../videoblock/videoblock.directive.ts | 12 +- .../pages/content/content.component.ts | 77 +++----- src/learnplace/pages/content/content.html | 14 +- src/learnplace/services/block.model.ts | 18 +- src/learnplace/services/block.service.ts | 166 +++++++----------- 10 files changed, 234 insertions(+), 315 deletions(-) diff --git a/src/learnplace/directives/accordion/accordion.directive.ts b/src/learnplace/directives/accordion/accordion.directive.ts index 5f02e47c..9180dea0 100644 --- a/src/learnplace/directives/accordion/accordion.directive.ts +++ b/src/learnplace/directives/accordion/accordion.directive.ts @@ -1,7 +1,6 @@ import {ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from "@angular/core"; import {AccordionBlockModel} from "../../services/block.model"; import {animate, state, style, transition, trigger} from "@angular/animations"; -import {Observable} from "rxjs/Observable"; import {Subscription} from "rxjs/Subscription"; import {isDefined} from "ionic-angular/es2015/util/util"; @@ -24,25 +23,19 @@ import {isDefined} from "ionic-angular/es2015/util/util"; export class AccordionBlock implements OnInit, OnDestroy { @Input("value") - readonly observableAccordion: Observable; - - accordion: AccordionBlockModel; + readonly accordion: AccordionBlockModel; private expanded: boolean = false; - private accordionSubscription: Subscription | undefined = undefined; + private accordionSubscription?: Subscription; constructor( private readonly detectorRef: ChangeDetectorRef ) {} ngOnInit(): void { - - this.observableAccordion.subscribe(it => { - this.accordion = it; - this.expanded = it.expanded; - this.detectorRef.detectChanges(); - }); + this.expanded = this.accordion.expanded; + this.accordionSubscription = this.accordion.blocks.subscribe(_ => this.detectorRef.detectChanges()) } ngOnDestroy(): void { diff --git a/src/learnplace/directives/accordion/accordion.html b/src/learnplace/directives/accordion/accordion.html index 6c8b66e7..be100ae4 100644 --- a/src/learnplace/directives/accordion/accordion.html +++ b/src/learnplace/directives/accordion/accordion.html @@ -1,35 +1,35 @@ - + - + - - + + - {{accordion.title}} - + {{accordion.title}} + -
+
-
- - - - -
+
+ + + + +
-
+
-
+
- - {{"learnplace.block.accordion.too_far_away" | translate}} - + + {{"learnplace.block.accordion.too_far_away" | translate}} + diff --git a/src/learnplace/directives/linkblock/link-block.directive.ts b/src/learnplace/directives/linkblock/link-block.directive.ts index 319a0006..4cee4a6e 100644 --- a/src/learnplace/directives/linkblock/link-block.directive.ts +++ b/src/learnplace/directives/linkblock/link-block.directive.ts @@ -2,8 +2,8 @@ import {ChangeDetectorRef, Component, Inject, Input, OnDestroy, OnInit} from "@a import {LinkBlockModel} from "../../services/block.model"; import {LINK_BUILDER, LinkBuilder} from "../../../services/link/link-builder.service"; import { - OPEN_OBJECT_IN_ILIAS_ACTION_FACTORY, - OpenObjectInILIASAction + OPEN_OBJECT_IN_ILIAS_ACTION_FACTORY, + OpenObjectInILIASAction } from "../../../actions/open-object-in-ilias-action"; import {Builder} from "../../../services/builder.base"; import {ILIASObjectAction} from "../../../actions/object-action"; @@ -17,67 +17,78 @@ import {Subscription} from "rxjs/Subscription"; import {isDefined} from "ionic-angular/es2015/util/util"; @Component({ - selector: "link-block", - templateUrl: "link-block.html" + selector: "link-block", + templateUrl: "link-block.html" }) export class LinkBlock implements OnInit, OnDestroy { - @Input("value") - readonly observableLinkBlock: Observable; + @Input("value") + readonly link: LinkBlockModel; - link: LinkBlockModel | undefined = undefined; - linkLabel: string | undefined = undefined; - disableLink: boolean = false; + linkLabel: string | undefined = undefined; + disableLink: boolean = false; - private linkBlockSubscription: Subscription | undefined = undefined; + private linkBlockSubscription: Subscription | undefined = undefined; - private readonly log: Logger = Logging.getLogger(LinkBlock.name); + private readonly log: Logger = Logging.getLogger(LinkBlock.name); - constructor( - @Inject(LINK_BUILDER) private readonly linkBuilder: LinkBuilder, - private readonly translate: TranslateService, - @Inject(OPEN_OBJECT_IN_ILIAS_ACTION_FACTORY) - private readonly openInIliasActionFactory: (title: string, urlBuilder: Builder>) => OpenObjectInILIASAction, - private readonly detectorRef: ChangeDetectorRef - ){} + constructor( + @Inject(LINK_BUILDER) private readonly linkBuilder: LinkBuilder, + private readonly translate: TranslateService, + @Inject(OPEN_OBJECT_IN_ILIAS_ACTION_FACTORY) + private readonly openInIliasActionFactory: (title: string, urlBuilder: Builder>) => OpenObjectInILIASAction, + private readonly detectorRef: ChangeDetectorRef + ) { + } + + ngOnInit(): void { - ngOnInit(): void { + // this.linkBlockSubscription = this.observableLinkBlock.subscribe(it => { + // this.link = it; + // this.detectorRef.detectChanges(); + // }); - this.linkBlockSubscription = this.observableLinkBlock.subscribe(it => { - this.link = it; - this.detectorRef.detectChanges(); - }); + // because the ref id is immutable, we only want to read the objects title once + User.findActiveUser().then(user => { + return ILIASObject.findByRefId(this.link.refId, user.id); + }).then(obj => { + this.linkLabel = obj.title; + }).catch(_ => { + this.disableLink = true; + this.log.warn(() => `Could not load label for link block with refId: refId=${this.link.refId}`); + this.linkLabel = this.translate.instant("learnplace.block.link.no_access"); + }); - // because the ref id is immutable, we only want to read the objects title once - this.observableLinkBlock.first().subscribe(it => { - User.findActiveUser().then(user => { - return ILIASObject.findByRefId(it.refId, user.id); - }).then(obj => { - this.linkLabel = obj.title; - }).catch(_ => { - this.disableLink = true; - this.log.warn(() => `Could not load label for link block with refId: refId=${it.refId}`); - this.linkLabel = this.translate.instant("learnplace.block.link.no_access"); - }); - }); - } + // this.observableLinkBlock.first().subscribe(it => { + // + // User.findActiveUser().then(user => { + // return ILIASObject.findByRefId(it.refId, user.id); + // }).then(obj => { + // this.linkLabel = obj.title; + // }).catch(_ => { + // this.disableLink = true; + // this.log.warn(() => `Could not load label for link block with refId: refId=${it.refId}`); + // this.linkLabel = this.translate.instant("learnplace.block.link.no_access"); + // }); + // }); + } - ngOnDestroy(): void { - if(isDefined(this.linkBlockSubscription)) { - this.linkBlockSubscription.unsubscribe(); + ngOnDestroy(): void { + if (isDefined(this.linkBlockSubscription)) { + this.linkBlockSubscription.unsubscribe(); + } } - } - open(): void { + open(): void { - if(!this.disableLink) { - const action: ILIASObjectAction = this.openInIliasActionFactory( - this.translate.instant("actions.view_in_ilias"), - this.linkBuilder.default().target(this.link.refId) - ); + if (!this.disableLink) { + const action: ILIASObjectAction = this.openInIliasActionFactory( + this.translate.instant("actions.view_in_ilias"), + this.linkBuilder.default().target(this.link.refId) + ); - action.execute(); + action.execute(); + } } - } } diff --git a/src/learnplace/directives/pictureblock/pictureblock.directive.ts b/src/learnplace/directives/pictureblock/pictureblock.directive.ts index 9cbc03a7..2b098157 100644 --- a/src/learnplace/directives/pictureblock/pictureblock.directive.ts +++ b/src/learnplace/directives/pictureblock/pictureblock.directive.ts @@ -1,4 +1,4 @@ -import {ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from "@angular/core"; +import {Component, Input, OnDestroy, OnInit} from "@angular/core"; import {PictureBlockModel} from "../../services/block.model"; import {Platform} from "ionic-angular"; import {File} from "@ionic-native/file"; @@ -6,73 +6,53 @@ import {PhotoViewer, PhotoViewerOptions} from "@ionic-native/photo-viewer"; import {DomSanitizer, SafeUrl} from "@angular/platform-browser"; import {Logger} from "../../../services/logging/logging.api"; import {Logging} from "../../../services/logging/logging.service"; -import {Observable} from "rxjs/Observable"; -import {Subscription} from "rxjs/Subscription"; -import {isDefined} from "ionic-angular/es2015/util/util"; @Component({ - selector: "picture-block", - templateUrl: "picture-block.html" + selector: "picture-block", + templateUrl: "picture-block.html" }) -export class PictureBlock implements OnInit, OnDestroy { +export class PictureBlock implements OnInit { - @Input("value") - readonly observablePicture: Observable; + @Input("value") + readonly pictureBlock: PictureBlockModel; - pictureBlock: PictureBlockModel | undefined = undefined; + embeddedSrc?: SafeUrl; - embeddedSrc: SafeUrl | undefined = undefined; + private readonly log: Logger = Logging.getLogger(PictureBlock.name); - private pictureBlockSubscription: Subscription | undefined = undefined; - - private readonly log: Logger = Logging.getLogger(PictureBlock.name); - - constructor( - private readonly platform: Platform, - private readonly file: File, - private readonly photoViewer: PhotoViewer, - private readonly sanitizer: DomSanitizer, - private readonly detectorRef: ChangeDetectorRef - ) {} - - ngOnInit(): void { + constructor( + private readonly platform: Platform, + private readonly file: File, + private readonly photoViewer: PhotoViewer, + private readonly sanitizer: DomSanitizer + ) { + } - this.pictureBlockSubscription = this.observablePicture.subscribe(it => { - this.pictureBlock = it; - this.detectorRef.detectChanges(); - }); + ngOnInit(): void { - // because the thumbnail is immutable, we only use the first picture block to encode it - this.observablePicture.first().subscribe(it => { - const fileName: string = it.thumbnail.split("/").pop(); - const path: string = it.thumbnail.replace(fileName, ""); + const fileName: string = this.pictureBlock.thumbnail.split("/").pop(); + const path: string = this.pictureBlock.thumbnail.replace(fileName, ""); - this.file.readAsDataURL(`${this.getStorageLocation()}${path}`, fileName).then(data => { - this.embeddedSrc = this.sanitizer.bypassSecurityTrustUrl(data); - }).catch(error => { - this.log.warn(() => `Could not load thumbnail: url: ${it.thumbnail}`); - this.log.debug(() => `Thumbnail load error: ${JSON.stringify(error)}`); - }); - }); - } + this.file.readAsDataURL(`${this.getStorageLocation()}${path}`, fileName).then(data => { + this.embeddedSrc = this.sanitizer.bypassSecurityTrustUrl(data); + }).catch(error => { + this.log.warn(() => `Could not load thumbnail: url: ${this.pictureBlock.thumbnail}`); + this.log.debug(() => `Thumbnail load error: ${JSON.stringify(error)}`); + }); + } - ngOnDestroy(): void { - if(isDefined(this.pictureBlockSubscription)) - this.pictureBlockSubscription.unsubscribe(); - } + show(): void { + this.photoViewer.show(`${this.getStorageLocation()}${this.pictureBlock.url}`, this.pictureBlock.title, {share: false}); + } - show(): void { - this.photoViewer.show(`${this.getStorageLocation()}${this.pictureBlock.url}`, this.pictureBlock.title, {share:false}); - } + private getStorageLocation(): string { + if (this.platform.is("android")) { + return this.file.externalApplicationStorageDirectory; + } + if (this.platform.is("ios")) { + return this.file.dataDirectory; + } - private getStorageLocation(): string { - if (this.platform.is("android")) { - return this.file.externalApplicationStorageDirectory; + throw new Error("Unsupported platform. Can not return a storage location."); } - if (this.platform.is("ios")) { - return this.file.dataDirectory; - } - - throw new Error("Unsupported platform. Can not return a storage location."); - } } diff --git a/src/learnplace/directives/textblock/textblock.directive.ts b/src/learnplace/directives/textblock/textblock.directive.ts index 472731f0..12997972 100644 --- a/src/learnplace/directives/textblock/textblock.directive.ts +++ b/src/learnplace/directives/textblock/textblock.directive.ts @@ -11,9 +11,7 @@ import {isDefined} from "ionic-angular/es2015/util/util"; export class TextBlock implements OnInit, OnDestroy { @Input("value") - readonly observableTextBlock: Observable; - - textBlock: TextBlockModel | undefined = undefined; + readonly textBlock: TextBlockModel; private textBlockSubscription: Subscription | undefined = undefined; @@ -22,10 +20,10 @@ export class TextBlock implements OnInit, OnDestroy { ) {} ngOnInit(): void { - this.textBlockSubscription = this.observableTextBlock.subscribe(it => { - this.textBlock = it; - this.detectorRef.detectChanges(); - }) + // this.textBlockSubscription = this.observableTextBlock.subscribe(it => { + // this.textBlock = it; + // this.detectorRef.detectChanges(); + // }) } ngOnDestroy(): void { diff --git a/src/learnplace/directives/videoblock/videoblock.directive.ts b/src/learnplace/directives/videoblock/videoblock.directive.ts index b27cdc45..a7e16a1c 100644 --- a/src/learnplace/directives/videoblock/videoblock.directive.ts +++ b/src/learnplace/directives/videoblock/videoblock.directive.ts @@ -14,9 +14,7 @@ import {isDefined} from "ionic-angular/es2015/util/util"; export class VideoBlock implements OnInit, OnDestroy { @Input("value") - readonly observableVideoBlock: Observable; - - videoBlock: VideoBlockModel | undefined = undefined; + readonly videoBlock: VideoBlockModel; private videoBlockSubscription: Subscription | undefined = undefined; @@ -29,10 +27,10 @@ export class VideoBlock implements OnInit, OnDestroy { ngOnInit(): void { - this.videoBlockSubscription = this.observableVideoBlock.subscribe(it => { - this.videoBlock = it; - this.detectorRef.detectChanges(); - }) + // this.videoBlockSubscription = this.observableVideoBlock.subscribe(it => { + // this.videoBlock = it; + // this.detectorRef.detectChanges(); + // }) } ngOnDestroy(): void { diff --git a/src/learnplace/pages/content/content.component.ts b/src/learnplace/pages/content/content.component.ts index 9ead79c7..27f8dcf1 100644 --- a/src/learnplace/pages/content/content.component.ts +++ b/src/learnplace/pages/content/content.component.ts @@ -1,62 +1,39 @@ -import {Component, Inject, OnDestroy, OnInit} from "@angular/core"; +import {ChangeDetectorRef, Component, Inject, OnDestroy} from "@angular/core"; import {BlockModel} from "../../services/block.model"; import {BLOCK_SERVICE, BlockService} from "../../services/block.service"; import {AlertController, AlertOptions, NavParams} from "ionic-angular"; -import {AlertButton} from "ionic-angular/components/alert/alert-options"; import {TranslateService} from "ng2-translate"; -import {Logger} from "../../../services/logging/logging.api"; -import {Logging} from "../../../services/logging/logging.service"; import {Observable} from "rxjs/Observable"; @Component({ - templateUrl: "content.html" + templateUrl: "content.html" }) -export class ContentPage implements OnInit, OnDestroy { - - private readonly learnplaceId: number; - readonly title: string; - readonly blockList: Array> = []; - - private readonly log: Logger = Logging.getLogger(ContentPage.name); - - constructor( - @Inject(BLOCK_SERVICE) private readonly blockService: BlockService, - private readonly translate: TranslateService, - private readonly alert: AlertController, - params: NavParams - ) { - this.learnplaceId = params.get("learnplaceId"); - this.title = params.get("learnplaceName"); - } - - ngOnInit(): void { - this.blockService.getBlocks(this.learnplaceId) - .then(blocks => this.blockList.push(...blocks)) - .catch(error => { - this.log.warn(() => `Could not load content: error=${JSON.stringify(error)}`); - this.showAlert(this.translate.instant("something_went_wrong")); - }); - } - - - ngOnDestroy(): void { - this.blockService.shutdown(); - } - - private showAlert(message: string): void { - this.alert.create({ - title: message, - buttons: [ - { - text: this.translate.instant("close"), - role: "cancel" - } - ] - }).present(); - } +export class ContentPage implements OnDestroy { + + private readonly learnplaceId: number; + readonly title: string; + readonly blockList: Observable>; + + constructor( + @Inject(BLOCK_SERVICE) private readonly blockService: BlockService, + private readonly translate: TranslateService, + private readonly alert: AlertController, + private readonly detectorRef: ChangeDetectorRef, + params: NavParams + ) { + this.learnplaceId = params.get("learnplaceId"); + this.title = params.get("learnplaceName"); + + // we detect property changes, when a block list is emitted to update the UI with the new block list + this.blockList = this.blockService.getBlockList(this.learnplaceId).do(_ => this.detectorRef.detectChanges()); + } + + ngOnDestroy(): void { + this.blockService.shutdown(); + } } export interface ContentPageParams { - readonly learnplaceId: number; - readonly learnplaceName: string; + readonly learnplaceId: number; + readonly learnplaceName: string; } diff --git a/src/learnplace/pages/content/content.html b/src/learnplace/pages/content/content.html index 16a5ca43..f3c0ac81 100644 --- a/src/learnplace/pages/content/content.html +++ b/src/learnplace/pages/content/content.html @@ -6,7 +6,7 @@ -
+

{{"learnplace.block.no_content" | translate}}

@@ -14,13 +14,13 @@

{{"learnplace.block.no_content" | translate}}

-
+
- - - - - + + + + +
diff --git a/src/learnplace/services/block.model.ts b/src/learnplace/services/block.model.ts index 190b03b4..23403d83 100644 --- a/src/learnplace/services/block.model.ts +++ b/src/learnplace/services/block.model.ts @@ -7,7 +7,7 @@ import {VisibilityStrategyType} from "./visibility/visibility.strategy"; * Contains information to display a map. * * @author nmaerchy - * @version 2.0.0 + * @since 2.0.0 */ export class MapModel implements VisibilityAware { @@ -41,7 +41,7 @@ export class MapModel implements VisibilityAware { * Enumerator for all available block types. * * @author nmaerchy - * @version 1.0.0 + * @since 2.0.0 */ export enum BlockType { FEEDBACK, @@ -62,7 +62,7 @@ export enum BlockType { * Base class for all specific block types. Shares common attributes over all blocks. * * @author nmaerchy - * @version 1.3.0 + * @since 2.0.0 */ export class BlockModel implements VisibilityAware { @@ -87,7 +87,7 @@ export class BlockModel implements VisibilityAware { * Model class for a text block. * * @author nmaerchy - * @version 1.0.0 + * @since 2.0.0 */ export class TextBlockModel extends BlockModel { @@ -101,7 +101,7 @@ export class TextBlockModel extends BlockModel { * Model class for a picture block. * * @author nmaerchy - * @version 1.0.0 + * @since 2.0.0 */ export class PictureBlockModel extends BlockModel { @@ -118,7 +118,7 @@ export class PictureBlockModel extends BlockModel { * Model class for link block. * * @author nmaerchy - * @version 0.0.1 + * @since 2.0.0 */ export class LinkBlockModel extends BlockModel { @@ -132,7 +132,7 @@ export class LinkBlockModel extends BlockModel { * Model class for a video block * * @author nmaerchy - * @version 1.0.0 + * @since 2.0.0 */ export class VideoBlockModel extends BlockModel { @@ -146,7 +146,7 @@ export class VideoBlockModel extends BlockModel { * Model class for an accordion block * * @author nmaerchy - * @version 1.0.0 + * @since 2.0.0 */ export class AccordionBlockModel extends BlockModel { @@ -154,7 +154,7 @@ export class AccordionBlockModel extends BlockModel { sequence: number, readonly title: string, readonly expanded: boolean, - readonly blocks: Array>, + readonly blocks: Observable>, ) { super(sequence, false, BlockType.ACCORDION) } diff --git a/src/learnplace/services/block.service.ts b/src/learnplace/services/block.service.ts index 71bfc775..35482c0b 100644 --- a/src/learnplace/services/block.service.ts +++ b/src/learnplace/services/block.service.ts @@ -15,29 +15,36 @@ import {LinkblockEntity} from "../entity/linkblock.entity"; import {VideoBlockEntity} from "../entity/videoblock.entity"; import {Observable} from "rxjs/Observable"; import {USER_REPOSITORY, UserRepository} from "../../providers/repository/repository.user"; +import {Logger} from "../../services/logging/logging.api"; +import {Logging} from "../../services/logging/logging.service"; /** * Describes a service that can provide all block types of a single learnplace. * * @author nmaerchy - * @version 2.0.0 + * @since 2.0.0 */ export interface BlockService { /** - * Returns all block types related to the given {@code learnplaceObjectId}. - * The returned array is ordered by the {@link BlockModel#sequence} property. + * Returns all blocks related to the learnplace matching the given {@code learnplaceObjectId}. + * The blocks are ordered by the {@link BlockModel#sequence} property. * - * The returned array contains {@link Observable} for each block. + * The return blocks are wrapped in an {@link Observable}. The observable never completes, + * but can be stopped by the {@link BlockService#shutdown} method. * - * @param {number} learnplaceObjectId - ILIAS object id + * The returned observable emits a new ordered array every time when a visibility of a block changes. * - * @returns {Promise>>} an ordered array of observables for each block type + * @param {number} learnplaceObjectId - ILIAS object id of the laernplace + * + * @returns {Observable>} an ordered array wrapped in an observable + * + * @since 2.0.1 */ - getBlocks(learnplaceObjectId: number): Promise>> + getBlockList(learnplaceObjectId: number): Observable> /** - * Shutdown every depending or async task which can be occurred by the {@link BlockService#getBlocks} method. + * Shutdown every depending or async task which can be occurred by the {@link BlockService#getBlockList} method. */ shutdown(): void; } @@ -47,11 +54,13 @@ export const BLOCK_SERVICE: InjectionToken = new InjectionToken - * @version 2.0.1 + * @since 2.0.0 */ @Injectable() export class VisibilityManagedBlockService implements BlockService { + private readonly log: Logger = Logging.getLogger(VisibilityManagedBlockService.name); + constructor( @Inject(LEARNPLACE_REPOSITORY) private readonly learnplaceRepository: LearnplaceRepository, @Inject(USER_REPOSITORY) private readonly userRepository: UserRepository, @@ -60,40 +69,39 @@ export class VisibilityManagedBlockService implements BlockService { ) {} /** - * Returns an array of observables for each block of the learnplace - * matching the given {@code learnplaceObjectId}. + * Returns all blocks related to the learnplace matching the given {@code learnplaceObjectId}. + * The blocks are ordered by the {@link BlockModel#sequence} property. + * + * The return blocks are wrapped in an {@link Observable}. The observable never completes, + * but can be stopped by the {@link BlockService#shutdown} method. * - * The array is sorted by the {@code sequence} property of each block. + * The returned observable emits a new ordered array every time when a visibility of a block changes. * - * @param {number} learnplaceObjectId - ILIAS object id of the learnplace + * @param {number} learnplaceObjectId - ILIAS object id of the laernplace * - * @return {Promise>>} sorted array of observables of a specific block + * @returns {Observable>} an ordered array wrapped in an observable + * + * @since 2.0.1 */ - async getBlocks(learnplaceObjectId: number): Promise>> { + getBlockList(learnplaceObjectId: number): Observable> { const learnplaceEntity: Observable = Observable.fromPromise(this.userRepository.findAuthenticatedUser()) .mergeMap(user => this.learnplaceRepository.findByObjectIdAndUserId(learnplaceObjectId, user.get().id)) .map(it => it.get()) .do(it => this.strategyApplier.setLearnplace(it.id)); - return Observable.merge( - this.mapTextblocks(learnplaceEntity.mergeMap(it => Observable.of(...it.textBlocks))), - this.mapPictureBlocks(learnplaceEntity.mergeMap(it => Observable.of(...it.pictureBlocks))), - this.mapLinkBlocks(learnplaceEntity.mergeMap(it => Observable.of(...it.linkBlocks))), - this.mapVideoBlocks(learnplaceEntity.mergeMap(it => Observable.of(...it.videoBlocks))), - this.mapAccordionBlock(learnplaceEntity.mergeMap(it => Observable.of(...it.accordionBlocks))) - ).toArray().toPromise(); - + return learnplaceEntity.mergeMap(it => { + this.log.trace(() => `Map blocks of learnplace: id=${it.id}`); - // return [ - // ...this.mapTextblocks(learnplace.textBlocks), - // ...this.mapPictureBlocks(learnplace.pictureBlocks), - // ...this.mapLinkBlocks(learnplace.linkBlocks), - // ...this.mapVideoBlocks(learnplace.videoBlocks), - // ...this.mapAccordionBlock(learnplace.accordionBlocks) - // ].sort((a, b) => a[0] - b[0]) - // .map(it => it[1]); + return Observable.combineLatest( + ...this.mapTextblocks(it.textBlocks), + ...this.mapPictureBlocks(it.pictureBlocks), + ...this.mapLinkBlocks(it.linkBlocks), + ...this.mapVideoBlocks(it.videoBlocks), + ...this.mapAccordionBlock(it.accordionBlocks) + ).map(it => it.sort((a, b) => a.sequence - b.sequence)) + }); } /** @@ -103,108 +111,62 @@ export class VisibilityManagedBlockService implements BlockService { this.strategyApplier.shutdown(); } - private mapTextblocks(textBlocks: Observable): Observable> { + private mapTextblocks(textBlockList: Array): Array> { - return textBlocks + return textBlockList .map(it => { const model: TextBlockModel = new TextBlockModel(it.sequence, this.sanitizer.bypassSecurityTrustHtml(it.content)); return this.strategyApplier.apply(model, VisibilityStrategyType[it.visibility.value]); }); - - // - // - // return textBlocks.map<[number, Observable]>(it => { - // const model: TextBlockModel = new TextBlockModel(it.sequence, this.sanitizer.bypassSecurityTrustHtml(it.content)); - // const observable: Observable = this.strategyApplier.apply(model, VisibilityStrategyType[it.visibility.value]); - // return [it.sequence, observable]; - // }); } - private mapPictureBlocks(pictureBlocks: Observable): Observable> { + private mapPictureBlocks(pictureBlockList: Array): Array> { - return pictureBlocks + return pictureBlockList .map(it => { const model: PictureBlockModel = new PictureBlockModel(it.sequence, it.title, it.description, it.thumbnail, it.url); return this.strategyApplier.apply(model, VisibilityStrategyType[it.visibility.value]); }); - - - // return pictureBlocks.map<[number, Observable]>(it => { - // const model: PictureBlockModel = new PictureBlockModel(it.sequence, it.title, it.description, it.thumbnail, it.url); - // const observable: Observable = this.strategyApplier.apply(model, VisibilityStrategyType[it.visibility.value]); - // return [it.sequence, observable]; - // }); } - private mapLinkBlocks(linkBlocks: Observable): Observable> { + private mapLinkBlocks(linkBlockList: Array): Array> { - return linkBlocks + return linkBlockList .map(it => { const model: LinkBlockModel = new LinkBlockModel(it.sequence, it.refId); return this.strategyApplier.apply(model, VisibilityStrategyType[it.visibility.value]); }); - - - // return linkBlocks.map<[number, Observable]>(it => { - // const model: LinkBlockModel = new LinkBlockModel(it.sequence, it.refId); - // const observable: Observable = this.strategyApplier.apply(model, VisibilityStrategyType[it.visibility.value]); - // return [it.sequence, observable]; - // }) } - private mapVideoBlocks(videoBlocks: Observable): Observable> { + private mapVideoBlocks(videoBlockList: Array): Array> { - return videoBlocks + return videoBlockList .map(it => { const model: VideoBlockModel = new VideoBlockModel(it.sequence, it.url); return this.strategyApplier.apply(model, VisibilityStrategyType[it.visibility.value]); }); - - - // return videoBlocks.map<[number, Observable]>(it => { - // const model: VideoBlockModel = new VideoBlockModel(it.sequence, it.url); - // const observable: Observable = this.strategyApplier.apply(model, VisibilityStrategyType[it.visibility.value]); - // return [it.sequence, observable]; - // }) } - private mapAccordionBlock(accordionBlocks: Observable): Observable> { + private mapAccordionBlock(accordionBlockList: Array): Array> { - const blockLists: Observable>> = accordionBlocks.mergeMap(it => Observable.merge( - this.mapTextblocks(Observable.of(...it.textBlocks)), - this.mapPictureBlocks(Observable.of(...it.pictureBlocks)), - this.mapLinkBlocks(Observable.of(...it.linkBlocks)), - this.mapVideoBlocks(Observable.of(...it.videoBlocks)) - ).toArray>() - ); + return accordionBlockList + .map(accordion => { - return Observable.forkJoin(blockLists, accordionBlocks, (blockList, accordion) => { + const blockList: Observable> = Observable.combineLatest( + ...this.mapTextblocks(accordion.textBlocks), + ...this.mapPictureBlocks(accordion.pictureBlocks), + ...this.mapLinkBlocks(accordion.linkBlocks), + ...this.mapVideoBlocks(accordion.videoBlocks) + ).map(it => it.sort((a, b) => a.sequence - b.sequence)); - const model: AccordionBlockModel = new AccordionBlockModel( - accordion.sequence, - accordion.title, - accordion.expanded, - blockList - ); - - return this.strategyApplier.apply(model, VisibilityStrategyType[accordion.visibility.value]); - }); + const model: AccordionBlockModel = new AccordionBlockModel( + accordion.sequence, + accordion.title, + accordion.expanded, + blockList + ); - // return accordions.map<[number, Observable]>(it => { - // const model: AccordionBlockModel = new AccordionBlockModel( - // it.sequence, - // it.title, - // it.expanded, - // [ - // ...this.mapTextblocks(it.textBlocks), - // ...this.mapPictureBlocks(it.pictureBlocks), - // ...this.mapLinkBlocks(it.linkBlocks), - // ...this.mapVideoBlocks(it.videoBlocks) - // ].sort((a, b) => a[0] - b[0]) - // .map(it => it[1]) - // ); - // const observable: Observable = this.strategyApplier.apply(model, VisibilityStrategyType[it.visibility.value]); - // return [it.sequence, observable]; - // }) + return this.strategyApplier.apply(model, VisibilityStrategyType[accordion.visibility.value]); + }) } } From df3bb0839e4063d7647fc2de236d214c19bd31db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20Scha=CC=88fli?= Date: Wed, 16 May 2018 10:48:58 +0200 Subject: [PATCH 06/10] Add course and group reference translation --- src/assets/i18n/de.json | 1 + src/assets/i18n/en.json | 2 ++ src/assets/i18n/it.json | 2 ++ 3 files changed, 5 insertions(+) diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 40558c1a..7f0d2a32 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -158,6 +158,7 @@ "object_type": { "crs" : "Kurs", "grp" : "Gruppe", + "grpr" : "Gruppe-Referenz", "frm" : "Forum", "fold": "Ordner", "sess": "Sitzung", diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 1019cb11..9cd8f8b6 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -154,7 +154,9 @@ }, "object_type": { "crs" : "Course", + "crsr": "Course Reference", "grp" : "Group", + "grpr" : "Group Reference", "frm" : "Forum", "fold": "Folder", "sess": "Session", diff --git a/src/assets/i18n/it.json b/src/assets/i18n/it.json index c74dc876..d1d31dea 100644 --- a/src/assets/i18n/it.json +++ b/src/assets/i18n/it.json @@ -151,7 +151,9 @@ }, "object_type": { "crs" : "Corso", + "crsr": "Riferimento del corso", "grp" : "Gruppo", + "grpr" : "Riferimento di gruppo", "frm" : "Forum", "fold": "Cartella", "sess": "Sessione", From 8aa7b0ed5ba091a918448ff38f383185518f875f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20Scha=CC=88fli?= Date: Wed, 16 May 2018 14:13:27 +0200 Subject: [PATCH 07/10] Add missing Individual Assessment lang var --- src/assets/i18n/de.json | 1 + src/assets/i18n/en.json | 1 + src/assets/i18n/it.json | 1 + 3 files changed, 3 insertions(+) diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 7f0d2a32..4f31b082 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -175,6 +175,7 @@ "lm" : "Lernmodul", "htlm" : "Lernmodul", "sahs" : "Lernmodul", + "iass" : "Individuelle Bewertung", "glo" : "Glossar", "dcl" : "Datensammlung", "bibl" : "Literaturliste", diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 9cd8f8b6..2fede0d4 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -172,6 +172,7 @@ "lm" : "Learning Module", "htlm" : "Learning Module", "sahs" : "Learning Module", + "iass" : "Individual Assessment", "glo" : "Glossary", "dcl" : "Data Collection", "bibl" : "Bibliography", diff --git a/src/assets/i18n/it.json b/src/assets/i18n/it.json index d1d31dea..017947ea 100644 --- a/src/assets/i18n/it.json +++ b/src/assets/i18n/it.json @@ -169,6 +169,7 @@ "lm" : "Modulo", "htlm" : "Modulo HTML", "sahs" : "Learning Module", + "iass" : "Valutazione individuale", "glo" : "Glossario", "dcl" : "Data Collection", "bibl" : "Bibliografia", From e84a7951cd3ee461dea07bdc8671d3aa327dc35c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20Scha=CC=88fli?= Date: Wed, 16 May 2018 19:40:38 +0200 Subject: [PATCH 08/10] Add translation fallback handling for unknown keys --- src/app/app.module.ts | 6 ++-- .../language/translation-missing-handler.ts | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 src/services/language/translation-missing-handler.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 5dc1afb5..65ac768f 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -15,7 +15,7 @@ import {StatusBar} from "@ionic-native/status-bar"; import {StreamingMedia} from "@ionic-native/streaming-media"; import {Toast} from "@ionic-native/toast"; import {IonicApp, IonicErrorHandler, IonicModule, ModalController, NavController, Platform} from "ionic-angular"; -import {TranslateModule, TranslateService} from "ng2-translate/ng2-translate"; +import {TranslateModule, TranslateService, MissingTranslationHandler} from "ng2-translate/ng2-translate"; import {TranslateLoader, TranslateStaticLoader} from "ng2-translate/src/translate.service"; import {OPEN_LEARNPLACE_ACTION_FACTORY, OpenLearnplaceAction, OpenLearnplaceActionFunction} from "../actions/open-learnplace-action"; import {OPEN_OBJECT_IN_ILIAS_ACTION_FACTORY, OpenObjectInILIASAction} from "../actions/open-object-in-ilias-action"; @@ -87,6 +87,7 @@ import {DiagnosticUtil} from "../services/device/hardware-features/diagnostics.u import {Hardware} from "../services/device/hardware-features/hardware-feature.service"; import {FileService} from "../services/file.service"; import {FooterToolbarService} from "../services/footer-toolbar.service"; +import {PegasusMissingTranslationHandler} from "../services/language/translation-missing-handler"; import {DEFAULT_LINK_BUILDER, DefaultLinkBuilder, DefaultLinkBuilderImpl} from "../services/link/default.builder"; import {LINK_BUILDER, LinkBuilderImpl} from "../services/link/link-builder.service"; import { @@ -459,7 +460,8 @@ import {HTTP} from "@ionic-native/http"; IonicErrorHandler, {provide: ErrorHandler, useClass: PegasusErrorHandler}, - {provide: XhrFactory, useClass: PegasusXhrFactory, multi: false} + {provide: XhrFactory, useClass: PegasusXhrFactory, multi: false}, + {provide: MissingTranslationHandler, useClass: PegasusMissingTranslationHandler, multi: false} ], exports: [ TranslateModule diff --git a/src/services/language/translation-missing-handler.ts b/src/services/language/translation-missing-handler.ts new file mode 100644 index 00000000..027aa13d --- /dev/null +++ b/src/services/language/translation-missing-handler.ts @@ -0,0 +1,36 @@ +import {MissingTranslationHandler, MissingTranslationHandlerParams, TranslateService} from "ng2-translate"; +import {Injectable} from "@angular/core"; +import {Logger} from "../logging/logging.api"; +import {Logging} from "../logging/logging.service"; + +/** + * Fallback handler for translation. + * The handler will check if the missing translation is due to a unknown type which may happens with ILIAS plugins. + * If the key do not start with the "object_type" namespace the missing key is return as translation in order to + * indicate which variable is missing. + * + * It is not possible to translate a string within this handler due to the fact that the + * TranslationService is depending on the MissingTranslation handler. + * + * @author Nicolas Schaefli + * @version 1.0.0 + * @since v2.0.1 + */ +@Injectable() +export class PegasusMissingTranslationHandler extends MissingTranslationHandler { + + private static readonly OBJECT_TYPE_KEY: string = "object_type."; + private readonly log: Logger = Logging.getLogger(PegasusMissingTranslationHandler.name); + + handle(params: MissingTranslationHandlerParams): string { + + // check if we got a object_type which indicates that we got a unknown object from ILIAS most likely a plugin + if(params.key.startsWith(PegasusMissingTranslationHandler.OBJECT_TYPE_KEY) === true) { + this.log.warn(() => `No ILIAS object translation found for "${params.key}" fallback to "Object" translation.`); + return "Object"; + } + + this.log.warn(() => `Missing translation falling back to key "${params.key}"`); + return params.key; + } +} From f54a21959b71e3e47e443424c5f606237daa9c88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20Scha=CC=88fli?= Date: Wed, 16 May 2018 19:43:36 +0200 Subject: [PATCH 09/10] Clean up translation fallback service imports --- src/services/language/translation-missing-handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/language/translation-missing-handler.ts b/src/services/language/translation-missing-handler.ts index 027aa13d..247bdf0f 100644 --- a/src/services/language/translation-missing-handler.ts +++ b/src/services/language/translation-missing-handler.ts @@ -1,5 +1,5 @@ -import {MissingTranslationHandler, MissingTranslationHandlerParams, TranslateService} from "ng2-translate"; import {Injectable} from "@angular/core"; +import {MissingTranslationHandler, MissingTranslationHandlerParams} from "ng2-translate"; import {Logger} from "../logging/logging.api"; import {Logging} from "../logging/logging.service"; From b9fa534c4475796f4b6919315c80c6da0b7f9614 Mon Sep 17 00:00:00 2001 From: Jonathan Gerber Date: Tue, 5 Jun 2018 16:55:46 +0200 Subject: [PATCH 10/10] onboarding added, language file extended, full screen modals and some css adaptations --- src/actions/open-learnplace-action.ts | 3 +- src/app/app.component.ts | 15 +- src/app/app.html | 2 +- src/app/app.module.ts | 3 + src/app/fallback/loading/loading.html | 11 +- src/app/fallback/loading/loading.scss | 43 +++++ src/assets/i18n/de.json | 17 +- src/assets/i18n/en.json | 21 ++- src/assets/i18n/it.json | 22 ++- src/assets/icon/icon_more.svg | 16 ++ src/assets/icon/ios-lock.svg | 13 ++ src/assets/img/decentralized.svg | 203 ++++++++++++++++++++++ src/assets/img/stura.svg | 233 ++++++++++++++++++++++++++ src/pages/favorites/favorites.html | 11 +- src/pages/info/info.html | 14 +- src/pages/login/login.html | 2 +- src/pages/news/news.html | 2 +- src/pages/news/news.ts | 1 + src/pages/onboarding/onboarding.html | 72 ++++++++ src/pages/onboarding/onboarding.scss | 126 ++++++++++++++ src/pages/onboarding/onboarding.ts | 35 ++++ src/theme/variables.scss | 4 + 22 files changed, 848 insertions(+), 21 deletions(-) create mode 100644 src/assets/icon/icon_more.svg create mode 100644 src/assets/icon/ios-lock.svg create mode 100644 src/assets/img/decentralized.svg create mode 100644 src/assets/img/stura.svg create mode 100644 src/pages/onboarding/onboarding.html create mode 100644 src/pages/onboarding/onboarding.scss create mode 100644 src/pages/onboarding/onboarding.ts diff --git a/src/actions/open-learnplace-action.ts b/src/actions/open-learnplace-action.ts index dd68fe77..4e1cb10e 100644 --- a/src/actions/open-learnplace-action.ts +++ b/src/actions/open-learnplace-action.ts @@ -26,7 +26,8 @@ export class OpenLearnplaceAction extends ILIASObjectAction { async execute(): Promise { - const loadingPage: Modal = this.modalController.create(LoadingPage); + const loadingPage: Modal = this.modalController.create(LoadingPage,undefined, + { cssClass: "modal-fullscreen" }); await loadingPage.present(); try { await this.loader.load(this.learnplaceObjectId); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 1d0d5cd1..c10ee536 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -23,6 +23,7 @@ import {DB_MIGRATION, DBMigration} from "../services/migration/migration.api"; import {SynchronizationService} from "../services/synchronization.service"; import {LoadingPage} from "./fallback/loading/loading.component"; import getMessage = Logging.getMessage; +import { OnboardingPage } from "../pages/onboarding/onboarding"; @Component({ templateUrl: "app.html" @@ -39,6 +40,7 @@ export class MyApp { settingsPage: object = SettingsPage; infoPage: object = InfoPage; loginPage: object = LoginPage; + onboardingPage: object = OnboardingPage; newsPage: string = "NewsPage"; //needs to be string in order to get lazy loaded LoadingPage: object = LoadingPage; loggedIn: boolean = false; @@ -143,7 +145,7 @@ export class MyApp { */ openTestpage(): void { this.menu.close(); - this.nav.push(LoadingPage); + this.nav.push(OnboardingPage); } // presentLoading(): void { @@ -213,6 +215,7 @@ export class MyApp { this.configureDefaultTranslation(); this.rootPage = this.loginPage; + this.presentOnboardingModal(); } } @@ -295,4 +298,14 @@ export class MyApp { } }); } + + // tslint:disable-next-line:typedef + presentOnboardingModal() { + // tslint:disable-next-line:typedef + const onboardingModal = this.modal.create(OnboardingPage, + undefined, + { cssClass: "modal-fullscreen" } + ); + onboardingModal.present(); + } } diff --git a/src/app/app.html b/src/app/app.html index 74b87a31..411cd3b6 100644 --- a/src/app/app.html +++ b/src/app/app.html @@ -45,7 +45,7 @@ {{ 'settings.title' | translate:[]}} - diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 5dc1afb5..09d9d311 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -61,6 +61,7 @@ import { } from "../learnplace/services/visitjournal.service"; import {FavoritesPage} from "../pages/favorites/favorites"; import {InfoPage} from "../pages/info/info"; +import {OnboardingPage} from "../pages/onboarding/onboarding"; import {LoginPage} from "../pages/login/login"; import {ModalPage} from "../pages/modal/modal"; import {NewObjectsPage} from "../pages/new-objects/new-objects"; @@ -130,6 +131,7 @@ import {HTTP} from "@ionic-native/http"; SyncFinishedModal, ModalPage, LoadingPage, + OnboardingPage, /* from src/learnplace */ MapPage, @@ -176,6 +178,7 @@ import {HTTP} from "@ionic-native/http"; SyncFinishedModal, //NewsPage, LoadingPage, + OnboardingPage, /* from src/learnplace */ MapPage, diff --git a/src/app/fallback/loading/loading.html b/src/app/fallback/loading/loading.html index a1ee0d08..dffbfbbb 100644 --- a/src/app/fallback/loading/loading.html +++ b/src/app/fallback/loading/loading.html @@ -2,13 +2,18 @@ +
+
+
+
+
- + -

+

diff --git a/src/app/fallback/loading/loading.scss b/src/app/fallback/loading/loading.scss index 83766142..383d17dd 100644 --- a/src/app/fallback/loading/loading.scss +++ b/src/app/fallback/loading/loading.scss @@ -42,3 +42,46 @@ // 50% { padding-left: 50%; } 100% { margin-left: 150%; } } + +.custom-spinner { + margin: 100px auto 0; + width: 200px; + text-align: center; +} + +.custom-spinner > div { + width: 60px; + height: 60px; + // background-color: color($colors, primary, base); + background-image: url("/assets/img/molly_pony_white.svg"); + + border-radius: 100%; + display: inline-block; + -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both; + animation: sk-bouncedelay 1.4s infinite ease-in-out both; +} + +.custom-spinner .bounce1 { + -webkit-animation-delay: -0.32s; + animation-delay: -0.32s; +} + +.custom-spinner .bounce2 { + -webkit-animation-delay: -0.16s; + animation-delay: -0.16s; +} + +@-webkit-keyframes sk-bouncedelay { + 0%, 80%, 100% { -webkit-transform: scale(0) } + 40% { -webkit-transform: scale(1.0) } +} + +@keyframes sk-bouncedelay { + 0%, 80%, 100% { + -webkit-transform: scale(0); + transform: scale(0); + } 40% { + -webkit-transform: scale(1.0); + transform: scale(1.0); + } +} diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 4f31b082..a8de0a0b 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -114,7 +114,7 @@ "download_successful": "Datei wurde heruntergeladen", "cant_open_file": "Es wurde keine Applikation gefunden, die diesen Dateityp öffnen kann.", "server_not_reachable": "Sie haben keine Verbindung zum Server, versuchen Sie es später erneut oder loggen Sie sich erneut ein.", - "server_response_validation_error": "Es besteht ein Problem mit der Server Kommunikation, dies kann durch eine veraltete App Version verursacht werden." + "server_response_validation_error": "Es besteht ein Problem mit der Server Kommunikation, dies kann durch eine veraltete App Version verursacht werden. Falls der Fehler bestehen bleibt, versuche Dich auszulogen und neu einzulogen." }, "details" : { "last_update": "Letztes Update", @@ -240,5 +240,20 @@ "activate": "Standort aktivieren", "back": "Nope, ich will zurück" } + }, + "onboarding": { + "title1": "Mit ILIAS Pegasus kannst Du:", + "function1": "Deine ILIAS Kursunterlagen auf Deinem Smartphone nutzen.", + "function2": "Deine Kurse & Dateien offline verfügbar machen.", + "function3": "Dich einmal einloggen und bleibst dann immer eingeloggt.", + "title2": "Eine dezentrale App", + "concept1": "Die Pegasus App verbindet Dich mit der ILIAS Installation Deiner Institution, Uni oder Schule. Und nur mit dieser.", + "concept2": "Wenn Deine Institution noch nicht mitmacht, schreib uns doch an", + "title3":"Deine Daten sind sicher!", + "datasecurity1": "Deine Daten werden nur auf Deinem Gerät gespeichert. Die App erhebt keinerlei personenbezogene Daten. Wenn Du dies im Detail prüfen möchtest, findest Du hier die", + "datasecurity2": "Datenschutzerklärung", + "datasecurity3": "ILIAS Pegasus ist ein Open Source Projekt. Wenn Du Dich selbst überzeugen möchtest, was die App tut, dann kannst Du hier den Code einsehen:", + "next":"Weiter", + "start": "Los geht's" } } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 2fede0d4..d9d0a90c 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -68,7 +68,9 @@ }, "info": { "title": "Info", - "guide": "Guide" + "guide": "Guide", + "instructions1": "If you're on the Home page 'My Courses and Groups' you can mark the courses and groups as 'available Offline'. They are marked with a gray cloud. Once the files are synced, the cloud turns blue.", + "instructions2": "The synchronization will start automatically each time the app is started or if a course has been marked as Available Offline. Manual synchronization can also be started by dragging (Pull to Refresh) the 'My Courses and Groups' page." }, "logout": { "title": "Logout", @@ -110,7 +112,7 @@ "download_successful": "Download successful", "cant_open_file": "There's no application that could open this filetype.", "server_not_reachable": "We cannot reach the server, please try again later or try logging in again.", - "server_response_validation_error": "Server communication issue, please make sure to run the latest app and try again." + "server_response_validation_error": "Server communication issue, please make sure to run the latest app and try again. Try login out and log in again-" }, "details" : { "last_update": "Last update", @@ -232,5 +234,20 @@ "browser_no": "No. return!", "loading": "Learnplace", "loading_subtitle": "Hang on a minute. If the learnplace contains a lot of content, it may take some time to download." + }, + "onboarding": { + "title1": "Whith the help of ILIAS Pegasus you can:", + "function1": "access your ILIAS courses on your smartphone.", + "function2": "make your courses & files available offline.", + "function3": "log in once and stay logged in.", + "title2": "A decentralized app", + "concept1": "The Pegasus app connects you to the ILIAS installation of your institution, university or school.", + "concept2": "If your institution did not join yet, contact us", + "title3":"Your data is safe!", + "datasecurity1": "Your data will only be stored on your device. The app does not collect any personal information. If you want to check this in detail, you will find here the", + "datasecurity2": "privacy policy", + "datasecurity3": "ILIAS Pegasus is an open source project. If you want to see for yourself what the app does, then you can see the code here:", + "next":"next", + "start": "Let's go" } } diff --git a/src/assets/i18n/it.json b/src/assets/i18n/it.json index 017947ea..4a8a1fde 100644 --- a/src/assets/i18n/it.json +++ b/src/assets/i18n/it.json @@ -66,7 +66,10 @@ }, "info": { "title": "Informazioni", - "guide": "Guida" + "guide": "Guida", + "instructions1": "If you're on the Home page 'My Courses and Groups' you can mark the courses and groups as 'available Offline'. They are marked with a gray cloud. Once the files are synced, the cloud turns blue.", + "instructions2": "The synchronization will start automatically each time the app is started or if a course has been marked as Available Offline. Manual synchronization can also be started by dragging (Pull to Refresh) the 'My Courses and Groups' page." + }, "logout": { "title": "Disconnettersi", @@ -226,5 +229,20 @@ "browser_no": "Senza ritorno!", "loading": "luogo di apprendimento", "loading_subtitle": "Aspetta un attimo. Se il punto di apprendimento contiene molti contenuti, potrebbe essere necessario del tempo per il download." - } + }, + "onboarding": { + "title1": "Whith the help of ILIAS Pegasus you can:", + "function1": "access your ILIAS courses on your smartphone.", + "function2": "make your courses & files available offline.", + "function3": "log in once and stay logged in.", + "title2": "A decentralized app", + "concept1": "The Pegasus app connects you to the ILIAS installation of your institution, university or school.", + "concept2": "If your institution did not join yet, contact us", + "title3":"Your data is safe!", + "datasecurity1": "Your data will only be stored on your device. The app does not collect any personal information. If you want to check this in detail, you will find here the", + "datasecurity2": "privacy policy", + "datasecurity3": "ILIAS Pegasus is an open source project. If you want to see for yourself what the app does, then you can see the code here:", + "next":"next", + "start": "Let's go" + } } diff --git a/src/assets/icon/icon_more.svg b/src/assets/icon/icon_more.svg new file mode 100644 index 00000000..4e72c570 --- /dev/null +++ b/src/assets/icon/icon_more.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/src/assets/icon/ios-lock.svg b/src/assets/icon/ios-lock.svg new file mode 100644 index 00000000..bcb91cba --- /dev/null +++ b/src/assets/icon/ios-lock.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/src/assets/img/decentralized.svg b/src/assets/img/decentralized.svg new file mode 100644 index 00000000..a2b5976f --- /dev/null +++ b/src/assets/img/decentralized.svg @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/stura.svg b/src/assets/img/stura.svg new file mode 100644 index 00000000..7531f659 --- /dev/null +++ b/src/assets/img/stura.svg @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/favorites/favorites.html b/src/pages/favorites/favorites.html index 6a812916..be71e45e 100644 --- a/src/pages/favorites/favorites.html +++ b/src/pages/favorites/favorites.html @@ -1,7 +1,7 @@ - Favorites + {{ 'favorites.title' | translate:{value: null} }} + + + + +

{{ 'onboarding.title2' | translate:[]}}

+

+ {{ 'onboarding.concept1' | translate:[]}} +

+

+ {{ 'onboarding.concept2' | translate:[]}} + support@ilias-pegasus.de +

+ + +
+ + + +

{{ 'onboarding.title3' | translate:[]}}

+

+ {{ 'onboarding.datasecurity1' | translate:[]}} + {{ 'onboarding.datasecurity2' | translate:[]}} +

+

+ {{ 'onboarding.datasecurity3' | translate:[]}} +

+ Github +

+

+ +
+ + + + diff --git a/src/pages/onboarding/onboarding.scss b/src/pages/onboarding/onboarding.scss new file mode 100644 index 00000000..0f40968c --- /dev/null +++ b/src/pages/onboarding/onboarding.scss @@ -0,0 +1,126 @@ +// .onboarding { +// color:white; + +.decentral{ + width: 40%; + padding-top: 10px; + padding-left: 15%; + padding-right: 15%; + // margin: 10%; +} + +.logo{ + width:40%; + padding-left: 35%; + padding-right: 25%; +} +.lock{ + width:40%; + padding-left: 35%; + padding-right: 35%; +} + + .huge-header{ + padding-top: 5%; + padding-bottom: 5%; + font-weight: 800; + font-size: 1.8em; + color: white; + // color: color($colors, primary, base); + } + + .central-text{ + padding-left: 15%; + padding-right: 15%; + // padding-bottom: 6%; + font-weight: 600; + color: white; + + font-size: 0.8em; + } + + .flagtoflag{ + width: 80%; + margin: auto; + } + + .grid-text{ + // padding-left: 15%; + // padding-right: 15%; + // padding-bottom: 6%; + font-weight: 600; + + font-size: 0.8em; + padding-bottom: 6%; + text-align: left; + padding-right: 5%; + } + + .logo{ + width: 50%; + + // background-image: url(/assets/icon/icon_galliker.svg); + // background-size: 140%; + // background-repeat: no-repeat; + } + + .notification-badge{ + text-align: center; + position: absolute; + bottom: 27%; + left: 12%; + } + + .flag_1{ + // background-color: color($colors, secondary, base); + // border-radius: 50%; + // background-size: 80%; + + height:20vw; + width: 30vw; + margin: auto; + // margin-bottom: 5%; + -webkit-animation: wobble-hor-bottom 30s infinite both; + animation: wobble-hor-bottom 30s infinite both; + z-index: -100; + } + + .directions{ + width: 20%; + height: 10%; + } + +ion-slide { + align-items: flex-start; + color: white; +} + +ion-item { + margin-bottom: 5px; + font-size: 1.2em; + background-color: map-get($colors, primary); +} + +flash-card { + color: #000; + + img { + width: 70%; + height: 70vw; + } +} + +.start-slide { + justify-content: center; + align-items: center; + color:white; + + button { + // font-size: 1.3em; + // font-weight: bold; + } +} + + + +// } diff --git a/src/pages/onboarding/onboarding.ts b/src/pages/onboarding/onboarding.ts new file mode 100644 index 00000000..f1805048 --- /dev/null +++ b/src/pages/onboarding/onboarding.ts @@ -0,0 +1,35 @@ +import {Component, ViewChild} from "@angular/core"; +import {NavController, ViewController} from "ionic-angular"; +import {InAppBrowser} from "@ionic-native/in-app-browser"; + +/* + Generated class for the InfoPage page. + + See http://ionicframework.com/docs/v2/components/#navigation for more info on + Ionic pages and navigation. +*/ +@Component({ + templateUrl: "onboarding.html" +}) +export class OnboardingPage { + + @ViewChild('slides') slides: any; + + slideOptions: any; + + constructor(public navCtrl: NavController, + public viewCtrl: ViewController, + ) { + + } + + nextSlide(){ + this.slides.slideNext(); + } + + dismiss() { + this.viewCtrl.dismiss(); + } + + +} diff --git a/src/theme/variables.scss b/src/theme/variables.scss index c75c550b..4ff54021 100644 --- a/src/theme/variables.scss +++ b/src/theme/variables.scss @@ -96,6 +96,10 @@ $refresher-text-font-size: 1em; color:white; } + +.white-text{ + color: white; +} .blue-gradient-invert{ background-image: linear-gradient(to top right, #4a668b, #003c66)