From 5606082fda2cd0883df63eaca4a622730fef30f0 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 3 May 2018 04:17:58 -0500 Subject: [PATCH] Resource in step view (connects #668) (#827) --- src/app/_variables.scss | 3 + src/app/app.module.ts | 3 +- src/app/courses/courses.module.ts | 4 +- src/app/courses/courses.service.ts | 5 +- .../courses-step-view.component.html | 21 +++++- .../courses-step-view.component.ts | 10 ++- .../step-view-courses/courses-step-view.scss | 19 ++++++ .../view-courses/courses-view.component.ts | 2 +- src/app/resources/resources.module.ts | 4 +- .../resources-viewer.component.html | 15 +++++ .../resources-viewer.component.ts | 66 +++++++++++++++++++ .../view-resources/resources-viewer.scss | 14 ++++ src/styles.scss | 2 +- 13 files changed, 154 insertions(+), 14 deletions(-) create mode 100644 src/app/courses/step-view-courses/courses-step-view.scss create mode 100644 src/app/resources/view-resources/resources-viewer.component.html create mode 100644 src/app/resources/view-resources/resources-viewer.component.ts create mode 100644 src/app/resources/view-resources/resources-viewer.scss diff --git a/src/app/_variables.scss b/src/app/_variables.scss index 627e428c38..ff9c5e089b 100644 --- a/src/app/_variables.scss +++ b/src/app/_variables.scss @@ -30,6 +30,9 @@ $font-family: Roboto, sans-serif; $screen-md: 1000px; $screen-sm: 780px; +// Maximum full height of view container (avoiding vertical overflow) +$view-container-height: calc(100vh - 352px); + /* * Old Planet Material Colors * Saved in case we want to revert diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 7712e923e9..75f1b13629 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -19,6 +19,7 @@ import { NgxImgModule } from 'ngx-img'; import { environment } from '../environments/environment'; import { MatIconRegistry } from '@angular/material'; import { FeedbackService } from './feedback/feedback.service'; +import { ResourcesService } from './resources/resources.service'; @NgModule({ imports: [ @@ -36,7 +37,7 @@ import { FeedbackService } from './feedback/feedback.service'; AppComponent, PageNotFoundComponent ], providers: [ - CouchService, AuthService, UserService, ValidatorService, PlanetMessageService, MatIconRegistry, FeedbackService + CouchService, AuthService, UserService, ValidatorService, PlanetMessageService, MatIconRegistry, FeedbackService, ResourcesService ], bootstrap: [ AppComponent ] }) diff --git a/src/app/courses/courses.module.ts b/src/app/courses/courses.module.ts index b248079167..404ec51934 100644 --- a/src/app/courses/courses.module.ts +++ b/src/app/courses/courses.module.ts @@ -13,6 +13,7 @@ import { CoursesViewComponent } from './view-courses/courses-view.component'; import { CoursesStepComponent } from './add-courses/courses-step.component'; import { CoursesStepViewComponent } from './step-view-courses/courses-step-view.component'; import { CoursesService } from './courses.service'; +import { ResourcesViewerComponent } from '../resources/view-resources/resources-viewer.component'; @NgModule({ imports: [ @@ -30,7 +31,8 @@ import { CoursesService } from './courses.service'; CoursesRequestComponent, CoursesViewComponent, CoursesStepComponent, - CoursesStepViewComponent + CoursesStepViewComponent, + ResourcesViewerComponent ], providers: [ CoursesService diff --git a/src/app/courses/courses.service.ts b/src/app/courses/courses.service.ts index ff0bc01d1b..48608886ed 100644 --- a/src/app/courses/courses.service.ts +++ b/src/app/courses/courses.service.ts @@ -16,8 +16,9 @@ export class CoursesService { // Components call this to get details of one course. // If the id already matches what is stored on the service, return that. - requestCourse(courseId: string, opts: any = {}) { - if (courseId === this.course._id) { + // Or will get new version if forceLatest set to true + requestCourse({ courseId, forceLatest = false }, opts: any = {}) { + if (!forceLatest && courseId === this.course._id) { this.courseUpdated.next(this.course); } else { this.getCourse(courseId, opts); diff --git a/src/app/courses/step-view-courses/courses-step-view.component.html b/src/app/courses/step-view-courses/courses-step-view.component.html index 88d37c6724..a242486c20 100644 --- a/src/app/courses/step-view-courses/courses-step-view.component.html +++ b/src/app/courses/step-view-courses/courses-step-view.component.html @@ -5,13 +5,28 @@
-

{{stepDetail.stepTitle}}

+

Step {{stepNum}}: {{stepDetail.stepTitle}}

+ + Open resource new tab + {{stepNum}}/{{maxStep}}
-
- {{stepDetail.description}} +
+
{{stepDetail.description}}
+ +
diff --git a/src/app/courses/step-view-courses/courses-step-view.component.ts b/src/app/courses/step-view-courses/courses-step-view.component.ts index af36d7041c..e77fc4cc8d 100644 --- a/src/app/courses/step-view-courses/courses-step-view.component.ts +++ b/src/app/courses/step-view-courses/courses-step-view.component.ts @@ -5,7 +5,8 @@ import { Subject } from 'rxjs/Subject'; import { takeUntil } from 'rxjs/operators'; @Component({ - templateUrl: './courses-step-view.component.html' + templateUrl: './courses-step-view.component.html', + styleUrls: [ './courses-step-view.scss' ] }) export class CoursesStepViewComponent implements OnInit, OnDestroy { @@ -14,6 +15,7 @@ export class CoursesStepViewComponent implements OnInit, OnDestroy { stepNum = 0; stepDetail: any = { stepTitle: '', description: '' }; maxStep = 1; + resourceUrl = ''; constructor( private router: Router, @@ -29,7 +31,7 @@ export class CoursesStepViewComponent implements OnInit, OnDestroy { }); this.route.paramMap.pipe(takeUntil(this.onDestroy$)).subscribe((params: ParamMap) => { this.stepNum = +params.get('stepNum'); // Leading + forces string to number - this.coursesService.requestCourse(params.get('id')); + this.coursesService.requestCourse({ courseId: params.get('id') }); }); } @@ -47,4 +49,8 @@ export class CoursesStepViewComponent implements OnInit, OnDestroy { this.router.navigate([ '../../' ], { relativeTo: this.route }); } + setResourceUrl(resourceUrl: string) { + this.resourceUrl = resourceUrl; + } + } diff --git a/src/app/courses/step-view-courses/courses-step-view.scss b/src/app/courses/step-view-courses/courses-step-view.scss new file mode 100644 index 0000000000..955d5c8d65 --- /dev/null +++ b/src/app/courses/step-view-courses/courses-step-view.scss @@ -0,0 +1,19 @@ +:host { + + .view-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(10px, 1fr)); + grid-template-rows: 1fr; + grid-column-gap: 1rem; + + > * { + overflow-y: auto; + } + + } + + planet-resources-viewer * { + max-width: 100%; + } + +} diff --git a/src/app/courses/view-courses/courses-view.component.ts b/src/app/courses/view-courses/courses-view.component.ts index 603e070bed..219ed17c20 100644 --- a/src/app/courses/view-courses/courses-view.component.ts +++ b/src/app/courses/view-courses/courses-view.component.ts @@ -49,7 +49,7 @@ export class CoursesViewComponent implements OnInit, OnDestroy { ngOnInit() { this.coursesService.courseUpdated$.pipe(takeUntil(this.onDestroy$)).subscribe(course => this.courseDetail = course); this.route.paramMap.pipe(takeUntil(this.onDestroy$)).subscribe( - (params: ParamMap) => this.coursesService.requestCourse(params.get('id')), + (params: ParamMap) => this.coursesService.requestCourse({ courseId: params.get('id'), forceLatest: true }), error => console.log(error) ); } diff --git a/src/app/resources/resources.module.ts b/src/app/resources/resources.module.ts index 987bc5cd19..85fcc74b52 100644 --- a/src/app/resources/resources.module.ts +++ b/src/app/resources/resources.module.ts @@ -10,7 +10,6 @@ import { MaterialModule } from '../shared/material.module'; import { HttpClientModule, HttpClientJsonpModule } from '@angular/common/http'; import { ResourcesRatingComponent } from './rating-resources/resources-rating.component'; import { PlanetStackedBarComponent } from '../shared/planet-stacked-bar.component'; -import { ResourcesService } from './resources.service'; import { PlanetDialogsModule } from '../shared/dialogs/planet-dialogs.module'; @NgModule({ @@ -25,7 +24,6 @@ import { PlanetDialogsModule } from '../shared/dialogs/planet-dialogs.module'; HttpClientJsonpModule, PlanetDialogsModule ], - declarations: [ ResourcesComponent, ResourcesViewComponent, ResourcesAddComponent, ResourcesRatingComponent, PlanetStackedBarComponent ], - providers: [ ResourcesService ] + declarations: [ ResourcesComponent, ResourcesViewComponent, ResourcesAddComponent, ResourcesRatingComponent, PlanetStackedBarComponent ] }) export class ResourcesModule {} diff --git a/src/app/resources/view-resources/resources-viewer.component.html b/src/app/resources/view-resources/resources-viewer.component.html new file mode 100644 index 0000000000..b0a048fd7c --- /dev/null +++ b/src/app/resources/view-resources/resources-viewer.component.html @@ -0,0 +1,15 @@ +
+ + + + +
Open File
+ +
diff --git a/src/app/resources/view-resources/resources-viewer.component.ts b/src/app/resources/view-resources/resources-viewer.component.ts new file mode 100644 index 0000000000..f6ef3597ed --- /dev/null +++ b/src/app/resources/view-resources/resources-viewer.component.ts @@ -0,0 +1,66 @@ +import { Component, Input, OnChanges, OnDestroy, EventEmitter, Output } from '@angular/core'; + +import { DomSanitizer } from '@angular/platform-browser'; +import { environment } from '../../../environments/environment'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs/Subject'; +import { ResourcesService } from '../resources.service'; + +@Component({ + selector: 'planet-resources-viewer', + templateUrl: './resources-viewer.component.html', + styleUrls: [ './resources-viewer.scss' ] +}) +export class ResourcesViewerComponent implements OnChanges, OnDestroy { + + @Input() resourceId: string; + @Input() resource: any; + @Output() resourceUrl = new EventEmitter(); + mediaType: string; + contentType: string; + resourceSrc: string; + urlPrefix = environment.couchAddress + 'resources/'; + pdfSrc: any; + private onDestroy$ = new Subject(); + + constructor( + private sanitizer: DomSanitizer, + private resourcesService: ResourcesService + ) { } + + ngOnChanges() { + if (this.resource === undefined || this.resource._id !== this.resourceId) { + this.resourcesService.resourcesUpdated$.pipe(takeUntil(this.onDestroy$)) + .subscribe((resourceArr) => { + this.setResource(resourceArr[0]); + }); + this.resourcesService.updateResources({ resourceIds: [ this.resourceId ] }); + } else { + this.setResource(this.resource); + } + } + + ngOnDestroy() { + this.onDestroy$.next(); + this.onDestroy$.complete(); + } + + setResource(resource: any) { + this.resource = resource; + // openWhichFile is used to label which file to start with for HTML resources + const filename = resource.openWhichFile || Object.keys(resource._attachments)[0]; + this.mediaType = resource.mediaType; + this.contentType = resource._attachments[filename].content_type; + this.resourceSrc = this.urlPrefix + resource._id + '/' + filename; + if (!this.mediaType) { + const mediaTypes = [ 'image', 'pdf', 'audio', 'video', 'zip' ]; + this.mediaType = mediaTypes.find((type) => this.contentType.indexOf(type) > -1) || 'other'; + } + if (this.mediaType === 'pdf' || this.mediaType === 'HTML') { + this.pdfSrc = this.sanitizer.bypassSecurityTrustResourceUrl(this.resourceSrc); + } + // Emit resource src so parent component can use for links + this.resourceUrl.emit(this.resourceSrc); + } + +} diff --git a/src/app/resources/view-resources/resources-viewer.scss b/src/app/resources/view-resources/resources-viewer.scss new file mode 100644 index 0000000000..b5639ec992 --- /dev/null +++ b/src/app/resources/view-resources/resources-viewer.scss @@ -0,0 +1,14 @@ +@import '../../variables'; + +:host { + * { + max-width: 100%; + } + + iframe { + display: block; + border: none; + width: 100%; + height: $view-container-height; + } +} diff --git a/src/styles.scss b/src/styles.scss index b943eb7fca..7574be025e 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -300,7 +300,7 @@ body { // 352px = 5 * 64px toolbars + 32px of padding // Overflow must be set on children .view-container.view-full-height { - height: calc(100vh - 352px); + height: $view-container-height; } // Width of a form with 3 standard matInputs (180px + 10px of padding)