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 { 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); return numberPrefix + this.getTitle(titleInformation);
} }

View File

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

View File

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

View File

@ -1,7 +1,7 @@
<!-- Title --> <!-- 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 --> <!-- Content -->
<div mat-dialog-content *ngIf="data.choices"> <div mat-dialog-content *ngIf="data.choices">
<os-search-value-selector <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 { E2EImportsModule } from 'e2e-imports.module';
import { ChoiceDialogComponent } from './choice-dialog.component';
describe('ChoiceDialogComponent', () => { describe('ChoiceDialogComponent', () => {
// let component: ChoiceDialogComponent; let component: ChoiceDialogComponent;
// let fixture: ComponentFixture<ChoiceDialogComponent>; let fixture: ComponentFixture<ChoiceDialogComponent>;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [E2EImportsModule] imports: [E2EImportsModule],
providers: [
{ provide: MatDialogRef, useValue: {} },
{
provide: MAT_DIALOG_DATA,
useValue: null
}
]
}).compileComponents(); }).compileComponents();
})); }));
// TODO: You cannot create this component in the standard way. Needs different testing.
beforeEach(() => { beforeEach(() => {
/*fixture = TestBed.createComponent(PromptDialogComponent); fixture = TestBed.createComponent(ChoiceDialogComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges();*/ fixture.detectChanges();
}); });
/*it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
});*/ });
}); });

View File

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

View File

@ -1,23 +1,29 @@
<h2 mat-dialog-title> <h2 mat-dialog-title>
<span translate>Project selection?</span> <span translate>Project selection?</span>
</h2> </h2>
<div class="element-name">{{ projectorElementBuildDescriptor.getDialogTitle() }}</div> <div class="element-name" *ngIf="projectorElementBuildDescriptor">
{{ projectorElementBuildDescriptor.getDialogTitle() }}
</div>
<mat-dialog-content> <mat-dialog-content>
<div class="projectors" <div
class="projectors"
*ngFor="let projector of projectors" *ngFor="let projector of projectors"
[ngClass]="isProjectedOn(projector) ? 'projected' : ''"> [ngClass]="isProjectedOn(projector) ? 'projected' : ''"
>
<mat-checkbox [checked]="isProjectorSelected(projector)" (change)="toggleProjector(projector)"> <mat-checkbox [checked]="isProjectorSelected(projector)" (change)="toggleProjector(projector)">
{{ projector.name | translate }} {{ projector.name | translate }}
</mat-checkbox> </mat-checkbox>
<span *ngIf="isProjectedOn(projector)" class="right"> <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> </span>
</div> </div>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<div *ngIf="options.length > 0"> <div *ngIf="options && options.length">
<div *ngFor="let option of options"> <div *ngFor="let option of options">
<div *ngIf="isDecisionOption(option)" class="spacer-top-10 spacer-left-10"> <div *ngIf="isDecisionOption(option)" class="spacer-top-10 spacer-left-10">
<mat-checkbox <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 { E2EImportsModule } from 'e2e-imports.module';
import { ProjectionDialogComponent } from './projection-dialog.component';
describe('ProjectionDialogComponent', () => { describe('ProjectionDialogComponent', () => {
// let component: ProjectionDialogComponent; let component: ProjectionDialogComponent;
// let fixture: ComponentFixture<ProjectionDialogComponent>; let fixture: ComponentFixture<ProjectionDialogComponent>;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [E2EImportsModule] imports: [E2EImportsModule],
providers: [
{ provide: MatDialogRef, useValue: {} },
{
provide: MAT_DIALOG_DATA,
useValue: null
}
]
}).compileComponents(); }).compileComponents();
})); }));
// TODO: You cannot create this component in the standard way. Needs different testing.
beforeEach(() => { beforeEach(() => {
/*fixture = TestBed.createComponent(ProjectionDialogComponent); fixture = TestBed.createComponent(ProjectionDialogComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges();*/ fixture.detectChanges();
}); });
/*it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
});*/ });
}); });

View File

@ -38,26 +38,28 @@ export class ProjectionDialogComponent {
this.projectors = this.DS.getAll<Projector>('core/projector'); 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. // TODO: Maybe watch. But this may not be necessary for the short living time of this dialog.
this.selectedProjectors = this.projectorService.getProjectorsWhichAreProjecting( if (projectorElementBuildDescriptor) {
this.projectorElementBuildDescriptor 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 (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 { public toggleProjector(projector: Projector): void {

View File

@ -1,6 +1,8 @@
<h2 mat-dialog-title>{{ data.title }}</h2> <div *ngIf="data">
<mat-dialog-content [innerHTML]="data.content"></mat-dialog-content> <h2 mat-dialog-title>{{ data.title }}</h2>
<mat-dialog-actions> <mat-dialog-content [innerHTML]="data.content"></mat-dialog-content>
<button mat-button [mat-dialog-close]="true" color="warn">{{ 'Yes' | translate }}</button> <mat-dialog-actions>
<button mat-button [mat-dialog-close]="false">{{ 'Cancel' | translate }}</button> <button mat-button [mat-dialog-close]="true" color="warn">{{ 'Yes' | translate }}</button>
</mat-dialog-actions> <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 { E2EImportsModule } from 'e2e-imports.module';
import { PromptDialogComponent } from './prompt-dialog.component';
describe('PromptDialogComponent', () => { describe('PromptDialogComponent', () => {
// let component: PromptDialogComponent; let component: PromptDialogComponent;
// let fixture: ComponentFixture<PromptDialogComponent>; let fixture: ComponentFixture<PromptDialogComponent>;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [E2EImportsModule] imports: [E2EImportsModule],
providers: [
{ provide: MatDialogRef, useValue: {} },
{
provide: MAT_DIALOG_DATA,
useValue: null
}
]
}).compileComponents(); }).compileComponents();
})); }));
// TODO: You cannot create this component in the standard way. Needs different testing.
beforeEach(() => { beforeEach(() => {
/*fixture = TestBed.createComponent(PromptDialogComponent); fixture = TestBed.createComponent(PromptDialogComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges();*/ fixture.detectChanges();
}); });
/*it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); 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> <div mat-dialog-content>
<form class="item-dialog-form" [formGroup]="agendaInfoForm" (keydown)="onKeyDown($event)"> <form class="item-dialog-form" [formGroup]="agendaInfoForm" (keydown)="onKeyDown($event)">
<!-- Visibility --> <!-- 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 { E2EImportsModule } from 'e2e-imports.module';
import { ItemInfoDialogComponent } from './item-info-dialog.component';
describe('ItemInfoDialogComponent', () => { describe('ItemInfoDialogComponent', () => {
// let component: ItemInfoDialogComponent; let component: ItemInfoDialogComponent;
// let fixture: ComponentFixture<ItemInfoDialogComponent>; let fixture: ComponentFixture<ItemInfoDialogComponent>;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [E2EImportsModule] imports: [E2EImportsModule],
declarations: [ItemInfoDialogComponent],
providers: [
{ provide: MatDialogRef, useValue: {} },
{
provide: MAT_DIALOG_DATA,
useValue: null
}
]
}).compileComponents(); }).compileComponents();
})); }));
// TODO: You cannot create this component in the standard way. Needs different testing.
beforeEach(() => { beforeEach(() => {
/*fixture = TestBed.createComponent(ItemInfoDialogComponent); fixture = TestBed.createComponent(ItemInfoDialogComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges();*/ fixture.detectChanges();
}); });
/*it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
});*/ });
}); });

View File

@ -38,8 +38,7 @@ export class ItemInfoDialogComponent {
public formBuilder: FormBuilder, public formBuilder: FormBuilder,
public durationService: DurationService, public durationService: DurationService,
public dialogRef: MatDialogRef<ItemInfoDialogComponent>, public dialogRef: MatDialogRef<ItemInfoDialogComponent>,
@Inject(MAT_DIALOG_DATA) @Inject(MAT_DIALOG_DATA) public item: ViewItem
public item: ViewItem
) { ) {
this.agendaInfoForm = this.formBuilder.group({ this.agendaInfoForm = this.formBuilder.group({
type: [''], type: [''],
@ -49,10 +48,12 @@ export class ItemInfoDialogComponent {
}); });
// load current values // load current values
this.agendaInfoForm.get('type').setValue(item.type); if (item) {
this.agendaInfoForm.get('durationText').setValue(this.durationService.durationToString(item.duration, 'h')); this.agendaInfoForm.get('type').setValue(item.type);
this.agendaInfoForm.get('item_number').setValue(item.item_number); this.agendaInfoForm.get('durationText').setValue(this.durationService.durationToString(item.duration, 'h'));
this.agendaInfoForm.get('comment').setValue(item.comment); 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" [input]="assignment.assignment_related_users"
[live]="true" [live]="true"
[count]="true" [count]="true"
[enable]="hasPerms('addOthers')" [enable]="hasPerms('manage')"
(sortEvent)="onSortingChange($event)" (sortEvent)="onSortingChange($event)"
> >
<!-- implicit item references into the component using ng-template slot --> <!-- implicit item references into the component using ng-template slot -->
@ -177,6 +177,7 @@
<button <button
mat-icon-button mat-icon-button
matTooltip="{{ 'Remove candidate' | translate }}" matTooltip="{{ 'Remove candidate' | translate }}"
*osPerms="'assignments.can_manage'"
(click)="removeUser(item)" (click)="removeUser(item)"
> >
<mat-icon>clear</mat-icon> <mat-icon>clear</mat-icon>

View File

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

View File

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

View File

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

View File

@ -9,10 +9,6 @@ describe('MessageDialogComponent', () => {
let component: MessageDialogComponent; let component: MessageDialogComponent;
let fixture: ComponentFixture<MessageDialogComponent>; let fixture: ComponentFixture<MessageDialogComponent>;
// const dialogData: MessageData = {
// text: ''
// };
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [MessageDialogComponent], 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 --> <!-- Title -->
<div class="title-slot"> <div class="title-slot">
<h2>{{ projector?.name | translate }}</h2> <h2>{{ projector?.name | translate }}</h2>
</div> </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-head-bar>
<os-grid-layout *ngIf="projector"> <os-grid-layout *ngIf="projector">
<os-tile [preferredSize]="projectorTileSizeLeft"> <os-tile [preferredSize]="projectorTileSizeLeft">
<div *ngIf="projector" class="column-left"> <div *ngIf="projector" class="projector-detail-wrapper column-left">
<a [routerLink]="['/projector', projector.id]"> <a [routerLink]="['/projector', projector.id]">
<div id="projector"> <div id="projector">
<os-projector [projector]="projector"></os-projector> <os-projector [projector]="projector"></os-projector>
@ -16,18 +49,30 @@
<!-- Controls under the projector preview --> <!-- Controls under the projector preview -->
<div *osPerms="'core.can_manage_projector'" class="control-group projector-controls"> <div *osPerms="'core.can_manage_projector'" class="control-group projector-controls">
<!-- scale down --> <!-- scale down -->
<button type="button" mat-icon-button (click)="scale(scrollScaleDirection.Down)" <button
matTooltip="{{ 'Zoom out' | translate }}"> type="button"
mat-icon-button
(click)="scale(scrollScaleDirection.Down)"
matTooltip="{{ 'Zoom out' | translate }}"
>
<mat-icon>zoom_out</mat-icon> <mat-icon>zoom_out</mat-icon>
</button> </button>
<!-- scale up --> <!-- scale up -->
<button type="button" mat-icon-button (click)="scale(scrollScaleDirection.Up)" <button
matTooltip="{{ 'Zoom in' | translate }}"> type="button"
mat-icon-button
(click)="scale(scrollScaleDirection.Up)"
matTooltip="{{ 'Zoom in' | translate }}"
>
<mat-icon>zoom_in</mat-icon> <mat-icon>zoom_in</mat-icon>
</button> </button>
<!-- reset button --> <!-- reset button -->
<button type="button" mat-icon-button (click)="scale(scrollScaleDirection.Reset)" <button
matTooltip="{{ 'Reset' | translate }}"> type="button"
mat-icon-button
(click)="scale(scrollScaleDirection.Reset)"
matTooltip="{{ 'Reset' | translate }}"
>
<mat-icon>refresh</mat-icon> <mat-icon>refresh</mat-icon>
</button> </button>
<!-- scaling indicator --> <!-- scaling indicator -->
@ -73,8 +118,12 @@
<mat-icon>arrow_downward</mat-icon> <mat-icon>arrow_downward</mat-icon>
</button> </button>
<!-- reset button --> <!-- reset button -->
<button type="button" mat-icon-button (click)="scroll(scrollScaleDirection.Reset)" <button
matTooltip="{{ 'Reset' | translate }}"> type="button"
mat-icon-button
(click)="scroll(scrollScaleDirection.Reset)"
matTooltip="{{ 'Reset' | translate }}"
>
<mat-icon>refresh</mat-icon> <mat-icon>refresh</mat-icon>
</button> </button>
<!-- scroll indicator --> <!-- scroll indicator -->
@ -113,18 +162,20 @@
<mat-list> <mat-list>
<mat-list-item <mat-list-item
*ngFor="let element of projector.non_stable_elements" *ngFor="let element of projector.non_stable_elements"
class="currentElement backgroundColorAccent" class="current-element backgroundColorAccent"
> >
<button type="button" mat-icon-button (click)="unprojectCurrent(element)"> <div class="emelent-grid">
<mat-icon>videocam</mat-icon> <button type="button" mat-icon-button (click)="unprojectCurrent(element)">
</button> <mat-icon>videocam</mat-icon>
{{ getSlideTitle(element) }} </button>
<div class="button-right">
<div> <span class="ellipsis-overflow current-element-text">
<button type="button" mat-icon-button (click)="unprojectCurrent(element)"> {{ getSlideTitle(element) }}
<mat-icon>close</mat-icon> </span>
</button>
</div> <button type="button" mat-icon-button (click)="unprojectCurrent(element)">
<mat-icon>close</mat-icon>
</button>
</div> </div>
</mat-list-item> </mat-list-item>
</mat-list> </mat-list>
@ -178,16 +229,6 @@
</mat-action-row> </mat-action-row>
</mat-expansion-panel> </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 --> <!-- countdowns -->
<mat-expansion-panel> <mat-expansion-panel>
<mat-expansion-panel-header> <mat-expansion-panel-header>
@ -287,14 +328,16 @@
</mat-list> </mat-list>
</mat-expansion-panel> </mat-expansion-panel>
<!-- File display controls --> <mat-expansion-panel *ngIf="projector.elements_history.length">
<!--<mat-expansion-panel>
<mat-expansion-panel-header> <mat-expansion-panel-header>
<span translate>Media controls</span> <span translate>Previous slides</span>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<os-presentation-control [projector]="projector"> <ol>
</os-presentation-control> <li *ngFor="let elements of projector.elements_history">
</mat-expansion-panel>--> {{ getSlideTitle(elements[0]) }}
</li>
</ol>
</mat-expansion-panel>
<os-presentation-control [projector]="projector"> </os-presentation-control> <os-presentation-control [projector]="projector"> </os-presentation-control>
</mat-accordion> </mat-accordion>

View File

@ -1,8 +1,9 @@
@import '~assets/styles/drag.scss'; @import '~assets/styles/drag.scss';
#projector { .projector-detail-wrapper {
width: 100%; /*1000px;*/ #projector {
border: 1px solid lightgrey; border: 1px solid lightgrey;
}
} }
.column-left { .column-left {
@ -32,22 +33,29 @@
text-align: right; text-align: right;
} }
.currentElement { .current-element {
margin-bottom: 20px; margin-bottom: 20px;
box-shadow: 0px 3px 10px 0px rgba(0, 0, 0, 0.25); box-shadow: 0px 3px 10px 0px rgba(0, 0, 0, 0.25);
.button-right { .emelent-grid {
position: absolute; display: grid;
right: 10px; 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, .queue {
.previous-slides {
box-shadow: none !important; box-shadow: none !important;
background: none !important; background: none !important;
margin-bottom: 20px; margin-bottom: 20px;
.queue-element { .queue-element {
margin-bottom: 5px; margin-bottom: 5px;
} }

View File

@ -8,6 +8,7 @@ import { ActivatedRoute } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { timer } from 'rxjs'; import { timer } from 'rxjs';
import { OperatorService } from 'app/core/core-services/operator.service';
import { ProjectorService } from 'app/core/core-services/projector.service'; import { ProjectorService } from 'app/core/core-services/projector.service';
import { CountdownRepositoryService } from 'app/core/repositories/projector/countdown-repository.service'; import { CountdownRepositoryService } from 'app/core/repositories/projector/countdown-repository.service';
import { ProjectorMessageRepositoryService } from 'app/core/repositories/projector/projector-message-repository.service'; import { ProjectorMessageRepositoryService } from 'app/core/repositories/projector/projector-message-repository.service';
@ -16,6 +17,7 @@ import {
ScrollScaleDirection ScrollScaleDirection
} from 'app/core/repositories/projector/projector-repository.service'; } from 'app/core/repositories/projector/projector-repository.service';
import { DurationService } from 'app/core/ui-services/duration.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 { SizeObject } from 'app/shared/components/tile/tile.component';
import { Countdown } from 'app/shared/models/core/countdown'; import { Countdown } from 'app/shared/models/core/countdown';
import { ProjectorElement } from 'app/shared/models/core/projector'; import { ProjectorElement } from 'app/shared/models/core/projector';
@ -92,7 +94,9 @@ export class ProjectorDetailComponent extends BaseViewComponent implements OnIni
private currentListOfSpeakersSlideService: CurrentListOfSpeakersSlideService, private currentListOfSpeakersSlideService: CurrentListOfSpeakersSlideService,
private currentSpeakerChyronService: CurrentSpeakerChyronSlideService, private currentSpeakerChyronService: CurrentSpeakerChyronSlideService,
private durationService: DurationService, private durationService: DurationService,
private cd: ChangeDetectorRef private cd: ChangeDetectorRef,
private promptService: PromptService,
private opertator: OperatorService
) { ) {
super(titleService, translate, matSnackBar); 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 * Change the scroll
* *

View File

@ -8,18 +8,23 @@
(click)="onSetAsClosRef()" (click)="onSetAsClosRef()"
matTooltip="{{ 'Sets this projector as the reference for the current list of speakers' | translate }}" 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="projector.isReferenceProjector">star</mat-icon>
<mat-icon *ngIf="!this.projector.isReferenceProjector">star_border</mat-icon> <mat-icon *ngIf="!projector.isReferenceProjector">star_border</mat-icon>
</button> </button>
<button mat-icon-button (click)="editProjector()" matTooltip="{{ 'Edit projector' | translate }}"> <button mat-icon-button (click)="editProjector()" matTooltip="{{ 'Edit projector' | translate }}">
<mat-icon>edit</mat-icon> <mat-icon>edit</mat-icon>
</button> </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> <mat-icon>delete</mat-icon>
</button> </button>
</ng-container> </ng-container>
<ng-container class="meta-text-block-content"> <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"> <div class="projector">
<os-projector [projector]="projector"></os-projector> <os-projector [projector]="projector"></os-projector>
</div> </div>

View File

@ -5,6 +5,7 @@ import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core'; 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 { ProjectorRepositoryService } from 'app/core/repositories/projector/projector-repository.service';
import { PromptService } from 'app/core/ui-services/prompt.service'; import { PromptService } from 'app/core/ui-services/prompt.service';
import { largeDialogSettings } from 'app/shared/utils/dialog-settings'; import { largeDialogSettings } from 'app/shared/utils/dialog-settings';
@ -54,7 +55,8 @@ export class ProjectorListEntryComponent extends BaseViewComponent implements On
matSnackBar: MatSnackBar, matSnackBar: MatSnackBar,
private repo: ProjectorRepositoryService, private repo: ProjectorRepositoryService,
private promptService: PromptService, private promptService: PromptService,
private dialogService: MatDialog private dialogService: MatDialog,
private operator: OperatorService
) { ) {
super(titleService, translate, matSnackBar); super(titleService, translate, matSnackBar);
} }
@ -78,6 +80,19 @@ export class ProjectorListEntryComponent extends BaseViewComponent implements On
this.repo.setReferenceProjector(this.projector.id); 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. * Delete the projector.
*/ */

View File

@ -13,7 +13,7 @@ const routes: Routes = [
{ {
path: 'detail/:id', path: 'detail/:id',
component: ProjectorDetailComponent, 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[]; waiting?: SlideSpeaker[];
current?: SlideSpeaker; current?: SlideSpeaker;
finished?: SlideSpeaker[]; finished?: SlideSpeaker[];
title_information?: object; title_information?: {
_agenda_item_number: string;
agend_item_number: () => string;
[key: string]: any;
};
content_object_collection?: string; content_object_collection?: string;
closed?: boolean;
} }

View File

@ -1,6 +1,9 @@
<div *ngIf="data"> <div *ngIf="data">
<div class="slidetitle"> <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> <h2>
{{ getTitle() }} {{ getTitle() }}
<span *ngIf="getSpeakersCount() > 0 && !hideAmountOfSpeakers"> <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 { 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 { isBaseIsAgendaItemContentObjectRepository } from 'app/core/repositories/base-is-agenda-item-content-object-repository';
import { ConfigService } from 'app/core/ui-services/config.service'; 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 { BaseSlideComponent } from 'app/slides/base-slide-component';
import { CommonListOfSpeakersSlideData } from './common-list-of-speakers-slide-data'; 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> export class CommonListOfSpeakersSlideComponent extends BaseSlideComponent<CommonListOfSpeakersSlideData>
implements OnInit { 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. * 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"] list_of_speakers["content_object"]["id"]
].get("agenda_item_id") ].get("agenda_item_id")
if 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 agenda_item_id
]["item_number"] ]["item_number"]
@ -169,6 +169,7 @@ async def get_list_of_speakers_slide_data(
"finished": speakers_finished, "finished": speakers_finished,
"content_object_collection": list_of_speakers["content_object"]["collection"], "content_object_collection": list_of_speakers["content_object"]["collection"],
"title_information": title_information, "title_information": title_information,
"closed": list_of_speakers["closed"],
} }