Work on the presentation controls

This commit is contained in:
FinnStutzenstein 2019-04-29 13:39:02 +02:00 committed by Emanuel Schütze
parent efbce9b645
commit 469084a1b6
11 changed files with 164 additions and 82 deletions

View File

@ -5,9 +5,9 @@ import { Observable, BehaviorSubject } from 'rxjs';
import { WebsocketService } from 'app/core/core-services/websocket.service'; import { WebsocketService } from 'app/core/core-services/websocket.service';
import { ProjectorElement, Projector } from 'app/shared/models/core/projector'; import { ProjectorElement, Projector } from 'app/shared/models/core/projector';
export interface SlideData<T = { error?: string }> { export interface SlideData<T = { error?: string }, P extends ProjectorElement = ProjectorElement> {
data: T; data: T;
element: ProjectorElement; element: P;
error?: string; error?: string;
} }

View File

@ -0,0 +1,13 @@
import { ProjectorElement } from 'app/shared/models/core/projector';
export interface MediafileProjectorElement extends ProjectorElement {
// Images and Pdf
rotation?: 0 | 90 | 180 | 270;
// Images
fullscreen?: boolean;
// Pdf
page?: number;
zoom?: number; // 0 is normal, then +-1, +-2, ...
}

View File

@ -7,12 +7,17 @@ import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { BaseViewModelWithListOfSpeakers } from 'app/site/base/base-view-model-with-list-of-speakers'; import { BaseViewModelWithListOfSpeakers } from 'app/site/base/base-view-model-with-list-of-speakers';
import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers'; import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers';
export const IMAGE_MIMETYPES = ['image/png', 'image/jpeg', 'image/gif'];
export const FONT_MIMETYPES = ['font/ttf', 'font/woff', 'application/font-woff', 'application/font-sfnt'];
export const PDF_MIMETYPES = ['application/pdf'];
export interface MediafileTitleInformation { export interface MediafileTitleInformation {
title: string; title: string;
} }
export class ViewMediafile extends BaseViewModelWithListOfSpeakers<Mediafile> export class ViewMediafile extends BaseViewModelWithListOfSpeakers<Mediafile>
implements MediafileTitleInformation, Searchable { implements MediafileTitleInformation, Searchable {
public static COLLECTIONSTRING = Mediafile.COLLECTIONSTRING; public static COLLECTIONSTRING = Mediafile.COLLECTIONSTRING;
private _uploader: ViewUser; private _uploader: ViewUser;
@ -110,7 +115,7 @@ export class ViewMediafile extends BaseViewModelWithListOfSpeakers<Mediafile>
* @returns true or false * @returns true or false
*/ */
public isImage(): boolean { public isImage(): boolean {
return ['image/png', 'image/jpeg', 'image/gif'].includes(this.type); return IMAGE_MIMETYPES.includes(this.type);
} }
/** /**
@ -119,7 +124,7 @@ export class ViewMediafile extends BaseViewModelWithListOfSpeakers<Mediafile>
* @returns true or false * @returns true or false
*/ */
public isFont(): boolean { public isFont(): boolean {
return ['font/ttf', 'font/woff', 'application/font-woff', 'application/font-sfnt'].includes(this.type); return FONT_MIMETYPES.includes(this.type);
} }
/** /**
@ -128,7 +133,7 @@ export class ViewMediafile extends BaseViewModelWithListOfSpeakers<Mediafile>
* @returns true or false * @returns true or false
*/ */
public isPdf(): boolean { public isPdf(): boolean {
return ['application/pdf'].includes(this.type); return PDF_MIMETYPES.includes(this.type);
} }
/** /**

View File

@ -1,11 +1,18 @@
<h5 *ngIf="elements.length" translate>Presentation control</h5> <mat-expansion-panel *ngIf="elements.length">
<div *ngFor="let element of elements"> <mat-expansion-panel-header>
<span translate>Media controls</span>
</mat-expansion-panel-header>
<h5 translate>Presentation control</h5>
<div *ngFor="let element of elements">
{{ getMediafile(element).getTitle() }} {{ getMediafile(element).getTitle() }}
<span *ngIf="getMediafile(element).isImage()"> <span *ngIf="getMediafile(element).isImage()">
<button type="button" mat-icon-button (click)="imageFullscreen(element)"> <button type="button" *ngIf="!element.fullscreen" mat-icon-button (click)="fullscreen(element)">
<mat-icon>fullscreen</mat-icon> <mat-icon>fullscreen</mat-icon>
</button> </button>
<button type="button" mat-icon-button (click)="imageRotate(element)"> <button type="button" *ngIf="!!element.fullscreen" mat-icon-button (click)="fullscreen(element)">
<mat-icon>fullscreen_exit</mat-icon>
</button>
<button type="button" mat-icon-button (click)="rotate(element)">
<mat-icon>refresh</mat-icon> <mat-icon>refresh</mat-icon>
</button> </button>
</span> </span>
@ -18,17 +25,18 @@
</button> </button>
<!-- TODO: Use form for page number; use pdfSetPage then. --> <!-- TODO: Use form for page number; use pdfSetPage then. -->
Page {{ getPage(element) }}/{{ getMediafile(element).pages }} Page {{ getPage(element) }}/{{ getMediafile(element).pages }}
<button type="button" mat-icon-button (click)="pdfRotate(element)"> <button type="button" mat-icon-button (click)="rotate(element)">
<mat-icon>refresh</mat-icon> <mat-icon>refresh</mat-icon>
</button> </button>
<button type="button" mat-icon-button (click)="pdfZoom(element, 'in')"> <button type="button" mat-icon-button (click)="zoom(element, 'in')">
<mat-icon>zoom_in</mat-icon> <mat-icon>zoom_in</mat-icon>
</button> </button>
<button type="button" mat-icon-button (click)="pdfZoom(element, 'reset')"> <button type="button" mat-icon-button (click)="zoom(element, 'reset')">
<mat-icon>replay</mat-icon> <mat-icon>replay</mat-icon>
</button> </button>
<button type="button" mat-icon-button (click)="pdfZoom(element, 'out')"> <button type="button" mat-icon-button (click)="zoom(element, 'out')">
<mat-icon>zoom_out</mat-icon> <mat-icon>zoom_out</mat-icon>
</button> </button>
</span> </span>
</div> </div>
</mat-expansion-panel>

View File

@ -7,11 +7,11 @@ import { TranslateService } from '@ngx-translate/core';
import { BaseViewComponent } from 'app/site/base/base-view'; import { BaseViewComponent } from 'app/site/base/base-view';
import { Mediafile } from 'app/shared/models/mediafiles/mediafile'; import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
import { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service'; import { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service';
import { ProjectorElements, ProjectorElement } from 'app/shared/models/core/projector';
import { ProjectorService } from 'app/core/core-services/projector.service'; import { ProjectorService } from 'app/core/core-services/projector.service';
import { SlideManager } from 'app/slides/services/slide-manager.service'; import { SlideManager } from 'app/slides/services/slide-manager.service';
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile'; import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
import { ViewProjector } from '../../models/view-projector'; import { ViewProjector } from '../../models/view-projector';
import { MediafileProjectorElement } from 'app/site/mediafiles/models/mediafile-projector-element';
/** /**
* The presentation controls. * The presentation controls.
@ -38,7 +38,7 @@ export class PresentationControlComponent extends BaseViewComponent {
} }
// All mediafile elements. // All mediafile elements.
public elements: ProjectorElements = []; public elements: MediafileProjectorElement[] = [];
/** /**
* Constructor * Constructor
@ -66,7 +66,7 @@ export class PresentationControlComponent extends BaseViewComponent {
*/ */
private updateElements(): void { private updateElements(): void {
this.elements = this.projector.elements.filter(element => { this.elements = this.projector.elements.filter(element => {
if (element.name !== Mediafile.COLLECTIONSTRING && !element.id) { if (element.name !== Mediafile.COLLECTIONSTRING || !element.id) {
return false; return false;
} }
const mediafile = this.mediafileRepo.getViewModel(element.id); const mediafile = this.mediafileRepo.getViewModel(element.id);
@ -74,14 +74,14 @@ export class PresentationControlComponent extends BaseViewComponent {
}); });
} }
public getMediafile(element: ProjectorElement): ViewMediafile { public getMediafile(element: MediafileProjectorElement): ViewMediafile {
return this.mediafileRepo.getViewModel(element.id); return this.mediafileRepo.getViewModel(element.id);
} }
/** /**
* @returns the currently used page number (1 in case of unnumbered elements) * @returns the currently used page number (1 in case of unnumbered elements)
*/ */
public getPage(element: ProjectorElement): number { public getPage(element: MediafileProjectorElement): number {
return element.page || 1; return element.page || 1;
} }
@ -90,7 +90,7 @@ export class PresentationControlComponent extends BaseViewComponent {
* *
* @param element * @param element
*/ */
public pdfForward(element: ProjectorElement): void { public pdfForward(element: MediafileProjectorElement): void {
if (this.getPage(element) < this.getMediafile(element).pages) { if (this.getPage(element) < this.getMediafile(element).pages) {
this.pdfSetPage(element, this.getPage(element) + 1); this.pdfSetPage(element, this.getPage(element) + 1);
} }
@ -101,7 +101,7 @@ export class PresentationControlComponent extends BaseViewComponent {
* *
* @param element * @param element
*/ */
public pdfBackward(element: ProjectorElement): void { public pdfBackward(element: MediafileProjectorElement): void {
if (this.getPage(element) > 1) { if (this.getPage(element) > 1) {
this.pdfSetPage(element, this.getPage(element) - 1); this.pdfSetPage(element, this.getPage(element) - 1);
} }
@ -114,19 +114,47 @@ export class PresentationControlComponent extends BaseViewComponent {
* @param element * @param element
* @param page * @param page
*/ */
public pdfSetPage(element: ProjectorElement, page: number): void { public pdfSetPage(element: MediafileProjectorElement, page: number): void {
const idElement = this.slideManager.getIdentifialbeProjectorElement(element);
if (this.getMediafile(element).pages >= page) { if (this.getMediafile(element).pages >= page) {
idElement.page = page; element.page = page;
this.updateElement(element);
}
}
public zoom(element: MediafileProjectorElement, direction: 'in' | 'out' | 'reset'): void {
if (direction === 'reset') {
element.zoom = 0;
} else if (direction === 'in') {
element.zoom = (element.zoom || 0) + 1;
} else if (direction === 'out') {
element.zoom = (element.zoom || 0) - 1;
}
this.updateElement(element);
}
public fullscreen(element: MediafileProjectorElement): void {
element.fullscreen = !element.fullscreen;
this.updateElement(element);
}
public rotate(element: MediafileProjectorElement): void {
let rotation: 0 | 90 | 180 | 270 = element.rotation || 0;
if (rotation === 0) {
rotation = 90;
} else if (rotation === 90) {
rotation = 180;
} else if (rotation === 180) {
rotation = 270;
} else {
// 270
rotation = 0;
}
element.rotation = rotation;
this.updateElement(element);
}
private updateElement(element: MediafileProjectorElement): void {
const idElement = this.slideManager.getIdentifialbeProjectorElement(element);
this.projectorService.updateElement(this.projector.projector, idElement).then(null, this.raiseError); this.projectorService.updateElement(this.projector.projector, idElement).then(null, this.raiseError);
} }
}
public pdfZoom(element: ProjectorElement, direction: 'in' | 'out' | 'reset'): void {}
public pdfRotate(element: ProjectorElement): void {}
public imageFullscreen(element: ProjectorElement): void {}
public imageRotate(element: ProjectorElement): void {}
} }

View File

@ -264,13 +264,16 @@
</mat-expansion-panel> </mat-expansion-panel>
<!-- File display controls --> <!-- File display controls -->
<mat-expansion-panel> <!--<mat-expansion-panel>
<mat-expansion-panel-header> <mat-expansion-panel-header>
<span translate>Media controls</span> <span translate>Media controls</span>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<os-presentation-control [projector]="projector"> <os-presentation-control [projector]="projector">
</os-presentation-control> </os-presentation-control>
</mat-expansion-panel> </mat-expansion-panel>-->
<os-presentation-control [projector]="projector">
</os-presentation-control>
</mat-accordion> </mat-accordion>
</div> </div>
</div> </div>

View File

@ -2,17 +2,18 @@ import { Input } from '@angular/core';
import { ViewProjector } from 'app/site/projector/models/view-projector'; import { ViewProjector } from 'app/site/projector/models/view-projector';
import { SlideData } from 'app/core/core-services/projector-data.service'; import { SlideData } from 'app/core/core-services/projector-data.service';
import { ProjectorElement } from 'app/shared/models/core/projector';
/** /**
* Every slide has to extends this base class. It forces the slides * Every slide has to extends this base class. It forces the slides
* to have an input for the slidedata. * to have an input for the slidedata.
*/ */
export abstract class BaseSlideComponent<T extends object> { export abstract class BaseSlideComponent<T extends object, P extends ProjectorElement = ProjectorElement> {
/** /**
* Each slide must take slide data. * Each slide must take slide data.
*/ */
@Input() @Input()
public data: SlideData<T>; public data: SlideData<T, P>;
/** /**
* The projector where this slide is projected on. * The projector where this slide is projected on.

View File

@ -1,16 +1,18 @@
<div *ngIf="data"> <div *ngIf="data">
<div *ngIf="data.data.type == 'image/png'"> <div *ngIf="isImage">
<img [src]="url" alt=""/> <img [src]="url" alt="" [ngClass]="'rotate' + (data.element.rotation || 0)"/>
{{ data.element.fullscreen || false }}
</div> </div>
<div *ngIf="data.data.type == 'image/jpeg'"> <div *ngIf="isPdf">
<img [src]="url" alt=""/> {{ data.element.rotation || 0 }}
</div>
<div *ngIf="data.data.type == 'application/pdf'">
<pdf-viewer <pdf-viewer
[show-all]="false" [show-all]="false"
[original-size]="false" [original-size]="false"
[fit-to-page]="true"
[autoresize]="true" [autoresize]="true"
[page]="page" [page]="data.element.page || 1"
[zoom]="zoom"
[rotation]="data.element.rotation || 0"
[src]="url" [src]="url"
style="display: block;"></pdf-viewer> style="display: block;"></pdf-viewer>
</div> </div>

View File

@ -0,0 +1,12 @@
.rotate0 {
transform: rotate(0deg);
}
.rotate90 {
transform: rotate(90deg);
}
.rotate180 {
transform: rotate(180deg);
}
.rotate270 {
transform: rotate(270deg);
}

View File

@ -2,22 +2,32 @@ import { Component } from '@angular/core';
import { BaseSlideComponent } from 'app/slides/base-slide-component'; import { BaseSlideComponent } from 'app/slides/base-slide-component';
import { MediafileSlideData } from './mediafile-slide-data'; import { MediafileSlideData } from './mediafile-slide-data';
import { IMAGE_MIMETYPES, PDF_MIMETYPES } from 'app/site/mediafiles/models/view-mediafile';
import { MediafileProjectorElement } from 'app/site/mediafiles/models/mediafile-projector-element';
@Component({ @Component({
selector: 'os-mediafile-slide', selector: 'os-mediafile-slide',
templateUrl: './mediafile-slide.component.html', templateUrl: './mediafile-slide.component.html',
styleUrls: ['./mediafile-slide.component.scss'] styleUrls: ['./mediafile-slide.component.scss']
}) })
export class MediafileSlideComponent extends BaseSlideComponent<MediafileSlideData> { export class MediafileSlideComponent extends BaseSlideComponent<MediafileSlideData, MediafileProjectorElement> {
public constructor() {
super();
}
public get page(): string {
return this.data.element.page;
}
public get url(): string { public get url(): string {
return `${this.data.data.media_url_prefix}/${this.data.data.path}`; return `${this.data.data.media_url_prefix}/${this.data.data.path}`;
} }
public get zoom(): number {
return Math.pow(1.1, this.data.element.zoom || 0);
}
public get isImage(): boolean {
return IMAGE_MIMETYPES.includes(this.data.data.type);
}
public get isPdf(): boolean {
return PDF_MIMETYPES.includes(this.data.data.type);
}
public constructor() {
super();
}
} }

View File

@ -54,8 +54,8 @@ export class SlideManager {
return this.loadedSlideConfigurations[slideName]; return this.loadedSlideConfigurations[slideName];
} }
public getIdentifialbeProjectorElement(element: ProjectorElement): IdentifiableProjectorElement { public getIdentifialbeProjectorElement<P extends ProjectorElement>(element: P): IdentifiableProjectorElement & P {
const identifiableElement: IdentifiableProjectorElement = element as IdentifiableProjectorElement; const identifiableElement: IdentifiableProjectorElement & P = element as IdentifiableProjectorElement & P;
const identifiers = this.getManifest(element.name).elementIdentifiers.map(x => x); // map to copy. const identifiers = this.getManifest(element.name).elementIdentifiers.map(x => x); // map to copy.
identifiableElement.getIdentifiers = () => identifiers; identifiableElement.getIdentifiers = () => identifiers;
return identifiableElement; return identifiableElement;