Add leftover changes for 3.1

Adds various changes for a more complete 3.1 release

- cleaner "current slide" description in projector detail (grid with ellipsis)
- show the previously projected slides as ordered lists under the accordion
- fix a bug where everyone could access the projection manage view (although it was unfunctional without the correct permissions)
- assignment list now uses the correct ellipsis
- fixes a bug where the lable "list of speakers" was not translated on the projector slide
- Show a lock on the "list of speaker"-slide if it has been closed
- enable dialog tests that have previously been disabled
This commit is contained in:
Sean Engelhardt 2019-12-09 15:00:32 +01:00
parent 50ce5e7d61
commit c9cf99d0e4
29 changed files with 323 additions and 153 deletions

View File

@ -63,7 +63,8 @@ export abstract class BaseIsAgendaItemAndListOfSpeakersContentObjectRepository<
}
public getAgendaSlideTitle(titleInformation: T): string {
const numberPrefix = titleInformation.agenda_item_number() ? `${titleInformation.agenda_item_number()} · ` : '';
const itemNumber = titleInformation.agenda_item_number();
const numberPrefix = itemNumber ? `${itemNumber} · ` : '';
return numberPrefix + this.getTitle(titleInformation);
}

View File

@ -237,7 +237,7 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
public getTitle = (titleInformation: MotionTitleInformation) => {
if (titleInformation.identifier) {
return titleInformation.identifier + ': ' + titleInformation.title;
return `${titleInformation.identifier}: ${titleInformation.title}`;
} else {
return titleInformation.title;
}
@ -255,9 +255,9 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
const numberPrefix = titleInformation.agenda_item_number() ? `${titleInformation.agenda_item_number()} · ` : '';
// if the identifier is set, the title will be 'Motion <identifier>'.
if (titleInformation.identifier) {
return numberPrefix + this.translate.instant('Motion') + ' ' + titleInformation.identifier;
return `${numberPrefix} ${this.translate.instant('Motion')} ${titleInformation.identifier}`;
} else {
return numberPrefix + titleInformation.title;
return `${numberPrefix} ${titleInformation.title}`;
}
};

View File

@ -52,8 +52,8 @@ export class TopicRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCon
}
public getTitle = (titleInformation: TopicTitleInformation) => {
if (titleInformation.agenda_item_number()) {
return titleInformation.agenda_item_number() + ' · ' + titleInformation.title;
if (titleInformation.agenda_item_number && titleInformation.agenda_item_number()) {
return `${titleInformation.agenda_item_number()} · ${titleInformation.title}`;
} else {
return titleInformation.title;
}

View File

@ -1,7 +1,7 @@
<!-- Title -->
<h2 mat-dialog-title>{{ data.title | translate }}</h2>
<h2 mat-dialog-title *ngIf="data">{{ data.title | translate }}</h2>
<form [formGroup]="selectForm">
<form [formGroup]="selectForm" *ngIf="data">
<!-- Content -->
<div mat-dialog-content *ngIf="data.choices">
<os-search-value-selector

View File

@ -1,26 +1,34 @@
import { async, TestBed } from '@angular/core/testing';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
// import { ChoiceDialogComponent } from './choice-dialog.component';
import { E2EImportsModule } from 'e2e-imports.module';
import { ChoiceDialogComponent } from './choice-dialog.component';
describe('ChoiceDialogComponent', () => {
// let component: ChoiceDialogComponent;
// let fixture: ComponentFixture<ChoiceDialogComponent>;
let component: ChoiceDialogComponent;
let fixture: ComponentFixture<ChoiceDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule]
imports: [E2EImportsModule],
providers: [
{ provide: MatDialogRef, useValue: {} },
{
provide: MAT_DIALOG_DATA,
useValue: null
}
]
}).compileComponents();
}));
// TODO: You cannot create this component in the standard way. Needs different testing.
beforeEach(() => {
/*fixture = TestBed.createComponent(PromptDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();*/
fixture = TestBed.createComponent(ChoiceDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
/*it('should create', () => {
expect(component).toBeTruthy();
});*/
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -73,7 +73,7 @@ export class ChoiceDialogComponent {
* @returns true if there is a selection chosen
*/
public get hasSelection(): boolean {
if (this.data.choices) {
if (this.data && this.data.choices) {
if (this.selectForm.get('select').value) {
return !!this.selectForm.get('select').value || !!this.selectForm.get('select').value.length;
} else {

View File

@ -1,23 +1,29 @@
<h2 mat-dialog-title>
<span translate>Project selection?</span>
</h2>
<div class="element-name">{{ projectorElementBuildDescriptor.getDialogTitle() }}</div>
<div class="element-name" *ngIf="projectorElementBuildDescriptor">
{{ projectorElementBuildDescriptor.getDialogTitle() }}
</div>
<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>
<span *ngIf="isProjectedOn(projector)" class="right">
<mat-icon matTooltip="{{ 'Is already projected' | translate }}" matTooltipPosition="above">videocam</mat-icon>
<mat-icon matTooltip="{{ 'Is already projected' | translate }}" matTooltipPosition="above"
>videocam</mat-icon
>
</span>
</div>
<mat-divider></mat-divider>
<div *ngIf="options.length > 0">
<div *ngIf="options && options.length">
<div *ngFor="let option of options">
<div *ngIf="isDecisionOption(option)" class="spacer-top-10 spacer-left-10">
<mat-checkbox

View File

@ -1,26 +1,34 @@
import { async, TestBed } from '@angular/core/testing';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
// import { ProjectionDialogComponent } from './prjection-dialog.component';
import { E2EImportsModule } from 'e2e-imports.module';
import { ProjectionDialogComponent } from './projection-dialog.component';
describe('ProjectionDialogComponent', () => {
// let component: ProjectionDialogComponent;
// let fixture: ComponentFixture<ProjectionDialogComponent>;
let component: ProjectionDialogComponent;
let fixture: ComponentFixture<ProjectionDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule]
imports: [E2EImportsModule],
providers: [
{ provide: MatDialogRef, useValue: {} },
{
provide: MAT_DIALOG_DATA,
useValue: null
}
]
}).compileComponents();
}));
// TODO: You cannot create this component in the standard way. Needs different testing.
beforeEach(() => {
/*fixture = TestBed.createComponent(ProjectionDialogComponent);
fixture = TestBed.createComponent(ProjectionDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();*/
fixture.detectChanges();
});
/*it('should create', () => {
it('should create', () => {
expect(component).toBeTruthy();
});*/
});
});

View File

@ -38,26 +38,28 @@ export class ProjectionDialogComponent {
this.projectors = this.DS.getAll<Projector>('core/projector');
// TODO: Maybe watch. But this may not be necessary for the short living time of this dialog.
this.selectedProjectors = this.projectorService.getProjectorsWhichAreProjecting(
this.projectorElementBuildDescriptor
);
// Add default projector, if the projectable is not projected on it.
if (this.projectorElementBuildDescriptor.projectionDefaultName) {
const defaultProjector: Projector = this.projectorService.getProjectorForDefault(
this.projectorElementBuildDescriptor.projectionDefaultName
if (projectorElementBuildDescriptor) {
this.selectedProjectors = this.projectorService.getProjectorsWhichAreProjecting(
this.projectorElementBuildDescriptor
);
if (defaultProjector && !this.selectedProjectors.includes(defaultProjector)) {
this.selectedProjectors.push(defaultProjector);
// Add default projector, if the projectable is not projected on it.
if (this.projectorElementBuildDescriptor.projectionDefaultName) {
const defaultProjector: Projector = this.projectorService.getProjectorForDefault(
this.projectorElementBuildDescriptor.projectionDefaultName
);
if (defaultProjector && !this.selectedProjectors.includes(defaultProjector)) {
this.selectedProjectors.push(defaultProjector);
}
}
// Set option defaults
this.projectorElementBuildDescriptor.slideOptions.forEach(option => {
this.optionValues[option.key] = option.default;
});
this.options = this.projectorElementBuildDescriptor.slideOptions;
}
// Set option defaults
this.projectorElementBuildDescriptor.slideOptions.forEach(option => {
this.optionValues[option.key] = option.default;
});
this.options = this.projectorElementBuildDescriptor.slideOptions;
}
public toggleProjector(projector: Projector): void {

View File

@ -1,6 +1,8 @@
<h2 mat-dialog-title>{{ data.title }}</h2>
<mat-dialog-content [innerHTML]="data.content"></mat-dialog-content>
<mat-dialog-actions>
<button mat-button [mat-dialog-close]="true" color="warn">{{ 'Yes' | translate }}</button>
<button mat-button [mat-dialog-close]="false">{{ 'Cancel' | translate }}</button>
</mat-dialog-actions>
<div *ngIf="data">
<h2 mat-dialog-title>{{ data.title }}</h2>
<mat-dialog-content [innerHTML]="data.content"></mat-dialog-content>
<mat-dialog-actions>
<button mat-button [mat-dialog-close]="true" color="warn">{{ 'Yes' | translate }}</button>
<button mat-button [mat-dialog-close]="false">{{ 'Cancel' | translate }}</button>
</mat-dialog-actions>
</div>

View File

@ -1,26 +1,34 @@
import { async, TestBed } from '@angular/core/testing';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
// import { PromptDialogComponent } from './prompt-dialog.component';
import { E2EImportsModule } from 'e2e-imports.module';
import { PromptDialogComponent } from './prompt-dialog.component';
describe('PromptDialogComponent', () => {
// let component: PromptDialogComponent;
// let fixture: ComponentFixture<PromptDialogComponent>;
let component: PromptDialogComponent;
let fixture: ComponentFixture<PromptDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule]
imports: [E2EImportsModule],
providers: [
{ provide: MatDialogRef, useValue: {} },
{
provide: MAT_DIALOG_DATA,
useValue: null
}
]
}).compileComponents();
}));
// TODO: You cannot create this component in the standard way. Needs different testing.
beforeEach(() => {
/*fixture = TestBed.createComponent(PromptDialogComponent);
fixture = TestBed.createComponent(PromptDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();*/
fixture.detectChanges();
});
/*it('should create', () => {
it('should create', () => {
expect(component).toBeTruthy();
});*/
});
});

View File

@ -1,4 +1,4 @@
<h1 mat-dialog-title>{{ 'Edit details for' | translate }} {{ item.getTitle() }}</h1>
<h1 mat-dialog-title *ngIf="item">{{ 'Edit details for' | translate }} {{ item.getTitle() }}</h1>
<div mat-dialog-content>
<form class="item-dialog-form" [formGroup]="agendaInfoForm" (keydown)="onKeyDown($event)">
<!-- Visibility -->

View File

@ -1,26 +1,35 @@
import { async, TestBed } from '@angular/core/testing';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
// import { ItemInfoDialogComponent } from './item-info-dialog.component';
import { E2EImportsModule } from 'e2e-imports.module';
import { ItemInfoDialogComponent } from './item-info-dialog.component';
describe('ItemInfoDialogComponent', () => {
// let component: ItemInfoDialogComponent;
// let fixture: ComponentFixture<ItemInfoDialogComponent>;
let component: ItemInfoDialogComponent;
let fixture: ComponentFixture<ItemInfoDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule]
imports: [E2EImportsModule],
declarations: [ItemInfoDialogComponent],
providers: [
{ provide: MatDialogRef, useValue: {} },
{
provide: MAT_DIALOG_DATA,
useValue: null
}
]
}).compileComponents();
}));
// TODO: You cannot create this component in the standard way. Needs different testing.
beforeEach(() => {
/*fixture = TestBed.createComponent(ItemInfoDialogComponent);
fixture = TestBed.createComponent(ItemInfoDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();*/
fixture.detectChanges();
});
/*it('should create', () => {
it('should create', () => {
expect(component).toBeTruthy();
});*/
});
});

View File

@ -38,8 +38,7 @@ export class ItemInfoDialogComponent {
public formBuilder: FormBuilder,
public durationService: DurationService,
public dialogRef: MatDialogRef<ItemInfoDialogComponent>,
@Inject(MAT_DIALOG_DATA)
public item: ViewItem
@Inject(MAT_DIALOG_DATA) public item: ViewItem
) {
this.agendaInfoForm = this.formBuilder.group({
type: [''],
@ -49,10 +48,12 @@ export class ItemInfoDialogComponent {
});
// load current values
this.agendaInfoForm.get('type').setValue(item.type);
this.agendaInfoForm.get('durationText').setValue(this.durationService.durationToString(item.duration, 'h'));
this.agendaInfoForm.get('item_number').setValue(item.item_number);
this.agendaInfoForm.get('comment').setValue(item.comment);
if (item) {
this.agendaInfoForm.get('type').setValue(item.type);
this.agendaInfoForm.get('durationText').setValue(this.durationService.durationToString(item.duration, 'h'));
this.agendaInfoForm.get('item_number').setValue(item.item_number);
this.agendaInfoForm.get('comment').setValue(item.comment);
}
}
/**

View File

@ -168,7 +168,7 @@
[input]="assignment.assignment_related_users"
[live]="true"
[count]="true"
[enable]="hasPerms('addOthers')"
[enable]="hasPerms('manage')"
(sortEvent)="onSortingChange($event)"
>
<!-- implicit item references into the component using ng-template slot -->
@ -177,6 +177,7 @@
<button
mat-icon-button
matTooltip="{{ 'Remove candidate' | translate }}"
*osPerms="'assignments.can_manage'"
(click)="removeUser(item)"
>
<mat-icon>clear</mat-icon>

View File

@ -244,7 +244,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
} else {
return (
this.assignment.isSearchingForCandidates &&
this.operator.hasPerms('assignments.can_nominate_others') &&
this.operator.hasPerms('assignments.can_nominate_other') &&
!this.assignment.isFinished
);
}

View File

@ -34,11 +34,11 @@
<!-- Title -->
<div *pblNgridCellDef="'title'; row as assignment; rowContext as rowContext" class="cell-slot fill">
<a class="detail-link" [routerLink]="assignment.id" *ngIf="!isMultiSelect"></a>
<div>
<div class="innerTable">
<div class="title-line ellipsis-overflow">
{{ assignment.getListTitle() }}
</div>
<mat-chip-list *ngIf="vp.isMobile">
<mat-chip-list class="ellipsis-overflow" *ngIf="vp.isMobile">
<mat-chip color="primary" selected>
{{ assignment.phaseString | translate }}
</mat-chip>

View File

@ -7,7 +7,7 @@
'project title buttons'
'project controls controls';
grid-gap: 10px;
grid-template-columns: min-content auto auto;
grid-template-columns: min-content auto min-content;
}
// could be in a shared scss
@ -28,6 +28,7 @@
.action-buttons {
grid-area: buttons;
text-align: right;
display: flex;
}
.timer {

View File

@ -9,10 +9,6 @@ describe('MessageDialogComponent', () => {
let component: MessageDialogComponent;
let fixture: ComponentFixture<MessageDialogComponent>;
// const dialogData: MessageData = {
// text: ''
// };
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [MessageDialogComponent],

View File

@ -1,13 +1,46 @@
<os-head-bar [nav]="false" [hasMainButton]="true" mainButtonIcon="edit" [goBack]="true" (mainEvent)="editProjector()">
<os-head-bar
[nav]="false"
[hasMainButton]="canManage()"
mainButtonIcon="edit"
[goBack]="true"
(mainEvent)="editProjector()"
>
<!-- Title -->
<div class="title-slot">
<h2>{{ projector?.name | translate }}</h2>
</div>
<!-- Menu -->
<div class="menu-slot">
<button
*osPerms="'core.can_manage_projector'"
type="button"
mat-icon-button
[matMenuTriggerFor]="projectorExtraMenu"
>
<mat-icon>more_vert</mat-icon>
</button>
</div>
<mat-menu #projectorExtraMenu="matMenu">
<!-- Button for set reference projector -->
<button mat-menu-item (click)="onSetAsClosRef()" *ngIf="projector" [disabled]="projector.isReferenceProjector">
<mat-icon *ngIf="projector.isReferenceProjector">star</mat-icon>
<mat-icon *ngIf="!projector.isReferenceProjector">star_border</mat-icon>
<span>{{ 'Set as reference projector' | translate }}</span>
</button>
<!-- delete -->
<button mat-menu-item class="red-warning-text" (click)="onDeleteProjectorButton()">
<mat-icon>delete</mat-icon>
<span translate>Delete</span>
</button>
</mat-menu>
</os-head-bar>
<os-grid-layout *ngIf="projector">
<os-tile [preferredSize]="projectorTileSizeLeft">
<div *ngIf="projector" class="column-left">
<div *ngIf="projector" class="projector-detail-wrapper column-left">
<a [routerLink]="['/projector', projector.id]">
<div id="projector">
<os-projector [projector]="projector"></os-projector>
@ -16,18 +49,30 @@
<!-- Controls under the projector preview -->
<div *osPerms="'core.can_manage_projector'" class="control-group projector-controls">
<!-- scale down -->
<button type="button" mat-icon-button (click)="scale(scrollScaleDirection.Down)"
matTooltip="{{ 'Zoom out' | translate }}">
<button
type="button"
mat-icon-button
(click)="scale(scrollScaleDirection.Down)"
matTooltip="{{ 'Zoom out' | translate }}"
>
<mat-icon>zoom_out</mat-icon>
</button>
<!-- scale up -->
<button type="button" mat-icon-button (click)="scale(scrollScaleDirection.Up)"
matTooltip="{{ 'Zoom in' | translate }}">
<button
type="button"
mat-icon-button
(click)="scale(scrollScaleDirection.Up)"
matTooltip="{{ 'Zoom in' | translate }}"
>
<mat-icon>zoom_in</mat-icon>
</button>
<!-- reset button -->
<button type="button" mat-icon-button (click)="scale(scrollScaleDirection.Reset)"
matTooltip="{{ 'Reset' | translate }}">
<button
type="button"
mat-icon-button
(click)="scale(scrollScaleDirection.Reset)"
matTooltip="{{ 'Reset' | translate }}"
>
<mat-icon>refresh</mat-icon>
</button>
<!-- scaling indicator -->
@ -73,8 +118,12 @@
<mat-icon>arrow_downward</mat-icon>
</button>
<!-- reset button -->
<button type="button" mat-icon-button (click)="scroll(scrollScaleDirection.Reset)"
matTooltip="{{ 'Reset' | translate }}">
<button
type="button"
mat-icon-button
(click)="scroll(scrollScaleDirection.Reset)"
matTooltip="{{ 'Reset' | translate }}"
>
<mat-icon>refresh</mat-icon>
</button>
<!-- scroll indicator -->
@ -113,18 +162,20 @@
<mat-list>
<mat-list-item
*ngFor="let element of projector.non_stable_elements"
class="currentElement backgroundColorAccent"
class="current-element backgroundColorAccent"
>
<button type="button" mat-icon-button (click)="unprojectCurrent(element)">
<mat-icon>videocam</mat-icon>
</button>
{{ getSlideTitle(element) }}
<div class="button-right">
<div>
<button type="button" mat-icon-button (click)="unprojectCurrent(element)">
<mat-icon>close</mat-icon>
</button>
</div>
<div class="emelent-grid">
<button type="button" mat-icon-button (click)="unprojectCurrent(element)">
<mat-icon>videocam</mat-icon>
</button>
<span class="ellipsis-overflow current-element-text">
{{ getSlideTitle(element) }}
</span>
<button type="button" mat-icon-button (click)="unprojectCurrent(element)">
<mat-icon>close</mat-icon>
</button>
</div>
</mat-list-item>
</mat-list>
@ -178,16 +229,6 @@
</mat-action-row>
</mat-expansion-panel>
<!-- Previous Slides -->
<mat-expansion-panel *ngIf="projector.elements_history.length" class="previous-slides">
<mat-expansion-panel-header>
<span translate>Previous slides</span>
</mat-expansion-panel-header>
<p *ngFor="let elements of projector.elements_history; let i = index">
{{ i + 1 }}. &nbsp; {{ getSlideTitle(elements[0]) }}
</p>
</mat-expansion-panel>
<!-- countdowns -->
<mat-expansion-panel>
<mat-expansion-panel-header>
@ -287,14 +328,16 @@
</mat-list>
</mat-expansion-panel>
<!-- File display controls -->
<!--<mat-expansion-panel>
<mat-expansion-panel *ngIf="projector.elements_history.length">
<mat-expansion-panel-header>
<span translate>Media controls</span>
<span translate>Previous slides</span>
</mat-expansion-panel-header>
<os-presentation-control [projector]="projector">
</os-presentation-control>
</mat-expansion-panel>-->
<ol>
<li *ngFor="let elements of projector.elements_history">
{{ getSlideTitle(elements[0]) }}
</li>
</ol>
</mat-expansion-panel>
<os-presentation-control [projector]="projector"> </os-presentation-control>
</mat-accordion>

View File

@ -1,8 +1,9 @@
@import '~assets/styles/drag.scss';
#projector {
width: 100%; /*1000px;*/
border: 1px solid lightgrey;
.projector-detail-wrapper {
#projector {
border: 1px solid lightgrey;
}
}
.column-left {
@ -32,22 +33,29 @@
text-align: right;
}
.currentElement {
.current-element {
margin-bottom: 20px;
box-shadow: 0px 3px 10px 0px rgba(0, 0, 0, 0.25);
.button-right {
position: absolute;
right: 10px;
.emelent-grid {
display: grid;
width: 100%;
grid-gap: 5px;
grid-template-columns: min-content 1fr min-content;
.mat-icon-button {
display: inherit;
}
.current-element-text {
margin-top: auto;
margin-bottom: auto;
}
}
}
.queue,
.previous-slides {
.queue {
box-shadow: none !important;
background: none !important;
margin-bottom: 20px;
.queue-element {
margin-bottom: 5px;
}

View File

@ -8,6 +8,7 @@ import { ActivatedRoute } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { timer } from 'rxjs';
import { OperatorService } from 'app/core/core-services/operator.service';
import { ProjectorService } from 'app/core/core-services/projector.service';
import { CountdownRepositoryService } from 'app/core/repositories/projector/countdown-repository.service';
import { ProjectorMessageRepositoryService } from 'app/core/repositories/projector/projector-message-repository.service';
@ -16,6 +17,7 @@ import {
ScrollScaleDirection
} from 'app/core/repositories/projector/projector-repository.service';
import { DurationService } from 'app/core/ui-services/duration.service';
import { PromptService } from 'app/core/ui-services/prompt.service';
import { SizeObject } from 'app/shared/components/tile/tile.component';
import { Countdown } from 'app/shared/models/core/countdown';
import { ProjectorElement } from 'app/shared/models/core/projector';
@ -92,7 +94,9 @@ export class ProjectorDetailComponent extends BaseViewComponent implements OnIni
private currentListOfSpeakersSlideService: CurrentListOfSpeakersSlideService,
private currentSpeakerChyronService: CurrentSpeakerChyronSlideService,
private durationService: DurationService,
private cd: ChangeDetectorRef
private cd: ChangeDetectorRef,
private promptService: PromptService,
private opertator: OperatorService
) {
super(titleService, translate, matSnackBar);
@ -135,6 +139,32 @@ export class ProjectorDetailComponent extends BaseViewComponent implements OnIni
});
}
/**
* Handler to set the current reference projector
* TODO: same with projector list entry
*/
public onSetAsClosRef(): void {
this.repo.setReferenceProjector(this.projector.id);
}
/**
* Handler for the delete Projector button
* TODO: same with projector list entry
*/
public async onDeleteProjectorButton(): Promise<void> {
const title = this.translate.instant('Are you sure you want to delete this projector?');
if (await this.promptService.open(title, this.projector.name)) {
this.repo.delete(this.projector).catch(this.raiseError);
}
}
/**
* @returns true if the operator can manage
*/
public canManage(): boolean {
return this.opertator.hasPerms('core.can_manage_projector');
}
/**
* Change the scroll
*

View File

@ -8,18 +8,23 @@
(click)="onSetAsClosRef()"
matTooltip="{{ 'Sets this projector as the reference for the current list of speakers' | translate }}"
>
<mat-icon *ngIf="this.projector.isReferenceProjector">star</mat-icon>
<mat-icon *ngIf="!this.projector.isReferenceProjector">star_border</mat-icon>
<mat-icon *ngIf="projector.isReferenceProjector">star</mat-icon>
<mat-icon *ngIf="!projector.isReferenceProjector">star_border</mat-icon>
</button>
<button mat-icon-button (click)="editProjector()" matTooltip="{{ 'Edit projector' | translate }}">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button color="warn" (click)="onDeleteButton()" matTooltip="{{ 'Delete projector' | translate }}">
<button
mat-icon-button
color="warn"
(click)="onDeleteButton()"
matTooltip="{{ 'Delete projector' | translate }}"
>
<mat-icon>delete</mat-icon>
</button>
</ng-container>
<ng-container class="meta-text-block-content">
<a class="no-markup" [routerLink]="['/projectors/detail', projector.id]">
<a class="no-markup" [routerLink]="getDetailLink()">
<div class="projector">
<os-projector [projector]="projector"></os-projector>
</div>

View File

@ -5,6 +5,7 @@ import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
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';
import { largeDialogSettings } from 'app/shared/utils/dialog-settings';
@ -54,7 +55,8 @@ export class ProjectorListEntryComponent extends BaseViewComponent implements On
matSnackBar: MatSnackBar,
private repo: ProjectorRepositoryService,
private promptService: PromptService,
private dialogService: MatDialog
private dialogService: MatDialog,
private operator: OperatorService
) {
super(titleService, translate, matSnackBar);
}
@ -78,6 +80,19 @@ export class ProjectorListEntryComponent extends BaseViewComponent implements On
this.repo.setReferenceProjector(this.projector.id);
}
/**
* Determines the detail link by permission.
* Without manage permission, the user should see the full screen projector
* and not the detail view
*/
public getDetailLink(): string {
if (this.operator.hasPerms('core.can_can_manage_projector')) {
return `/projectors/detail/${this.projector.id}`;
} else {
return `/projector/${this.projector.id}`;
}
}
/**
* Delete the projector.
*/

View File

@ -13,7 +13,7 @@ const routes: Routes = [
{
path: 'detail/:id',
component: ProjectorDetailComponent,
data: { basePerm: 'core.can_see_projector' }
data: { basePerm: 'core.can_can_manage_projector' }
}
];

View File

@ -7,6 +7,11 @@ export interface CommonListOfSpeakersSlideData {
waiting?: SlideSpeaker[];
current?: SlideSpeaker;
finished?: SlideSpeaker[];
title_information?: object;
title_information?: {
_agenda_item_number: string;
agend_item_number: () => string;
[key: string]: any;
};
content_object_collection?: string;
closed?: boolean;
}

View File

@ -1,6 +1,9 @@
<div *ngIf="data">
<div class="slidetitle">
<h1 translate>List of speakers</h1>
<h1 translate>
List of speakers
<mat-icon *ngIf="data.data.closed">lock</mat-icon>
</h1>
<h2>
{{ getTitle() }}
<span *ngIf="getSpeakersCount() > 0 && !hideAmountOfSpeakers">

View File

@ -1,8 +1,10 @@
import { Component, OnInit } from '@angular/core';
import { Component, Input, OnInit } from '@angular/core';
import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service';
import { SlideData } from 'app/core/core-services/projector-data.service';
import { isBaseIsAgendaItemContentObjectRepository } from 'app/core/repositories/base-is-agenda-item-content-object-repository';
import { ConfigService } from 'app/core/ui-services/config.service';
import { ProjectorElement } from 'app/shared/models/core/projector';
import { BaseSlideComponent } from 'app/slides/base-slide-component';
import { CommonListOfSpeakersSlideData } from './common-list-of-speakers-slide-data';
@ -13,6 +15,21 @@ import { CommonListOfSpeakersSlideData } from './common-list-of-speakers-slide-d
})
export class CommonListOfSpeakersSlideComponent extends BaseSlideComponent<CommonListOfSpeakersSlideData>
implements OnInit {
@Input()
public set data(value: SlideData<CommonListOfSpeakersSlideData, ProjectorElement>) {
// In the case of projected references without ListOfSpeakers Slide
if (Object.entries(value.data).length) {
value.data.title_information.agenda_item_number = () => value.data.title_information._agenda_item_number;
this._data = value;
}
}
public get data(): SlideData<CommonListOfSpeakersSlideData, ProjectorElement> {
return this._data;
}
private _data: SlideData<CommonListOfSpeakersSlideData, ProjectorElement>;
/**
* Boolean, whether the amount of speakers should be shown.
*/

View File

@ -123,7 +123,7 @@ async def get_list_of_speakers_slide_data(
list_of_speakers["content_object"]["id"]
].get("agenda_item_id")
if agenda_item_id:
title_information["agenda_item_number"] = all_data["agenda/item"][
title_information["_agenda_item_number"] = all_data["agenda/item"][
agenda_item_id
]["item_number"]
@ -169,6 +169,7 @@ async def get_list_of_speakers_slide_data(
"finished": speakers_finished,
"content_object_collection": list_of_speakers["content_object"]["collection"],
"title_information": title_information,
"closed": list_of_speakers["closed"],
}