Merge pull request #4338 from FinnStutzenstein/projector-message-slide

ProjectorMessageSlide and some renaming
This commit is contained in:
Emanuel Schütze 2019-02-14 13:41:02 +01:00 committed by GitHub
commit 34feac553b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 700 additions and 220 deletions

View File

@ -16,8 +16,8 @@ import {
import { HttpService } from './http.service';
import { SlideManager } from 'app/slides/services/slide-manager.service';
import { BaseModel } from 'app/shared/models/base/base-model';
import { BaseViewModel } from 'app/site/base/base-view-model';
import { ViewModelStoreService } from './view-model-store.service';
import { BaseProjectableViewModel } from 'app/site/base/base-projectable-view-model';
/**
* This service cares about Projectables being projected and manage all projection-related
@ -260,9 +260,15 @@ export class ProjectorService {
* @param element The projector element
* @returns the view model from the projector element
*/
public getViewModelFromProjectorElement<T extends BaseViewModel>(element: IdentifiableProjectorElement): T {
public getViewModelFromProjectorElement<T extends BaseProjectableViewModel>(
element: IdentifiableProjectorElement
): T {
this.assertElementIsMappable(element);
return this.viewModelStore.get<T>(element.name, element.id);
const viewModel = this.viewModelStore.get<T>(element.name, element.id);
if (!isProjectable(viewModel)) {
console.error('The view model is not projectable', viewModel, element);
}
return viewModel;
}
/**

View File

@ -138,6 +138,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
return viewMotion.getTitle();
}
};
viewMotion.getProjectorTitle = viewMotion.getAgendaTitle;
viewMotion.getAgendaTitleWithType = () => {
// Append the verbose name only, if not the special format 'Motion <identifier>' is used.
if (viewMotion.identifier) {

View File

@ -1,7 +1,7 @@
import { TestBed, inject } from '@angular/core/testing';
import { E2EImportsModule } from 'e2e-imports.module';
import { ProjectorMessageRepositoryService } from './projectormessage-repository.service';
import { ProjectorMessageRepositoryService } from './projector-message-repository.service';
describe('ProjectorMessageRepositoryService', () => {
beforeEach(() => {

View File

@ -4,9 +4,10 @@ import { BaseRepository } from '../base-repository';
import { Identifiable } from 'app/shared/models/base/identifiable';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
import { ProjectorMessage } from 'app/shared/models/core/projector-message';
import { ViewProjectorMessage } from 'app/site/projector/models/view-projectormessage';
import { ViewProjectorMessage } from 'app/site/projector/models/view-projector-message';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { TranslateService } from '@ngx-translate/core';
import { DataSendService } from 'app/core/core-services/data-send.service';
@Injectable({
providedIn: 'root'
@ -16,7 +17,8 @@ export class ProjectorMessageRepositoryService extends BaseRepository<ViewProjec
DS: DataStoreService,
mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
private translate: TranslateService
private translate: TranslateService,
private dataSend: DataSendService
) {
super(DS, mapperService, viewModelStoreService, ProjectorMessage);
}
@ -30,14 +32,16 @@ export class ProjectorMessageRepositoryService extends BaseRepository<ViewProjec
}
public async create(message: ProjectorMessage): Promise<Identifiable> {
throw new Error('TODO');
return await this.dataSend.createModel(message);
}
public async update(message: Partial<ProjectorMessage>, viewMessage: ViewProjectorMessage): Promise<void> {
throw new Error('TODO');
const update = viewMessage.projectormessage;
update.patchValues(message);
await this.dataSend.updateModel(update);
}
public async delete(viewMessage: ViewProjectorMessage): Promise<void> {
throw new Error('TODO');
await this.dataSend.deleteModel(viewMessage.projectormessage);
}
}

View File

@ -1,13 +1,10 @@
<h2 mat-dialog-title>
<span *ngIf="projectorElementBuildDescriptor.projectionDefaultName !== 'motions'" translate>Project {{ projectorElementBuildDescriptor.getTitle() }}?</span>
<span *ngIf="projectorElementBuildDescriptor.projectionDefaultName === 'motions'" translate>Project motion {{ projectorElementBuildDescriptor.getTitle() }}?</span>
<span translate>Project</span> {{ projectorElementBuildDescriptor.getTitle() }}?
</h2>
<mat-dialog-content>
<div
class="projectors"
<div class="projectors"
*ngFor="let projector of projectors"
[ngClass]="isProjectedOn(projector) ? 'projected' : ''"
>
[ngClass]="isProjectedOn(projector) ? 'projected' : ''">
<mat-checkbox [checked]="isProjectorSelected(projector)" (change)="toggleProjector(projector)">
{{ projector.name | translate }}
</mat-checkbox>

View File

@ -13,7 +13,7 @@
</div>
</div>
<div *ngFor="let slide of slides" class="content">
<div *ngFor="let slide of slides">
<os-slide-container [slideData]="slide" [scroll]="scroll" [scale]="scale"></os-slide-container>
</div>

View File

@ -51,14 +51,6 @@
}
}
}
.content {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 50px;
right: 50px;
}
#footer {
position: fixed;

View File

@ -1 +1,6 @@
<div id="slide" [ngStyle]="slideStyle"><ng-container #slide></ng-container></div>
<div
id="slide"
[ngClass]="{'content': slideOptions.scaleable || slideOptions.scrollable}"
[ngStyle]="slideStyle">
<ng-container #slide></ng-container>
</div>

View File

@ -1,6 +1,14 @@
#slide {
width: calc(100% - 100px);
width: 100%;
height: 100%;
&.content {
width: calc(100% - 100px);
margin-left: 50px;
margin-right: 50px;
}
}
::ng-deep #slide {
z-index: 5;
height: 100%;

View File

@ -88,7 +88,7 @@ export class SlideContainerComponent extends BaseComponent {
/**
* The current slideoptions.
*/
private slideOptions: SlideOptions = { scaleable: false, scrollable: false };
public slideOptions: SlideOptions = { scaleable: false, scrollable: false };
/**
* Styles for scaling and scrolling.

View File

@ -0,0 +1,11 @@
/**
* Helper to remove html tags from a string.
* CAUTION: It is just a basic "don't show distracting html tags in a
* preview", not an actual tested sanitizer!
*
* @param inputString
*/
export function stripHtmlTags(inputString: string): string {
const regexp = new RegExp(/<[^ ][^<>]*(>|$)/g);
return inputString.replace(regexp, '').trim();
}

View File

@ -10,6 +10,7 @@ import { DurationService } from 'app/core/ui-services/duration.service';
import { FileExportService } from 'app/core/ui-services/file-export.service';
import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
import { ViewCreateTopic } from '../../models/view-create-topic';
import { stripHtmlTags } from 'app/shared/utils/strip-html-tags';
/**
* Component for the agenda import list view.
@ -59,9 +60,9 @@ export class AgendaImportListComponent extends BaseImportListComponent<ViewCreat
return '';
}
if (input.length > 50) {
return this.stripHtmlTags(input.substring(0, 47)) + '...';
return stripHtmlTags(input.substring(0, 47)) + '...';
}
return this.stripHtmlTags(input);
return stripHtmlTags(input);
}
/**
@ -77,28 +78,15 @@ export class AgendaImportListComponent extends BaseImportListComponent<ViewCreat
return '';
}
if (input.length < 300) {
return this.stripHtmlTags(input);
return stripHtmlTags(input);
}
return (
this.stripHtmlTags(input.substring(0, 147)) +
stripHtmlTags(input.substring(0, 147)) +
' [...] ' +
this.stripHtmlTags(input.substring(input.length - 150, input.length))
stripHtmlTags(input.substring(input.length - 150, input.length))
);
}
/**
* Helper to remove html tags from a string.
* CAUTION: It is just a basic "don't show distracting html tags in a
* preview", not an actual tested sanitizer!
*
* @param inputString
* @returns a string without hatml tags
*/
private stripHtmlTags(inputString: string): string {
const regexp = new RegExp(/<[^ ][^<>]*(>|$)/g);
return inputString.replace(regexp, '').trim();
}
/**
* Triggers an example csv download
*/

View File

@ -6,4 +6,11 @@ import { BaseViewModel } from './base-view-model';
*/
export abstract class BaseProjectableViewModel extends BaseViewModel implements Projectable {
public abstract getSlide(): ProjectorElementBuildDeskriptor;
/**
* @returns the projector title used for managing projector elements.
*/
public getProjectorTitle = () => {
return this.getTitle();
};
}

View File

@ -8,6 +8,7 @@ import { BaseImportListComponent } from 'app/site/base/base-import-list';
import { MotionCsvExportService } from '../../services/motion-csv-export.service';
import { MotionImportService } from '../../services/motion-import.service';
import { ViewMotion } from '../../models/view-motion';
import { stripHtmlTags } from 'app/shared/utils/strip-html-tags';
/**
* Component for the motion import list view.
@ -43,9 +44,9 @@ export class MotionImportListComponent extends BaseImportListComponent<ViewMotio
*/
public getShortPreview(input: string): string {
if (input.length > 50) {
return this.stripHtmlTags(input.substring(0, 47)) + '...';
return stripHtmlTags(input.substring(0, 47)) + '...';
}
return this.stripHtmlTags(input);
return stripHtmlTags(input);
}
/**
@ -56,26 +57,15 @@ export class MotionImportListComponent extends BaseImportListComponent<ViewMotio
*/
public getLongPreview(input: string): string {
if (input.length < 300) {
return this.stripHtmlTags(input);
return stripHtmlTags(input);
}
return (
this.stripHtmlTags(input.substring(0, 147)) +
stripHtmlTags(input.substring(0, 147)) +
' [...] ' +
this.stripHtmlTags(input.substring(input.length - 150, input.length))
stripHtmlTags(input.substring(input.length - 150, input.length))
);
}
/**
* Helper to remove html tags from a string.
* CAUTION: It is just a basic "don't show distracting html tags in a
* preview", not an actual tested sanitizer!
* @param inputString
*/
private stripHtmlTags(inputString: string): string {
const regexp = new RegExp(/<[^ ][^<>]*(>|$)/g);
return inputString.replace(regexp, '').trim();
}
/**
* Triggers an example csv download
*/

View File

@ -7,6 +7,7 @@ import { BaseImportListComponent } from 'app/site/base/base-import-list';
import { ViewStatuteParagraph } from 'app/site/motions/models/view-statute-paragraph';
import { StatuteImportService } from 'app/site/motions/services/statute-import.service';
import { StatuteCsvExportService } from 'app/site/motions/services/statute-csv-export.service';
import { stripHtmlTags } from 'app/shared/utils/strip-html-tags';
/**
* Component for the statute paragraphs import list view.
@ -42,9 +43,9 @@ export class StatuteImportListComponent extends BaseImportListComponent<ViewStat
*/
public getShortPreview(input: string): string {
if (input.length > 50) {
return this.stripHtmlTags(input.substring(0, 47)) + '...';
return stripHtmlTags(input.substring(0, 47)) + '...';
}
return this.stripHtmlTags(input);
return stripHtmlTags(input);
}
/**
@ -55,26 +56,15 @@ export class StatuteImportListComponent extends BaseImportListComponent<ViewStat
*/
public getLongPreview(input: string): string {
if (input.length < 300) {
return this.stripHtmlTags(input);
return stripHtmlTags(input);
}
return (
this.stripHtmlTags(input.substring(0, 147)) +
stripHtmlTags(input.substring(0, 147)) +
' [...] ' +
this.stripHtmlTags(input.substring(input.length - 150, input.length))
stripHtmlTags(input.substring(input.length - 150, input.length))
);
}
/**
* Helper to remove html tags from a string.
* CAUTION: It is just a basic "don't show distracting html tags in a
* preview", not an actual tested sanitizer!
* @param inputString
*/
private stripHtmlTags(inputString: string): string {
const regexp = new RegExp(/<[^ ][^<>]*(>|$)/g);
return inputString.replace(regexp, '').trim();
}
/**
* Triggers an example csv download
*/

View File

@ -580,7 +580,7 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
}
],
projectionDefaultName: 'motions',
getTitle: () => this.identifier
getTitle: this.getAgendaTitle
};
}

View File

@ -1,12 +1,12 @@
import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { MatSnackBar } from '@angular/material';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { PromptService } from 'app/core/ui-services/prompt.service';
import { BaseViewComponent } from '../../../base/base-view';
import { MatSnackBar } from '@angular/material';
import { ViewCountdown } from '../../models/view-countdown';
import { CountdownRepositoryService } from 'app/core/repositories/projector/countdown-repository.service';
import { Countdown } from 'app/shared/models/core/countdown';

View File

@ -7,11 +7,14 @@
<div class="content-container" *ngIf="projector">
<div class="column-left">
<a [routerLink]="['/projector', projector.id]">
<!--<a [routerLink]="['/projector', projector.id]">
<div id="projector">
<os-projector [projector]="projector"></os-projector>
</div>
</a>
</a>-->
<div id="projector">
<os-projector [projector]="projector"></os-projector>
</div>
</div>
<div class="column-right" *osPerms="'core.can_manage_projector'">
<div class="control-group">
@ -75,7 +78,7 @@
<div *ngIf="countdowns.length">
<h4>
<span translate>Countdowns</span>
<button type="button" mat-icon-button disableRipple routerLink="/countdowns">
<button type="button" mat-icon-button disableRipple routerLink="/projectors/countdowns">
<mat-icon>edit</mat-icon>
</button>
</h4>
@ -92,16 +95,16 @@
<div *ngIf="messages.length">
<h4>
<span translate>Messages</span>
<button type="button" mat-icon-button disableRipple routerLink="/messages">
<button type="button" mat-icon-button disableRipple routerLink="/projectors/messages">
<mat-icon>edit</mat-icon>
</button>
</h4>
<mat-list>
<mat-list-item *ngFor="let message of messages; let i = index" [ngClass]="{'projected': isProjected(message)}">
<mat-list-item *ngFor="let message of messages" [ngClass]="{'projected': isProjected(message)}">
<button type="button" mat-icon-button (click)="project(message)">
<mat-icon>videocam</mat-icon>
</button>
<span translate>Message</span> {{ i + 1 }}
<span translate>{{ message.getPreview(40) }}</span>
</mat-list-item>
</mat-list>
</div>

View File

@ -16,8 +16,8 @@ import { moveItemInArray, CdkDragDrop } from '@angular/cdk/drag-drop';
import { ProjectorElement } from 'app/shared/models/core/projector';
import { SlideManager } from 'app/slides/services/slide-manager.service';
import { CountdownRepositoryService } from 'app/core/repositories/projector/countdown-repository.service';
import { ProjectorMessageRepositoryService } from 'app/core/repositories/projector/projectormessage-repository.service';
import { ViewProjectorMessage } from 'app/site/projector/models/view-projectormessage';
import { ProjectorMessageRepositoryService } from 'app/core/repositories/projector/projector-message-repository.service';
import { ViewProjectorMessage } from 'app/site/projector/models/view-projector-message';
import { ViewCountdown } from 'app/site/projector/models/view-countdown';
import { Projectable } from 'app/site/base/projectable';
import { CurrentListOfSpeakersSlideService } from '../../services/current-list-of-of-speakers-slide.service';
@ -121,7 +121,7 @@ export class ProjectorDetailComponent extends BaseViewComponent implements OnIni
const idElement = this.slideManager.getIdentifialbeProjectorElement(element);
const viewModel = this.projectorService.getViewModelFromProjectorElement(idElement);
if (viewModel) {
return viewModel.getTitle();
return viewModel.getProjectorTitle();
}
}

View File

@ -111,12 +111,12 @@
</div>
<mat-menu #ellipsisMenu="matMenu">
<button mat-menu-item routerLink="/projectors/countdowns">
<mat-icon>alarm</mat-icon>
<span translate>Countdowns</span>
</button>
<button mat-menu-item>
<button type="button" mat-menu-item routerLink="/projectors/messages">
<mat-icon>note</mat-icon>
<span translate>Projector messages</span>
</button>
<button type="button" mat-menu-item routerLink="/projectors/countdowns">
<mat-icon>alarm</mat-icon>
<span translate>Countdowns</span>
</button>
</mat-menu>

View File

@ -0,0 +1,83 @@
<os-head-bar [nav]="false" [goBack]="true" [mainButton]="true" (mainEvent)="onPlusButton()">
<!-- Title -->
<div class="title-slot">
<h2 translate>Messages</h2>
</div>
</os-head-bar>
<div class="head-spacer"></div>
<mat-card *ngIf="messageToCreate">
<mat-card-title translate>New message</mat-card-title>
<mat-card-content>
<form [formGroup]="createForm"
(keydown)="onKeyDownCreate($event)">
<p>
<editor formControlName="message" [init]="tinyMceSettings"></editor>
</p>
</form>
</mat-card-content>
<mat-card-actions>
<button mat-button (click)="create()">
<span translate>Save</span>
</button>
<button mat-button (click)="onCancelCreate()">
<span translate>Cancel</span>
</button>
</mat-card-actions>
</mat-card>
<mat-accordion class="os-card">
<mat-expansion-panel *ngFor="let message of messages" (opened)="openId = message.id"
(closed)="panelClosed(message)" [expanded]="openId === message.id" multiple="false">
<!-- Projector button and countdown description-->
<mat-expansion-panel-header>
<mat-panel-title>
<div class="header-container">
<div class="header-projector-button">
<os-projector-button [object]="message"></os-projector-button>
</div>
<div class="header-name">
{{ message.getPreview() }}
</div>
</div>
</mat-panel-title>
</mat-expansion-panel-header>
<form [formGroup]="updateForm"
*ngIf="editId === message.id"
(keydown)="onKeyDownUpdate($event)">
<h5 translate>Edit message</h5>
<p>
<editor formControlName="message" [init]="tinyMceSettings"></editor>
</p>
</form>
<ng-container *ngIf="editId !== message.id">
<div class="message" *ngIf="message.message" [innerHTML]="getSafeMessage(message)"></div>
<div *ngIf="!message.message" class="no-content" translate>No message</div>
</ng-container>
<mat-action-row>
<button *ngIf="editId !== message.id" mat-button class="on-transition-fade" (click)="onEditButton(message)"
mat-icon-button>
<mat-icon>edit</mat-icon>
</button>
<button *ngIf="editId === message.id" mat-button class="on-transition-fade" (click)="onCancelUpdate()"
mat-icon-button>
<mat-icon>close</mat-icon>
</button>
<button *ngIf="editId === message.id" mat-button class="on-transition-fade" (click)="onSaveButton(message)"
mat-icon-button>
<mat-icon>save</mat-icon>
</button>
<button mat-button class='on-transition-fade' (click)=onDeleteButton(message) mat-icon-button>
<mat-icon>delete</mat-icon>
</button>
</mat-action-row>
</mat-expansion-panel>
</mat-accordion>
<mat-card *ngIf="messages.length === 0">
<mat-card-content>
<div class="no-content" translate>No messages</div>
</mat-card-content>
</mat-card>

View File

@ -0,0 +1,42 @@
.head-spacer {
width: 100%;
height: 60px;
line-height: 60px;
text-align: right;
background: white; /* TODO: remove this and replace with theme */
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
}
mat-card {
margin-bottom: 20px;
}
.header-container {
display: grid;
grid-template-rows: auto;
grid-template-columns: 40px 1fr;
width: 100%;
> div {
grid-row-start: 1;
grid-row-end: span 1;
grid-column-end: span 2;
}
.header-projector-button {
grid-column-start: 1;
}
.header-name {
grid-column-start: 2;
padding: 10px;
}
}
.message {
text-align: center;
::ng-deep p {
margin: 0 0 10px;
}
}

View File

@ -1,7 +1,7 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { E2EImportsModule } from 'e2e-imports.module';
import { ProjectorMessageListComponent } from './projectormessage-list.component';
import { ProjectorMessageListComponent } from './projector-message-list.component';
describe('CountdownListComponent', () => {
let component: ProjectorMessageListComponent;

View File

@ -0,0 +1,192 @@
import { Component, OnInit } from '@angular/core';
import { Title, SafeHtml, DomSanitizer } from '@angular/platform-browser';
import { MatSnackBar } from '@angular/material';
import { FormGroup, FormBuilder } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';
import { BaseViewComponent } from '../../../base/base-view';
import { ProjectorMessage } from 'app/shared/models/core/projector-message';
import { ViewProjectorMessage } from '../../models/view-projector-message';
import { ProjectorMessageRepositoryService } from 'app/core/repositories/projector/projector-message-repository.service';
import { PromptService } from 'app/core/ui-services/prompt.service';
/**
* List view for the projector messages.
*/
@Component({
selector: 'os-projector-message-list',
templateUrl: './projector-message-list.component.html',
styleUrls: ['./projector-message-list.component.scss']
})
export class ProjectorMessageListComponent extends BaseViewComponent implements OnInit {
public messageToCreate: ProjectorMessage | null;
/**
* Source of the Data
*/
public messages: ViewProjectorMessage[] = [];
/**
* The current focussed formgroup
*/
public updateForm: FormGroup;
public createForm: FormGroup;
public openId: number | null;
public editId: number | null;
/**
*/
public constructor(
titleService: Title,
translate: TranslateService,
matSnackBar: MatSnackBar,
private repo: ProjectorMessageRepositoryService,
private formBuilder: FormBuilder,
private promptService: PromptService,
private santinizer: DomSanitizer
) {
super(titleService, translate, matSnackBar);
const form = {
message: ['']
};
this.createForm = this.formBuilder.group(form);
this.updateForm = this.formBuilder.group(form);
}
/**
* Init function.
*
* Sets the title and gets/observes messages from DataStore
*/
public ngOnInit(): void {
super.setTitle('Messages');
this.messages = this.repo.getViewModelList();
this.repo.getViewModelListObservable().subscribe(messages => (this.messages = messages));
}
public getSafeMessage(message: ViewProjectorMessage): SafeHtml {
return this.santinizer.bypassSecurityTrustHtml(message.message);
}
/**
* Add a new message.
*/
public onPlusButton(): void {
if (!this.messageToCreate) {
this.createForm.reset();
this.createForm.setValue({
message: ''
});
this.messageToCreate = new ProjectorMessage();
}
}
/**
* Handler when clicking on create to create a new statute paragraph
*/
public create(): void {
if (this.createForm.valid) {
this.messageToCreate.patchValues(this.createForm.value as ProjectorMessage);
this.repo.create(this.messageToCreate).then(() => {
this.messageToCreate = null;
}, this.raiseError);
}
}
/**
* Executed on edit button
* @param message
*/
public onEditButton(message: ViewProjectorMessage): void {
this.editId = message.id;
this.updateForm.setValue({
message: message.message
});
}
/**
* Saves the message
* @param message The message to save
*/
public onSaveButton(message: ViewProjectorMessage): void {
if (this.updateForm.valid) {
this.repo.update(this.updateForm.value as Partial<ProjectorMessage>, message).then(() => {
this.openId = this.editId = null;
}, this.raiseError);
}
}
/**
* Is executed, when the delete button is pressed
*
* @param message The message to delete
*/
public async onDeleteButton(message: ViewProjectorMessage): Promise<void> {
const content = this.translate.instant('Delete this message?');
if (await this.promptService.open('Are you sure?', content)) {
this.repo.delete(message).then(() => (this.openId = this.editId = null), this.raiseError);
}
}
/**
* Is executed when a mat-extension-panel is closed
*
* @param message the message in the panel
*/
public panelClosed(message: ViewProjectorMessage): void {
this.openId = null;
if (this.editId) {
this.onSaveButton(message);
}
}
/**
* clicking Shift and Enter will save automatically
* clicking Escape will cancel the process
*
* @param event has the code
*/
public onKeyDownCreate(event: KeyboardEvent): void {
if (event.key === 'Enter' && event.shiftKey) {
this.create();
}
if (event.key === 'Escape') {
this.onCancelCreate();
}
}
/**
* Cancels the current form action
*/
public onCancelCreate(): void {
this.messageToCreate = null;
}
/**
* clicking Shift and Enter will save automatically
* clicking Escape will cancel the process
*
* @param event has the code
*/
public onKeyDownUpdate(event: KeyboardEvent): void {
if (event.key === 'Enter' && event.shiftKey) {
const message = this.messages.find(x => x.id === this.editId);
this.onSaveButton(message);
}
if (event.key === 'Escape') {
this.onCancelUpdate();
}
}
/**
* Cancels the current form action
*/
public onCancelUpdate(): void {
this.editId = null;
}
}

View File

@ -1,9 +0,0 @@
<os-head-bar [nav]="false" [goBack]="true" [mainButton]="true" (mainEvent)="onPlusButton()">
<!-- Title -->
<div class="title-slot">
<h2 translate>Messages</h2>
</div>
</os-head-bar>
<div class="head-spacer"></div>
<p>TODO</p>

View File

@ -1,32 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { BaseViewComponent } from '../../../base/base-view';
import { MatSnackBar } from '@angular/material';
/**
* List view for the projector messages.
*/
@Component({
selector: 'os-projectormessage-list',
templateUrl: './projectormessage-list.component.html',
styleUrls: ['./projectormessage-list.component.scss']
})
export class ProjectorMessageListComponent extends BaseViewComponent implements OnInit {
public constructor(titleService: Title, translate: TranslateService, matSnackBar: MatSnackBar) {
super(titleService, translate, matSnackBar);
}
/**
* Init function.
*
* Sets the title and gets/observes messages from DataStore
*/
public ngOnInit(): void {
super.setTitle('Messages');
}
public onPlusButton(): void {}
}

View File

@ -2,22 +2,23 @@ import { BaseProjectableViewModel } from 'app/site/base/base-projectable-view-mo
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { ProjectorMessage } from 'app/shared/models/core/projector-message';
import { BaseViewModel } from 'app/site/base/base-view-model';
import { stripHtmlTags } from 'app/shared/utils/strip-html-tags';
export class ViewProjectorMessage extends BaseProjectableViewModel {
public static COLLECTIONSTRING = ProjectorMessage.COLLECTIONSTRING;
private _message: ProjectorMessage;
public get projctormessage(): ProjectorMessage {
public get projectormessage(): ProjectorMessage {
return this._message;
}
public get id(): number {
return this.projctormessage.id;
return this.projectormessage.id;
}
public get message(): string {
return this.projctormessage.message;
return this.projectormessage.message;
}
/**
@ -49,4 +50,13 @@ export class ViewProjectorMessage extends BaseProjectableViewModel {
getTitle: () => this.getTitle()
};
}
public getPreview(maxLength: number = 100): string {
const html = stripHtmlTags(this.message);
if (html.length > maxLength) {
return html.substring(0, maxLength) + ' ...';
} else {
return html;
}
}
}

View File

@ -3,7 +3,7 @@ import { Routes, RouterModule } from '@angular/router';
import { ProjectorListComponent } from './components/projector-list/projector-list.component';
import { ProjectorDetailComponent } from './components/projector-detail/projector-detail.component';
import { CountdownListComponent } from './components/countdown-list/countdown-list.component';
import { ProjectorMessageListComponent } from './components/projectormessage-list/projectormessage-list.component';
import { ProjectorMessageListComponent } from './components/projector-message-list/projector-message-list.component';
const routes: Routes = [
{

View File

@ -4,10 +4,10 @@ import { Countdown } from 'app/shared/models/core/countdown';
import { ProjectorMessage } from 'app/shared/models/core/projector-message';
import { ProjectorRepositoryService } from 'app/core/repositories/projector/projector-repository.service';
import { CountdownRepositoryService } from 'app/core/repositories/projector/countdown-repository.service';
import { ProjectorMessageRepositoryService } from 'app/core/repositories/projector/projectormessage-repository.service';
import { ProjectorMessageRepositoryService } from 'app/core/repositories/projector/projector-message-repository.service';
import { ViewProjector } from './models/view-projector';
import { ViewCountdown } from './models/view-countdown';
import { ViewProjectorMessage } from './models/view-projectormessage';
import { ViewProjectorMessage } from './models/view-projector-message';
export const ProjectorAppConfig: AppConfig = {
name: 'projector',

View File

@ -9,7 +9,7 @@ import { ClockSlideService } from './services/clock-slide.service';
import { ProjectorDataService } from './services/projector-data.service';
import { CurrentListOfSpeakersSlideService } from './services/current-list-of-of-speakers-slide.service';
import { CountdownListComponent } from './components/countdown-list/countdown-list.component';
import { ProjectorMessageListComponent } from './components/projectormessage-list/projectormessage-list.component';
import { ProjectorMessageListComponent } from './components/projector-message-list/projector-message-list.component';
@NgModule({
providers: [ClockSlideService, ProjectorDataService, CurrentListOfSpeakersSlideService],

View File

@ -41,7 +41,7 @@ export const allSlides: SlideManifest[] = [
{
slide: 'core/clock',
path: 'core/clock',
loadChildren: './slides/core/clock/core-clock-slide.module#CoreClockSlideModule',
loadChildren: './slides/core/clock/clock-slide.module#ClockSlideModule',
scaleable: false,
scrollable: false,
verboseName: 'Clock',
@ -51,13 +51,23 @@ export const allSlides: SlideManifest[] = [
{
slide: 'core/countdown',
path: 'core/countdown',
loadChildren: './slides/core/countdown/core-countdown-slide.module#CoreCountdownSlideModule',
loadChildren: './slides/core/countdown/countdown-slide.module#CountdownSlideModule',
scaleable: false,
scrollable: false,
verboseName: 'Countdown',
elementIdentifiers: ['name', 'id'],
canBeMappedToModel: true
},
{
slide: 'core/projector-message',
path: 'core/projector-message',
loadChildren: './slides/core/projector-message/projector-message-slide.module#ProjectorMessageSlideModule',
scaleable: false,
scrollable: false,
verboseName: 'Message',
elementIdentifiers: ['name', 'id'],
canBeMappedToModel: true
},
{
slide: 'agenda/current-list-of-speakers',
path: 'agenda/current-list-of-speakers',
@ -79,5 +89,15 @@ export const allSlides: SlideManifest[] = [
verboseName: 'Current list of speakers overlay',
elementIdentifiers: ['name', 'id'],
canBeMappedToModel: false
},
{
slide: 'assignments/assignment',
path: 'assignments/assignment',
loadChildren: './slides/assignments/assignment/assignment-slide.module#AssignmentSlideModule',
scaleable: true,
scrollable: true,
verboseName: 'Election',
elementIdentifiers: ['name', 'id'],
canBeMappedToModel: true
}
];

View File

@ -0,0 +1,3 @@
export interface AssignmentSlideData {
user: string;
}

View File

@ -0,0 +1,3 @@
<div *ngIf="data">
<h1>TODO</h1>
</div>

View File

@ -1,21 +1,21 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CoreCountdownSlideComponent } from './core-countdown-slide.component';
import { AssignmentSlideComponent } from './assignment-slide.component';
import { E2EImportsModule } from '../../../../e2e-imports.module';
describe('CoreCountdownSlideComponent', () => {
let component: CoreCountdownSlideComponent;
let fixture: ComponentFixture<CoreCountdownSlideComponent>;
describe('AssignmentSlideComponent', () => {
let component: AssignmentSlideComponent;
let fixture: ComponentFixture<AssignmentSlideComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
declarations: [CoreCountdownSlideComponent]
declarations: [AssignmentSlideComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CoreCountdownSlideComponent);
fixture = TestBed.createComponent(AssignmentSlideComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import { BaseSlideComponent } from 'app/slides/base-slide-component';
import { AssignmentSlideData } from './assignment-slide-data';
@Component({
selector: 'os-assignment-slide',
templateUrl: './assignment-slide.component.html',
styleUrls: ['./assignment-slide.component.scss']
})
export class AssignmentSlideComponent extends BaseSlideComponent<AssignmentSlideData> {
public constructor() {
super();
}
}

View File

@ -0,0 +1,13 @@
import { AssignmentSlideModule } from './assignment-slide.module';
describe('UsersUserSlideModule', () => {
let usersUserSlideModule: AssignmentSlideModule;
beforeEach(() => {
usersUserSlideModule = new AssignmentSlideModule();
});
it('should create an instance', () => {
expect(usersUserSlideModule).toBeTruthy();
});
});

View File

@ -0,0 +1,7 @@
import { NgModule } from '@angular/core';
import { makeSlideModule } from 'app/slides/base-slide-module';
import { AssignmentSlideComponent } from './assignment-slide.component';
@NgModule(makeSlideModule(AssignmentSlideComponent))
export class AssignmentSlideModule {}

View File

@ -0,0 +1,26 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ClockSlideComponent } from './clock-slide.component';
import { E2EImportsModule } from '../../../../e2e-imports.module';
describe('ClockSlideComponent', () => {
let component: ClockSlideComponent;
let fixture: ComponentFixture<ClockSlideComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
declarations: [ClockSlideComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ClockSlideComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,14 +1,16 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { BaseSlideComponent } from 'app/slides/base-slide-component';
import { ServertimeService } from 'app/core/core-services/servertime.service';
import { Subscription } from 'rxjs';
import { BaseSlideComponent } from 'app/slides/base-slide-component';
import { ServertimeService } from 'app/core/core-services/servertime.service';
@Component({
selector: 'os-core-clock-slide',
templateUrl: './core-clock-slide.component.html',
styleUrls: ['./core-clock-slide.component.scss']
selector: 'os-clock-slide',
templateUrl: './clock-slide.component.html',
styleUrls: ['./clock-slide.component.scss']
})
export class CoreClockSlideComponent extends BaseSlideComponent<{}> implements OnInit, OnDestroy {
export class ClockSlideComponent extends BaseSlideComponent<{}> implements OnInit, OnDestroy {
public time: string;
private servertimeSubscription: Subscription | null = null;

View File

@ -0,0 +1,13 @@
import { ClockSlideModule } from './clock-slide.module';
describe('ClockSlideModule', () => {
let clockSlideModule: ClockSlideModule;
beforeEach(() => {
clockSlideModule = new ClockSlideModule();
});
it('should create an instance', () => {
expect(clockSlideModule).toBeTruthy();
});
});

View File

@ -0,0 +1,7 @@
import { NgModule } from '@angular/core';
import { makeSlideModule } from 'app/slides/base-slide-module';
import { ClockSlideComponent } from './clock-slide.component';
@NgModule(makeSlideModule(ClockSlideComponent))
export class ClockSlideModule {}

View File

@ -1,13 +0,0 @@
import { CoreClockSlideModule } from './core-clock-slide.module';
describe('CoreClockSlideModule', () => {
let coreClockSlideModule: CoreClockSlideModule;
beforeEach(() => {
coreClockSlideModule = new CoreClockSlideModule();
});
it('should create an instance', () => {
expect(coreClockSlideModule).toBeTruthy();
});
});

View File

@ -1,7 +0,0 @@
import { NgModule } from '@angular/core';
import { makeSlideModule } from 'app/slides/base-slide-module';
import { CoreClockSlideComponent } from './core-clock-slide.component';
@NgModule(makeSlideModule(CoreClockSlideComponent))
export class CoreClockSlideModule {}

View File

@ -1,3 +0,0 @@
export interface CoreCountdownSlideData {
error: string;
}

View File

@ -1,14 +0,0 @@
import { Component } from '@angular/core';
import { BaseSlideComponent } from 'app/slides/base-slide-component';
import { CoreCountdownSlideData } from './core-countdown-slide-data';
@Component({
selector: 'os-core-countdown-slide',
templateUrl: './core-countdown-slide.component.html',
styleUrls: ['./core-countdown-slide.component.scss']
})
export class CoreCountdownSlideComponent extends BaseSlideComponent<CoreCountdownSlideData> {
public constructor() {
super();
}
}

View File

@ -1,13 +0,0 @@
import { CoreCountdownSlideModule } from './core-countdown-slide.module';
describe('CoreCountdownSlideModule', () => {
let coreCountdownSlideModule: CoreCountdownSlideModule;
beforeEach(() => {
coreCountdownSlideModule = new CoreCountdownSlideModule();
});
it('should create an instance', () => {
expect(CoreCountdownSlideModule).toBeTruthy();
});
});

View File

@ -1,7 +0,0 @@
import { NgModule } from '@angular/core';
import { makeSlideModule } from 'app/slides/base-slide-module';
import { CoreCountdownSlideComponent } from './core-countdown-slide.component';
@NgModule(makeSlideModule(CoreCountdownSlideComponent))
export class CoreCountdownSlideModule {}

View File

@ -0,0 +1,3 @@
export interface CountdownSlideData {
error: string;
}

View File

@ -1,21 +1,21 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CoreClockSlideComponent } from './core-clock-slide.component';
import { CountdownSlideComponent } from './countdown-slide.component';
import { E2EImportsModule } from '../../../../e2e-imports.module';
describe('CoreClockSlideComponent', () => {
let component: CoreClockSlideComponent;
let fixture: ComponentFixture<CoreClockSlideComponent>;
describe('CountdownSlideComponent', () => {
let component: CountdownSlideComponent;
let fixture: ComponentFixture<CountdownSlideComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
declarations: [CoreClockSlideComponent]
declarations: [CountdownSlideComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CoreClockSlideComponent);
fixture = TestBed.createComponent(CountdownSlideComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@ -0,0 +1,14 @@
import { Component } from '@angular/core';
import { BaseSlideComponent } from 'app/slides/base-slide-component';
import { CountdownSlideData } from './countdown-slide-data';
@Component({
selector: 'os-countdown-slide',
templateUrl: './countdown-slide.component.html',
styleUrls: ['./countdown-slide.component.scss']
})
export class CountdownSlideComponent extends BaseSlideComponent<CountdownSlideData> {
public constructor() {
super();
}
}

View File

@ -0,0 +1,13 @@
import { CountdownSlideModule } from './countdown-slide.module';
describe('CountdownSlideModule', () => {
let countdownSlideModule: CountdownSlideModule;
beforeEach(() => {
countdownSlideModule = new CountdownSlideModule();
});
it('should create an instance', () => {
expect(countdownSlideModule).toBeTruthy();
});
});

View File

@ -0,0 +1,7 @@
import { NgModule } from '@angular/core';
import { makeSlideModule } from 'app/slides/base-slide-module';
import { CountdownSlideComponent } from './countdown-slide.component';
@NgModule(makeSlideModule(CountdownSlideComponent))
export class CountdownSlideModule {}

View File

@ -0,0 +1,3 @@
export interface ProjectorMessageSlideData {
message: string;
}

View File

@ -0,0 +1,5 @@
<div id="background" *ngIf="data">
<div id="message">
<div [innerHTML]="trustHTML(data.data.message)"></div>
</div>
</div>

View File

@ -0,0 +1,29 @@
#background {
position: absolute;
left: 0;
top: 0;
background-color: rgba($color: #000000, $alpha: 0.5);
z-index: 10;
width: 100%;
height: 100%;
display: grid;
grid-template-rows: auto;
grid-template-columns: auto;
#message {
align-self: center;
width: 80%;
text-align: center;
justify-self: center;
border-radius: 0.2em;
background: #ffffff;
font-size: 2.75em;
padding: 0.2em 0;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
min-height: 20px;
::ng-deep p {
margin: 0 0 10px;
}
}
}

View File

@ -0,0 +1,26 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { E2EImportsModule } from '../../../../e2e-imports.module';
import { ProjectorMessageSlideComponent } from './projector-message-slide.component';
describe('ProjectorMessageSlideComponent', () => {
let component: ProjectorMessageSlideComponent;
let fixture: ComponentFixture<ProjectorMessageSlideComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
declarations: [ProjectorMessageSlideComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ProjectorMessageSlideComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,20 @@
import { Component } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { BaseSlideComponent } from 'app/slides/base-slide-component';
import { ProjectorMessageSlideData } from './projector-message-slide-data';
@Component({
selector: 'os-projector-message-slide',
templateUrl: './projector-message-slide.component.html',
styleUrls: ['./projector-message-slide.component.scss']
})
export class ProjectorMessageSlideComponent extends BaseSlideComponent<ProjectorMessageSlideData> {
public constructor(private sanitizer: DomSanitizer) {
super();
}
public trustHTML(html: string): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(html);
}
}

View File

@ -0,0 +1,13 @@
import { ProjectorMessageSlideModule } from './projector-message-slide.module';
describe('ProjectormessageSlideModule', () => {
let projectorMessageSlideModule: ProjectorMessageSlideModule;
beforeEach(() => {
projectorMessageSlideModule = new ProjectorMessageSlideModule();
});
it('should create an instance', () => {
expect(projectorMessageSlideModule).toBeTruthy();
});
});

View File

@ -0,0 +1,7 @@
import { NgModule } from '@angular/core';
import { makeSlideModule } from 'app/slides/base-slide-module';
import { ProjectorMessageSlideComponent } from './projector-message-slide.component';
@NgModule(makeSlideModule(ProjectorMessageSlideComponent))
export class ProjectorMessageSlideModule {}