Merge pull request #4389 from FinnStutzenstein/chyron
current speaker chyron
This commit is contained in:
commit
23c1857fa6
@ -1,10 +1,10 @@
|
||||
<div id="container" [osResized]="resizeSubject" [ngStyle]="containerStyle" #container>
|
||||
<div id="projector" [ngStyle]="projectorStyle">
|
||||
<div id="header" [ngStyle]="headerFooterStyle" *ngIf="enableHeaderAndFooter">
|
||||
<div id="header" [ngStyle]="headerFooterStyle" *ngIf="projector && projector.show_header_footer">
|
||||
<!-- projector logo -->
|
||||
<img *ngIf="enableLogo && projectorLogo" src="{{ projectorLogo }}" class="projector-logo-main" />
|
||||
|
||||
<div *ngIf="enableTitle" id="eventdata">
|
||||
<div *ngIf="projector.show_title" id="eventdata">
|
||||
<div
|
||||
*ngIf="eventName"
|
||||
class="event-name"
|
||||
@ -19,7 +19,7 @@
|
||||
<os-slide-container [slideData]="slide" [scroll]="scroll" [scale]="scale"></os-slide-container>
|
||||
</div>
|
||||
|
||||
<div id="footer" [ngStyle]="headerFooterStyle" *ngIf="enableHeaderAndFooter">
|
||||
<div id="footer" [ngStyle]="headerFooterStyle" *ngIf="projector && projector.show_header_footer">
|
||||
<div class="footertext">
|
||||
<span *ngIf="eventDate"> {{ eventDate }} </span>
|
||||
<span *ngIf="eventDate && eventLocation"> | </span>
|
||||
|
@ -52,6 +52,9 @@ export class ProjectorComponent extends BaseComponent implements OnDestroy {
|
||||
) {
|
||||
this.updateScaling();
|
||||
}
|
||||
this.projectorStyle['background-color'] = projector.background_color;
|
||||
this.headerFooterStyle.color = projector.header_font_color;
|
||||
this.headerFooterStyle['background-color'] = projector.header_background_color;
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +97,8 @@ export class ProjectorComponent extends BaseComponent implements OnDestroy {
|
||||
/**
|
||||
* Dynamic style attributes for the header and footer.
|
||||
*/
|
||||
public headerFooterStyle: { 'background-color': string; color: string } = {
|
||||
public headerFooterStyle: { 'background-image': string; 'background-color': string; color: string } = {
|
||||
'background-image': 'none',
|
||||
'background-color': 'blue',
|
||||
color: 'white'
|
||||
};
|
||||
@ -156,26 +160,6 @@ export class ProjectorComponent extends BaseComponent implements OnDestroy {
|
||||
private configService: ConfigService
|
||||
) {
|
||||
super(titleService, translate);
|
||||
|
||||
// Get all important config variables.
|
||||
|
||||
// enable header/footer
|
||||
this.configService
|
||||
.get<boolean>('projector_enable_header_footer')
|
||||
.subscribe(val => (this.enableHeaderAndFooter = val));
|
||||
this.configService.get<boolean>('projector_enable_title').subscribe(val => (this.enableTitle = val));
|
||||
|
||||
// projector colors
|
||||
this.configService
|
||||
.get<string>('projector_header_fontcolor')
|
||||
.subscribe(val => (this.headerFooterStyle.color = val));
|
||||
this.configService
|
||||
.get<string>('projector_header_backgroundcolor')
|
||||
.subscribe(val => (this.headerFooterStyle['background-color'] = val));
|
||||
this.configService
|
||||
.get<string>('projector_background_color')
|
||||
.subscribe(val => (this.projectorStyle['background-color'] = val));
|
||||
|
||||
// projector logo / background-image
|
||||
this.configService.get<boolean>('projector_enable_logo').subscribe(val => (this.enableLogo = val));
|
||||
this.configService.get<{ path?: string }>('logo_projector_main').subscribe(val => {
|
||||
@ -186,6 +170,8 @@ export class ProjectorComponent extends BaseComponent implements OnDestroy {
|
||||
this.configService.get<{ path?: string }>('logo_projector_header').subscribe(val => {
|
||||
if (val && val.path) {
|
||||
this.headerFooterStyle['background-image'] = "url('" + val.path + "')";
|
||||
} else {
|
||||
this.headerFooterStyle['background-image'] = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -65,6 +65,13 @@ export class Projector extends BaseModel<Projector> {
|
||||
public height: number;
|
||||
public reference_projector_id: number;
|
||||
public projectiondefaults: ProjectionDefault[];
|
||||
public background_color: string;
|
||||
public header_background_color: string;
|
||||
public header_font_color: string;
|
||||
public header_h1_color: string;
|
||||
public show_header_footer: boolean;
|
||||
public show_title: boolean;
|
||||
public show_logo: boolean;
|
||||
|
||||
public constructor(input?: any) {
|
||||
super(Projector.COLLECTIONSTRING, input);
|
||||
|
@ -9,7 +9,6 @@ import { BehaviorSubject, Subscription } from 'rxjs';
|
||||
|
||||
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
|
||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||
import { CurrentListOfSpeakersSlideService } from 'app/site/projector/services/current-list-of-of-speakers-slide.service';
|
||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||
import { ProjectorRepositoryService } from 'app/core/repositories/projector/projector-repository.service';
|
||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
@ -20,6 +19,7 @@ import { ViewProjector } from 'app/site/projector/models/view-projector';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
|
||||
import { DurationService } from 'app/core/ui-services/duration.service';
|
||||
import { CurrentAgendaItemService } from 'app/site/projector/services/current-agenda-item.service';
|
||||
|
||||
/**
|
||||
* The list of speakers for agenda items.
|
||||
@ -104,7 +104,7 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
|
||||
* @param itemRepo Repository fpr agenda items
|
||||
* @param op the current operator
|
||||
* @param promptService
|
||||
* @param currentListOfSpeakersService
|
||||
* @param currentAgendaItemService
|
||||
* @param durationService helper for speech duration display
|
||||
*/
|
||||
public constructor(
|
||||
@ -116,7 +116,7 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
|
||||
private itemRepo: ItemRepositoryService,
|
||||
private op: OperatorService,
|
||||
private promptService: PromptService,
|
||||
private currentListOfSpeakersService: CurrentListOfSpeakersSlideService,
|
||||
private currentAgendaItemService: CurrentAgendaItemService,
|
||||
private durationService: DurationService,
|
||||
private userRepository: UserRepositoryService
|
||||
) {
|
||||
@ -185,7 +185,7 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
|
||||
this.viewItem = null;
|
||||
}
|
||||
|
||||
this.projectorSubscription = this.currentListOfSpeakersService
|
||||
this.projectorSubscription = this.currentAgendaItemService
|
||||
.getAgendaItemObservable(projector)
|
||||
.subscribe(item => {
|
||||
if (item) {
|
||||
|
@ -131,6 +131,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4>Current speaker chyron</h4>
|
||||
<mat-list>
|
||||
<mat-list-item [ngClass]="{'projected': isChyronProjected()}">
|
||||
<button type="button" mat-icon-button (click)="toggleChyron()">
|
||||
<mat-icon>videocam</mat-icon>
|
||||
</button>
|
||||
<span translate>Current speaker chyron</span>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</div>
|
||||
|
||||
<div class="queue">
|
||||
<h5 translate>Queue</h5>
|
||||
<div cdkDropList class="drop-list" (cdkDropListDropped)="onSortingChange($event)">
|
||||
|
@ -21,6 +21,7 @@ import { ViewProjectorMessage } from 'app/site/projector/models/view-projector-m
|
||||
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';
|
||||
import { CurrentSpeakerChyronSlideService } from '../../services/current-speaker-chyron-slide.service';
|
||||
|
||||
/**
|
||||
* The projector detail view.
|
||||
@ -59,7 +60,8 @@ export class ProjectorDetailComponent extends BaseViewComponent implements OnIni
|
||||
private slideManager: SlideManager,
|
||||
private countdownRepo: CountdownRepositoryService,
|
||||
private messageRepo: ProjectorMessageRepositoryService,
|
||||
private currentListOfSpeakersSlideService: CurrentListOfSpeakersSlideService
|
||||
private currentListOfSpeakersSlideService: CurrentListOfSpeakersSlideService,
|
||||
private currentSpeakerChyronService: CurrentSpeakerChyronSlideService
|
||||
) {
|
||||
super(titleService, translate, matSnackBar);
|
||||
|
||||
@ -148,4 +150,12 @@ export class ProjectorDetailComponent extends BaseViewComponent implements OnIni
|
||||
public toggleClos(stable: boolean): void {
|
||||
this.currentListOfSpeakersSlideService.toggleOn(this.projector, stable);
|
||||
}
|
||||
|
||||
public isChyronProjected(): boolean {
|
||||
return this.currentSpeakerChyronService.isProjectedOn(this.projector);
|
||||
}
|
||||
|
||||
public toggleChyron(): void {
|
||||
this.currentSpeakerChyronService.toggleOn(this.projector);
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +97,52 @@
|
||||
<mat-slider [thumbLabel]="true" formControlName="width" min="800" max="3840" step="10"></mat-slider>
|
||||
{{ updateForm.value.width }}
|
||||
|
||||
<!-- Clock -->
|
||||
<!-- colors -->
|
||||
<mat-form-field>
|
||||
<span translate>Background color</span>
|
||||
<input matInput formControlName="background_color" type="color" />
|
||||
<mat-hint *ngIf="!updateForm.controls.background_color.valid">
|
||||
<span translate>Required</span>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<span translate>Header background color</span>
|
||||
<input matInput formControlName="header_background_color" type="color" />
|
||||
<mat-hint *ngIf="!updateForm.controls.header_background_color.valid">
|
||||
<span translate>Required</span>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<span translate>Header font color</span>
|
||||
<input matInput formControlName="header_font_color" type="color" />
|
||||
<mat-hint *ngIf="!updateForm.controls.header_font_color.valid">
|
||||
<span translate>Required</span>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<span translate>Header headline color</span>
|
||||
<input matInput formControlName="header_h1_color" type="color" />
|
||||
<mat-hint *ngIf="!updateForm.controls.header_h1_color.valid">
|
||||
<span translate>Required</span>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<!-- checkboxes -->
|
||||
<div>
|
||||
<mat-checkbox formControlName="show_header_footer">
|
||||
<span translate>Show header and footer</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div>
|
||||
<mat-checkbox formControlName="show_title">
|
||||
<span translate>Show title</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div>
|
||||
<mat-checkbox formControlName="show_logo">
|
||||
<span translate>Show logo</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div>
|
||||
<mat-checkbox formControlName="clock">
|
||||
<span translate>Show clock</span>
|
||||
|
@ -105,7 +105,14 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
|
||||
aspectRatio: ['', Validators.required],
|
||||
width: [0, Validators.required],
|
||||
clock: [true],
|
||||
reference_projector_id: []
|
||||
reference_projector_id: [],
|
||||
background_color: ['', Validators.required],
|
||||
header_background_color: ['', Validators.required],
|
||||
header_font_color: ['', Validators.required],
|
||||
header_h1_color: ['', Validators.required],
|
||||
show_header_footer: [],
|
||||
show_title: [],
|
||||
show_logo: []
|
||||
});
|
||||
}
|
||||
|
||||
@ -202,11 +209,21 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
|
||||
const reference_projector_id = projector.reference_projector_id
|
||||
? projector.reference_projector_id
|
||||
: projector.id;
|
||||
this.updateForm.patchValue({
|
||||
/*this.updateForm.patchValue({
|
||||
name: projector.name,
|
||||
aspectRatio: this.getAspectRatioKey(projector),
|
||||
width: projector.width,
|
||||
clock: this.clockSlideService.isProjectedOn(projector),
|
||||
reference_projector_id: reference_projector_id,
|
||||
background_color: projector.background_color,
|
||||
show_header_footer: projector.show_header_footer,
|
||||
show_title: projector.show_title,
|
||||
show_logo: projector.show_logo
|
||||
});*/
|
||||
this.updateForm.patchValue(projector.projector);
|
||||
this.updateForm.patchValue({
|
||||
aspectRatio: this.getAspectRatioKey(projector),
|
||||
clock: this.clockSlideService.isProjectedOn(projector),
|
||||
reference_projector_id: reference_projector_id
|
||||
});
|
||||
}
|
||||
@ -231,12 +248,21 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
|
||||
if (projector.id !== this.editId || !this.updateForm.valid) {
|
||||
return;
|
||||
}
|
||||
const updateProjector: Partial<Projector> = {
|
||||
const updateProjector: Partial<Projector> = this.updateForm.value;
|
||||
/*const updateProjector: Partial<Projector> = {
|
||||
name: this.updateForm.value.name,
|
||||
width: this.updateForm.value.width,
|
||||
height: Math.round(this.updateForm.value.width / aspectRatios[this.updateForm.value.aspectRatio]),
|
||||
reference_projector_id: this.updateForm.value.reference_projector_id
|
||||
};
|
||||
reference_projector_id: this.updateForm.value.reference_projector_id,
|
||||
background_color: this.updateForm.value.background_color,
|
||||
show_header_footer: this.updateForm.value.show_header_footer,
|
||||
show_title: this.updateForm.value.show_title,
|
||||
show_logo: this.updateForm.value.show_logo
|
||||
};*/
|
||||
updateProjector.height = Math.round(
|
||||
this.updateForm.value.width / aspectRatios[this.updateForm.value.aspectRatio]
|
||||
);
|
||||
|
||||
try {
|
||||
await this.clockSlideService.setProjectedOn(projector, this.updateForm.value.clock);
|
||||
await this.repo.update(updateProjector, projector);
|
||||
|
@ -54,6 +54,34 @@ export class ViewProjector extends BaseViewModel {
|
||||
return this.projector.reference_projector_id;
|
||||
}
|
||||
|
||||
public get background_color(): string {
|
||||
return this.projector.background_color;
|
||||
}
|
||||
|
||||
public get header_background_color(): string {
|
||||
return this.projector.header_background_color;
|
||||
}
|
||||
|
||||
public get header_font_color(): string {
|
||||
return this.projector.header_font_color;
|
||||
}
|
||||
|
||||
public get header_h1_color(): string {
|
||||
return this.projector.header_h1_color;
|
||||
}
|
||||
|
||||
public get show_header_footer(): boolean {
|
||||
return this.projector.show_header_footer;
|
||||
}
|
||||
|
||||
public get show_title(): boolean {
|
||||
return this.projector.show_title;
|
||||
}
|
||||
|
||||
public get show_logo(): boolean {
|
||||
return this.projector.show_logo;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is set by the repository
|
||||
*/
|
||||
|
@ -0,0 +1,71 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
|
||||
import { ProjectorService } from 'app/core/core-services/projector.service';
|
||||
import { ViewProjector } from '../models/view-projector';
|
||||
import { ProjectorRepositoryService } from 'app/core/repositories/projector/projector-repository.service';
|
||||
import { SlideManager } from 'app/slides/services/slide-manager.service';
|
||||
import { BaseAgendaViewModel } from 'app/site/base/base-agenda-view-model';
|
||||
import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||
|
||||
/**
|
||||
* Observes the projector config for a given projector and returns a observable of the
|
||||
* current view item displayed at on the projector.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CurrentAgendaItemService {
|
||||
private currentItemIds: { [projectorId: number]: BehaviorSubject<ViewItem | null> } = {};
|
||||
|
||||
public constructor(
|
||||
private projectorService: ProjectorService,
|
||||
private projectorRepo: ProjectorRepositoryService,
|
||||
private slideManager: SlideManager
|
||||
) {
|
||||
this.projectorRepo.getGeneralViewModelObservable().subscribe(projector => {
|
||||
if (projector && this.currentItemIds[projector.id]) {
|
||||
const item = this.getCurrentAgendaItemIdForProjector(projector);
|
||||
this.currentItemIds[projector.id].next(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable for the agenda item id of the currently projected element on the
|
||||
* given projector.
|
||||
*
|
||||
* @param projector The projector to observe.
|
||||
* @returns An observalbe for the agenda item id. Null, if no element with an agenda item is shown.
|
||||
*/
|
||||
public getAgendaItemObservable(projector: ViewProjector): Observable<ViewItem | null> {
|
||||
if (!this.currentItemIds[projector.id]) {
|
||||
const item = this.getCurrentAgendaItemIdForProjector(projector);
|
||||
this.currentItemIds[projector.id] = new BehaviorSubject<ViewItem | null>(item);
|
||||
}
|
||||
return this.currentItemIds[projector.id].asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to get the agenda item id for one non stable element on the projector.
|
||||
*
|
||||
* @param projector The projector
|
||||
* @returns The agenda item id or null, if there is no such projector element.
|
||||
*/
|
||||
private getCurrentAgendaItemIdForProjector(projector: ViewProjector): ViewItem | null {
|
||||
const nonStableElements = projector.elements.filter(element => !element.stable);
|
||||
if (nonStableElements.length > 0) {
|
||||
const nonStableElement = this.slideManager.getIdentifialbeProjectorElement(nonStableElements[0]); // The normal case is just one non stable slide
|
||||
try {
|
||||
const viewModel = this.projectorService.getViewModelFromProjectorElement(nonStableElement);
|
||||
if (viewModel instanceof BaseAgendaViewModel) {
|
||||
return viewModel.getAgendaItem();
|
||||
}
|
||||
} catch (e) {
|
||||
// make TypeScript silent.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,33 +1,18 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { ProjectorService } from 'app/core/core-services/projector.service';
|
||||
import { ViewProjector } from '../models/view-projector';
|
||||
import { IdentifiableProjectorElement } from 'app/shared/models/core/projector';
|
||||
import { ProjectorRepositoryService } from 'app/core/repositories/projector/projector-repository.service';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { SlideManager } from 'app/slides/services/slide-manager.service';
|
||||
import { BaseAgendaViewModel } from 'app/site/base/base-agenda-view-model';
|
||||
import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||
|
||||
/**
|
||||
* Handles the curent list of speakers slide. Manages the projection and provides
|
||||
* function to check, if it is projected. Handles the overlay and slide.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CurrentListOfSpeakersSlideService {
|
||||
private currentItemIds: { [projectorId: number]: BehaviorSubject<ViewItem | null> } = {};
|
||||
|
||||
public constructor(
|
||||
private projectorService: ProjectorService,
|
||||
private projectorRepo: ProjectorRepositoryService,
|
||||
private slideManager: SlideManager
|
||||
) {
|
||||
this.projectorRepo.getGeneralViewModelObservable().subscribe(projector => {
|
||||
if (projector && this.currentItemIds[projector.id]) {
|
||||
const item = this.getCurrentAgendaItemIdForProjector(projector);
|
||||
this.currentItemIds[projector.id].next(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
public constructor(private projectorService: ProjectorService) {}
|
||||
|
||||
/**
|
||||
* Returns the basic projector element for the CLOS slide. If overlay=True, the projector element
|
||||
@ -44,43 +29,6 @@ export class CurrentListOfSpeakersSlideService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable for the agenda item id of the currently projected element on the
|
||||
* given projector.
|
||||
*
|
||||
* @param projector The projector to observe.
|
||||
* @returns An observalbe for the agenda item id. Null, if no element with an agenda item is shown.
|
||||
*/
|
||||
public getAgendaItemObservable(projector: ViewProjector): Observable<ViewItem | null> {
|
||||
if (!this.currentItemIds[projector.id]) {
|
||||
const item = this.getCurrentAgendaItemIdForProjector(projector);
|
||||
this.currentItemIds[projector.id] = new BehaviorSubject<ViewItem | null>(item);
|
||||
}
|
||||
return this.currentItemIds[projector.id].asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to get the agenda item id for one non stable element on the projector.
|
||||
*
|
||||
* @param projector The projector
|
||||
* @returns The agenda item id or null, if there is no such projector element.
|
||||
*/
|
||||
private getCurrentAgendaItemIdForProjector(projector: ViewProjector): ViewItem | null {
|
||||
const nonStableElements = projector.elements.filter(element => !element.stable);
|
||||
if (nonStableElements.length > 0) {
|
||||
const nonStableElement = this.slideManager.getIdentifialbeProjectorElement(nonStableElements[0]); // The normal case is just one non stable slide
|
||||
try {
|
||||
const viewModel = this.projectorService.getViewModelFromProjectorElement(nonStableElement);
|
||||
if (viewModel instanceof BaseAgendaViewModel) {
|
||||
return viewModel.getAgendaItem();
|
||||
}
|
||||
} catch (e) {
|
||||
// make TypeScript silent.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries, if the slide/overlay is projected on the given projector.
|
||||
*
|
||||
|
@ -0,0 +1,51 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { ProjectorService } from 'app/core/core-services/projector.service';
|
||||
import { ViewProjector } from '../models/view-projector';
|
||||
import { IdentifiableProjectorElement } from 'app/shared/models/core/projector';
|
||||
|
||||
/**
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CurrentSpeakerChyronSlideService {
|
||||
public constructor(private projectorService: ProjectorService) {}
|
||||
|
||||
/**
|
||||
* Returns the basic projector element for the chyron slide
|
||||
*
|
||||
* @returns the identifiable chyron projector element.
|
||||
*/
|
||||
private getCurrentSpeakerChyronProjectorElement(): IdentifiableProjectorElement {
|
||||
return {
|
||||
name: 'agenda/current-speaker-chyron',
|
||||
stable: true,
|
||||
getIdentifiers: () => ['name']
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries, if the slide is projected on the given projector.
|
||||
*
|
||||
* @param projector The projector
|
||||
* @returns if the slide is projected on the projector
|
||||
*/
|
||||
public isProjectedOn(projector: ViewProjector): boolean {
|
||||
return this.projectorService.isProjectedOn(this.getCurrentSpeakerChyronProjectorElement(), projector.projector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the projection state of the slide on the given projector
|
||||
*
|
||||
* @param projector The projector
|
||||
*/
|
||||
public async toggleOn(projector: ViewProjector): Promise<void> {
|
||||
const isClosProjected = this.isProjectedOn(projector);
|
||||
if (isClosProjected) {
|
||||
await this.projectorService.removeFrom(projector.projector, this.getCurrentSpeakerChyronProjectorElement());
|
||||
} else {
|
||||
await this.projectorService.projectOn(projector.projector, this.getCurrentSpeakerChyronProjectorElement());
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@
|
||||
<div *ngIf="data.data.current" class="currentSpeaker">
|
||||
<mat-icon>mic</mat-icon>
|
||||
<span>{{ data.data.current.user }}</span>
|
||||
<mat-icon *ngIf="data.data.current.marked">star</mat-icon>
|
||||
</div>
|
||||
|
||||
<!-- Next speakers -->
|
||||
|
@ -0,0 +1,3 @@
|
||||
export interface CurrentSpeakerChyronSlideData {
|
||||
current_speaker?: string;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
<div id="chyron" *ngIf="data">
|
||||
<span id="inner">
|
||||
{{ data.data.current_speaker }}
|
||||
</span>
|
||||
</div>
|
@ -0,0 +1,19 @@
|
||||
#chyron {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
font-size: 32px;
|
||||
text-align: center;
|
||||
line-height: 1.1;
|
||||
display: table;
|
||||
|
||||
#inner {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CurrentSpeakerChyronSlideComponent } from './current-speaker-chyron-slide.component';
|
||||
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
||||
|
||||
describe('CurrentSpeakerChyronSlideComponent', () => {
|
||||
let component: CurrentSpeakerChyronSlideComponent;
|
||||
let fixture: ComponentFixture<CurrentSpeakerChyronSlideComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule],
|
||||
declarations: [CurrentSpeakerChyronSlideComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CurrentSpeakerChyronSlideComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,15 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||
import { CurrentSpeakerChyronSlideData } from './current-speaker-chyron-slide-data';
|
||||
|
||||
@Component({
|
||||
selector: 'os-current-speaker-chyron-speakers-slide',
|
||||
templateUrl: './current-speaker-chyron-slide.component.html',
|
||||
styleUrls: ['./current-speaker-chyron-slide.component.scss']
|
||||
})
|
||||
export class CurrentSpeakerChyronSlideComponent extends BaseSlideComponent<CurrentSpeakerChyronSlideData> {
|
||||
public constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import { CurrentSpeakerChyronSlideModule } from './current-speaker-chyron-slide.module';
|
||||
|
||||
describe('CurrentSpeakerChyronSlideModule', () => {
|
||||
let currentSpeakerChyronSlideModule: CurrentSpeakerChyronSlideModule;
|
||||
|
||||
beforeEach(() => {
|
||||
currentSpeakerChyronSlideModule = new CurrentSpeakerChyronSlideModule();
|
||||
});
|
||||
|
||||
it('should create an instance', () => {
|
||||
expect(currentSpeakerChyronSlideModule).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,7 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { makeSlideModule } from 'app/slides/base-slide-module';
|
||||
import { CurrentSpeakerChyronSlideComponent } from './current-speaker-chyron-slide.component';
|
||||
|
||||
@NgModule(makeSlideModule(CurrentSpeakerChyronSlideComponent))
|
||||
export class CurrentSpeakerChyronSlideModule {}
|
@ -72,6 +72,11 @@ export const allSlidesDynamicConfiguration: (SlideDynamicConfiguration & Slide)[
|
||||
scaleable: false,
|
||||
scrollable: false
|
||||
},
|
||||
{
|
||||
slide: 'agenda/current-speaker-chyron',
|
||||
scaleable: false,
|
||||
scrollable: false
|
||||
},
|
||||
{
|
||||
slide: 'assignments/assignment',
|
||||
scaleable: true,
|
||||
|
@ -78,7 +78,7 @@ export const allSlides: SlideManifest[] = [
|
||||
loadChildren:
|
||||
'./slides/agenda/current-list-of-speakers/current-list-of-speakers-slide.module#CurrentListOfSpeakersSlideModule',
|
||||
verboseName: 'Current list of speakers',
|
||||
elementIdentifiers: ['name', 'id'],
|
||||
elementIdentifiers: ['name'],
|
||||
canBeMappedToModel: false
|
||||
},
|
||||
{
|
||||
@ -87,7 +87,16 @@ export const allSlides: SlideManifest[] = [
|
||||
loadChildren:
|
||||
'./slides/agenda/current-list-of-speakers-overlay/current-list-of-speakers-overlay-slide.module#CurrentListOfSpeakersOverlaySlideModule',
|
||||
verboseName: 'Current list of speakers overlay',
|
||||
elementIdentifiers: ['name', 'id'],
|
||||
elementIdentifiers: ['name'],
|
||||
canBeMappedToModel: false
|
||||
},
|
||||
{
|
||||
slide: 'agenda/current-speaker-chyron',
|
||||
path: 'agenda/current-speaker-chyron',
|
||||
loadChildren:
|
||||
'./slides/agenda/current-speaker-chyron/current-speaker-chyron-slide.module#CurrentSpeakerChyronSlideModule',
|
||||
verboseName: 'Current speaker chyron',
|
||||
elementIdentifiers: ['name'],
|
||||
canBeMappedToModel: false
|
||||
},
|
||||
{
|
||||
|
@ -65,8 +65,14 @@
|
||||
background-color: mat-color($background, app-bar);
|
||||
}
|
||||
|
||||
// drag & drop view
|
||||
/* drag & drop views */
|
||||
.node-content-wrapper {
|
||||
background-color: mat-color($background, app-bar);
|
||||
}
|
||||
|
||||
/* projector components */
|
||||
#chyron {
|
||||
background-color: mat-color($primary);
|
||||
color: white; // TODO
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,10 @@
|
||||
* {
|
||||
font-family: customProjectorFont;
|
||||
}
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
}
|
||||
|
||||
|
||||
h2 {
|
||||
line-height: 40px;
|
||||
|
@ -1,5 +1,5 @@
|
||||
from collections import defaultdict
|
||||
from typing import Any, Dict, List
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
from ..users.projector import get_user_name
|
||||
from ..utils.projector import (
|
||||
@ -149,31 +149,16 @@ def get_list_of_speakers_slide_data(all_data: AllData, item_id: int) -> Dict[str
|
||||
}
|
||||
|
||||
|
||||
def current_list_of_speakers_slide(
|
||||
all_data: AllData, element: Dict[str, Any], projector_id: int
|
||||
) -> Dict[str, Any]:
|
||||
def get_current_item_id_for_projector(
|
||||
all_data: AllData, projector: Dict[str, Any]
|
||||
) -> Union[int, None]:
|
||||
"""
|
||||
The current list of speakers slide. Creates the data for the given projector.
|
||||
Search for elements, that do have an agenda item:
|
||||
Try to get a model by the collection and id in the element. This
|
||||
model needs to have a 'agenda_item_id'. This item must exist. The first
|
||||
matching element is taken.
|
||||
"""
|
||||
try:
|
||||
this_projector = all_data["core/projector"][projector_id]
|
||||
except KeyError:
|
||||
raise ProjectorElementException(f"Projector {projector_id} does not exist")
|
||||
|
||||
reference_projector_id = this_projector["reference_projector_id"] or projector_id
|
||||
try:
|
||||
reference_projector = all_data["core/projector"][reference_projector_id]
|
||||
except KeyError:
|
||||
raise ProjectorElementException(
|
||||
f"Projector {reference_projector_id} does not exist"
|
||||
)
|
||||
|
||||
# Search for elements, that do have an agenda item:
|
||||
# Try to get a model by the collection and id in the element. This
|
||||
# model needs to have a 'agenda_item_id'. This item must exist. The first
|
||||
# matching element is taken.
|
||||
|
||||
elements = reference_projector["elements"]
|
||||
elements = projector["elements"]
|
||||
item_id = None
|
||||
for element in elements:
|
||||
if "id" not in element:
|
||||
@ -193,12 +178,69 @@ def current_list_of_speakers_slide(
|
||||
item_id = model["agenda_item_id"]
|
||||
break
|
||||
|
||||
return item_id
|
||||
|
||||
|
||||
def get_reference_projector(all_data: AllData, projector_id: int) -> Dict[str, Any]:
|
||||
"""
|
||||
Returns the reference projector to the given projector (by id)
|
||||
"""
|
||||
try:
|
||||
this_projector = all_data["core/projector"][projector_id]
|
||||
except KeyError:
|
||||
raise ProjectorElementException(f"Projector {projector_id} does not exist")
|
||||
|
||||
reference_projector_id = this_projector["reference_projector_id"] or projector_id
|
||||
try:
|
||||
reference_projector = all_data["core/projector"][reference_projector_id]
|
||||
except KeyError:
|
||||
raise ProjectorElementException(
|
||||
f"Projector {reference_projector_id} does not exist"
|
||||
)
|
||||
|
||||
return reference_projector
|
||||
|
||||
|
||||
def current_list_of_speakers_slide(
|
||||
all_data: AllData, element: Dict[str, Any], projector_id: int
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
The current list of speakers slide. Creates the data for the given projector.
|
||||
"""
|
||||
reference_projector = get_reference_projector(all_data, projector_id)
|
||||
item_id = get_current_item_id_for_projector(all_data, reference_projector)
|
||||
if item_id is None: # no element found
|
||||
return {}
|
||||
|
||||
return get_list_of_speakers_slide_data(all_data, item_id)
|
||||
|
||||
|
||||
def current_speaker_chyron_slide(
|
||||
all_data: AllData, element: Dict[str, Any], projector_id: int
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Returns the username for the current speaker.
|
||||
"""
|
||||
reference_projector = get_reference_projector(all_data, projector_id)
|
||||
item_id = get_current_item_id_for_projector(all_data, reference_projector)
|
||||
if item_id is None: # no element found
|
||||
return {}
|
||||
|
||||
# get item
|
||||
try:
|
||||
item = all_data["agenda/item"][item_id]
|
||||
except KeyError:
|
||||
raise ProjectorElementException(f"Item {item_id} does not exist")
|
||||
|
||||
# find current speaker
|
||||
current_speaker = None
|
||||
for speaker in item["speakers"]:
|
||||
if speaker["begin_time"] is not None and speaker["end_time"] is None:
|
||||
current_speaker = get_user_name(all_data, speaker["user_id"])
|
||||
|
||||
return {"current_speaker": current_speaker}
|
||||
|
||||
|
||||
def register_projector_slides() -> None:
|
||||
register_projector_slide("agenda/item-list", item_list_slide)
|
||||
register_projector_slide("agenda/list-of-speakers", list_of_speakers_slide)
|
||||
@ -208,3 +250,6 @@ def register_projector_slides() -> None:
|
||||
register_projector_slide(
|
||||
"agenda/current-list-of-speakers-overlay", current_list_of_speakers_slide
|
||||
)
|
||||
register_projector_slide(
|
||||
"agenda/current-speaker-chyron", current_speaker_chyron_slide
|
||||
)
|
||||
|
@ -203,62 +203,6 @@ def get_config_variables():
|
||||
group="Projector",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="projector_enable_logo",
|
||||
default_value=True,
|
||||
input_type="boolean",
|
||||
label="Show logo on projector",
|
||||
help_text="You can replace the logo by uploading an image and set it as "
|
||||
'the "Projector logo" in "files".',
|
||||
weight=152,
|
||||
group="Projector",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="projector_enable_title",
|
||||
default_value=True,
|
||||
input_type="boolean",
|
||||
label="Show title and description of event on projector",
|
||||
weight=155,
|
||||
group="Projector",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="projector_enable_header_footer",
|
||||
default_value=True,
|
||||
input_type="boolean",
|
||||
label="Display header and footer",
|
||||
weight=157,
|
||||
group="Projector",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="projector_header_backgroundcolor",
|
||||
default_value="#317796",
|
||||
input_type="colorpicker",
|
||||
label="Background color of projector header and footer",
|
||||
weight=160,
|
||||
group="Projector",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="projector_header_fontcolor",
|
||||
default_value="#F5F5F5",
|
||||
input_type="colorpicker",
|
||||
label="Font color of projector header and footer",
|
||||
weight=165,
|
||||
group="Projector",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="projector_h1_fontcolor",
|
||||
default_value="#317796",
|
||||
input_type="colorpicker",
|
||||
label="Font color of projector headline",
|
||||
weight=170,
|
||||
group="Projector",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="projector_default_countdown",
|
||||
default_value=60,
|
||||
@ -268,15 +212,6 @@ def get_config_variables():
|
||||
group="Projector",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="projector_background_color",
|
||||
default_value="#FFFFFF",
|
||||
input_type="colorpicker",
|
||||
label="Backgroundolor of the projector",
|
||||
weight=190,
|
||||
group="Projector",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="projector_currentListOfSpeakers_reference",
|
||||
default_value=1,
|
||||
|
46
openslides/core/migrations/0018_auto_20190222_1209.py
Normal file
46
openslides/core/migrations/0018_auto_20190222_1209.py
Normal file
@ -0,0 +1,46 @@
|
||||
# Generated by Django 2.1.5 on 2019-02-22 11:09
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("core", "0017_auto_20190219_2015")]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="projector",
|
||||
name="background_color",
|
||||
field=models.CharField(default="#ffffff", max_length=7),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="projector",
|
||||
name="header_background_color",
|
||||
field=models.CharField(default="#317796", max_length=7),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="projector",
|
||||
name="header_font_color",
|
||||
field=models.CharField(default="#f5f5f5", max_length=7),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="projector",
|
||||
name="header_h1_color",
|
||||
field=models.CharField(default="#317796", max_length=7),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="projector",
|
||||
name="show_header_footer",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="projector",
|
||||
name="show_logo",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="projector",
|
||||
name="show_title",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
@ -82,6 +82,14 @@ class Projector(RESTModelMixin, models.Model):
|
||||
width = models.PositiveIntegerField(default=1024)
|
||||
height = models.PositiveIntegerField(default=768)
|
||||
|
||||
background_color = models.CharField(max_length=7, default="#ffffff")
|
||||
header_background_color = models.CharField(max_length=7, default="#317796")
|
||||
header_font_color = models.CharField(max_length=7, default="#f5f5f5")
|
||||
header_h1_color = models.CharField(max_length=7, default="#317796")
|
||||
show_header_footer = models.BooleanField(default=True)
|
||||
show_title = models.BooleanField(default=True)
|
||||
show_logo = models.BooleanField(default=True)
|
||||
|
||||
name = models.CharField(max_length=255, unique=True, blank=True)
|
||||
|
||||
reference_projector = models.ForeignKey(
|
||||
|
@ -99,6 +99,13 @@ class ProjectorSerializer(ModelSerializer):
|
||||
"height",
|
||||
"reference_projector",
|
||||
"projectiondefaults",
|
||||
"background_color",
|
||||
"header_background_color",
|
||||
"header_font_color",
|
||||
"header_h1_color",
|
||||
"show_header_footer",
|
||||
"show_title",
|
||||
"show_logo",
|
||||
)
|
||||
read_only_fields = ("scale", "scroll")
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user