Merge pull request #4143 from MaximilianKrambach/callList
Speaker list opening/closing/clearing
This commit is contained in:
commit
a7eaecfa14
@ -1,9 +1,12 @@
|
|||||||
<os-head-bar [nav]="false" [goBack]="true">
|
<os-head-bar [nav]="false" [goBack]="true">
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<div class="title-slot">
|
<div class="title-slot">
|
||||||
<h2>
|
<h2><span translate>List of speakers</span></h2>
|
||||||
<span translate>List of speakers</span>
|
</div>
|
||||||
</h2>
|
<div class="menu-slot" *osPerms="'agenda.can_manage_list_of_speakers'">
|
||||||
|
<button type="button" mat-icon-button [matMenuTriggerFor]="speakerMenu">
|
||||||
|
<mat-icon>more_vert</mat-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</os-head-bar>
|
</os-head-bar>
|
||||||
|
|
||||||
@ -27,8 +30,12 @@
|
|||||||
<!-- <span translate>minutes</span> -->
|
<!-- <span translate>minutes</span> -->
|
||||||
<span> (</span> <span translate>Start time</span> <span>: {{ speaker.begin_time }})</span>
|
<span> (</span> <span translate>Start time</span> <span>: {{ speaker.begin_time }})</span>
|
||||||
</div>
|
</div>
|
||||||
<button mat-stroked-button matTooltip="{{ 'Remove' | translate }}"
|
<button
|
||||||
*osPerms="'agenda.can_manage_list_of_speakers'" (click)="onDeleteButton(speaker)">
|
mat-stroked-button
|
||||||
|
matTooltip="{{ 'Remove' | translate }}"
|
||||||
|
*osPerms="'agenda.can_manage_list_of_speakers'"
|
||||||
|
(click)="onDeleteButton(speaker)"
|
||||||
|
>
|
||||||
<mat-icon>close</mat-icon>
|
<mat-icon>close</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</mat-list-item>
|
</mat-list-item>
|
||||||
@ -40,30 +47,34 @@
|
|||||||
<mat-icon class="speaking-icon">play_arrow</mat-icon>
|
<mat-icon class="speaking-icon">play_arrow</mat-icon>
|
||||||
<span class="speaking-name">{{ activeSpeaker }}</span>
|
<span class="speaking-name">{{ activeSpeaker }}</span>
|
||||||
|
|
||||||
<button mat-stroked-button matTooltip="{{ 'End speech' | translate }}"
|
<button
|
||||||
*osPerms="'agenda.can_manage_list_of_speakers'" (click)="onStopButton()">
|
mat-stroked-button
|
||||||
|
matTooltip="{{ 'End speech' | translate }}"
|
||||||
|
*osPerms="'agenda.can_manage_list_of_speakers'"
|
||||||
|
(click)="onStopButton()"
|
||||||
|
>
|
||||||
<mat-icon>mic_off</mat-icon>
|
<mat-icon>mic_off</mat-icon>
|
||||||
<span translate>Stop</span>
|
<span translate>Stop</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Waiting speakers -->
|
<!-- Waiting speakers -->
|
||||||
<div *osPerms="'agenda.can_manage_list_of_speakers'">
|
<div>
|
||||||
<div class="waiting-list" *ngIf="speakers && speakers.length > 0">
|
<div class="waiting-list" *ngIf="speakers && speakers.length > 0">
|
||||||
<os-sorting-list [input]="speakers" [live]="true" [count]="true" (sortEvent)="onSortingChange($event)">
|
<os-sorting-list [input]="speakers" [live]="true" [count]="true" (sortEvent)="onSortingChange($event)">
|
||||||
<!-- implicit item references into the component using ng-template slot -->
|
<!-- implicit item references into the component using ng-template slot -->
|
||||||
<ng-template let-item>
|
<ng-template let-item>
|
||||||
|
<span *osPerms="'agenda.can_manage_list_of_speakers'">
|
||||||
<span *ngIf="hasSpokenCount(item)" class="red-warning-text speaker-warning">
|
<span *ngIf="hasSpokenCount(item)" class="red-warning-text speaker-warning">
|
||||||
<span translate>Call</span><span> {{ hasSpokenCount(item) + 1 }}</span>
|
<span translate>Call</span><span> {{ hasSpokenCount(item) + 1 }}</span>
|
||||||
</span>
|
</span>
|
||||||
<mat-button-toggle-group>
|
</span>
|
||||||
<mat-button-toggle matTooltip="{{ 'Begin speech' | translate }}"
|
<mat-button-toggle-group *osPerms="'agenda.can_manage_list_of_speakers'">
|
||||||
(click)="onStartButton(item)">
|
<mat-button-toggle matTooltip="{{ 'Begin speech' | translate }}" (click)="onStartButton(item)">
|
||||||
<mat-icon>mic</mat-icon>
|
<mat-icon>mic</mat-icon>
|
||||||
<span translate>Start</span>
|
<span translate>Start</span>
|
||||||
</mat-button-toggle>
|
</mat-button-toggle>
|
||||||
<mat-button-toggle matTooltip="{{ 'Mark speaker' | translate }}"
|
<mat-button-toggle matTooltip="{{ 'Mark speaker' | translate }}" (click)="onMarkButton(item)">
|
||||||
(click)="onMarkButton(item)">
|
|
||||||
<mat-icon>{{ item.marked ? 'star' : 'star_border' }}</mat-icon>
|
<mat-icon>{{ item.marked ? 'star' : 'star_border' }}</mat-icon>
|
||||||
</mat-button-toggle>
|
</mat-button-toggle>
|
||||||
<mat-button-toggle matTooltip="{{ 'Remove' | translate }}" (click)="onDeleteButton(item)">
|
<mat-button-toggle matTooltip="{{ 'Remove' | translate }}" (click)="onDeleteButton(item)">
|
||||||
@ -92,12 +103,11 @@
|
|||||||
|
|
||||||
<!-- Add me and remove me if OP has correct permission -->
|
<!-- Add me and remove me if OP has correct permission -->
|
||||||
<div *osPerms="'agenda.can_be_speaker'" class="add-self-buttons">
|
<div *osPerms="'agenda.can_be_speaker'" class="add-self-buttons">
|
||||||
<div *ngIf="speakers">
|
<div *ngIf="speakers && !closedList">
|
||||||
<button mat-raised-button (click)="addNewSpeaker()" *ngIf="!isOpInList()">
|
<button mat-raised-button (click)="addNewSpeaker()" *ngIf="!isOpInList()">
|
||||||
<mat-icon>add</mat-icon>
|
<mat-icon>add</mat-icon>
|
||||||
<span translate>Add me</span>
|
<span translate>Add me</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button mat-raised-button (click)="onDeleteButton()" *ngIf="isOpInList()">
|
<button mat-raised-button (click)="onDeleteButton()" *ngIf="isOpInList()">
|
||||||
<mat-icon>remove</mat-icon>
|
<mat-icon>remove</mat-icon>
|
||||||
<span translate>Remove me</span>
|
<span translate>Remove me</span>
|
||||||
@ -105,3 +115,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|
||||||
|
<mat-menu #speakerMenu="matMenu">
|
||||||
|
|
||||||
|
<button mat-menu-item *ngIf="closedList" (click)="openSpeakerList()">
|
||||||
|
<mat-icon>mic</mat-icon>
|
||||||
|
<span translate>Open list of speakers</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button mat-menu-item *ngIf="!closedList" (click)="closeSpeakerList()">
|
||||||
|
<mat-icon>mic_off</mat-icon>
|
||||||
|
<span translate>Close list of speakers</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<mat-divider *ngIf="!emptyList"></mat-divider>
|
||||||
|
|
||||||
|
<button mat-menu-item (click)="clearSpeakerList()" *ngIf="!emptyList" class="red-warning-text">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
<span trabslate>Remove all speakers</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
@ -14,6 +14,7 @@ import { BaseViewComponent } from 'app/site/base/base-view';
|
|||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { MatSnackBar } from '@angular/material';
|
import { MatSnackBar } from '@angular/material';
|
||||||
|
import { PromptService } from 'app/core/services/prompt.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of speakers for agenda items.
|
* The list of speakers for agenda items.
|
||||||
@ -54,6 +55,22 @@ export class SpeakerListComponent extends BaseViewComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
public addSpeakerForm: FormGroup;
|
public addSpeakerForm: FormGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns true if the items' speaker list is currently not open
|
||||||
|
*/
|
||||||
|
public get closedList(): boolean {
|
||||||
|
return this.viewItem && this.viewItem.item.speaker_list_closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get emptyList(): boolean {
|
||||||
|
if (this.speakers && this.speakers.length) {
|
||||||
|
return false;
|
||||||
|
} else if (this.finishedSpeakers && this.finishedSpeakers.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.activeSpeaker ? false : true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for speaker list component
|
* Constructor for speaker list component
|
||||||
* @param title
|
* @param title
|
||||||
@ -71,7 +88,8 @@ export class SpeakerListComponent extends BaseViewComponent implements OnInit {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private DS: DataStoreService,
|
private DS: DataStoreService,
|
||||||
private itemRepo: AgendaRepositoryService,
|
private itemRepo: AgendaRepositoryService,
|
||||||
private op: OperatorService
|
private op: OperatorService,
|
||||||
|
private promptService: PromptService
|
||||||
) {
|
) {
|
||||||
super(title, translate, snackBar);
|
super(title, translate, snackBar);
|
||||||
this.addSpeakerForm = new FormGroup({ user_id: new FormControl([]) });
|
this.addSpeakerForm = new FormGroup({ user_id: new FormControl([]) });
|
||||||
@ -117,7 +135,8 @@ export class SpeakerListComponent extends BaseViewComponent implements OnInit {
|
|||||||
|
|
||||||
this.speakers = allSpeakers.filter(speaker => speaker.state === SpeakerState.WAITING);
|
this.speakers = allSpeakers.filter(speaker => speaker.state === SpeakerState.WAITING);
|
||||||
this.finishedSpeakers = allSpeakers.filter(speaker => speaker.state === SpeakerState.FINISHED);
|
this.finishedSpeakers = allSpeakers.filter(speaker => speaker.state === SpeakerState.FINISHED);
|
||||||
this.activeSpeaker = allSpeakers.find(speaker => speaker.state === SpeakerState.CURRENT);
|
const currentSpeaker = allSpeakers.find(speaker => speaker.state === SpeakerState.CURRENT);
|
||||||
|
this.activeSpeaker = currentSpeaker ? currentSpeaker : null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -190,4 +209,41 @@ export class SpeakerListComponent extends BaseViewComponent implements OnInit {
|
|||||||
public hasSpokenCount(speaker: ViewSpeaker): number {
|
public hasSpokenCount(speaker: ViewSpeaker): number {
|
||||||
return this.finishedSpeakers.filter(finishedSpeaker => finishedSpeaker.user.id === speaker.user.id).length;
|
return this.finishedSpeakers.filter(finishedSpeaker => finishedSpeaker.user.id === speaker.user.id).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the current speaker list
|
||||||
|
*/
|
||||||
|
public closeSpeakerList(): Promise<void> {
|
||||||
|
if (!this.viewItem.item.speaker_list_closed) {
|
||||||
|
return this.itemRepo.update({ speaker_list_closed: true }, this.viewItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the speaker list for the current item
|
||||||
|
*/
|
||||||
|
public openSpeakerList(): Promise<void> {
|
||||||
|
if (this.viewItem.item.speaker_list_closed) {
|
||||||
|
return this.itemRepo.update({ speaker_list_closed: false }, this.viewItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the speaker list by removing all current, past and future speakers
|
||||||
|
* after a confirmation dialog
|
||||||
|
*/
|
||||||
|
public async clearSpeakerList(): Promise<void> {
|
||||||
|
const content = this.translate.instant('This will clear all speakers from the list.');
|
||||||
|
if (await this.promptService.open('Are you sure?', content)) {
|
||||||
|
this.speakers.forEach(speaker => {
|
||||||
|
this.itemRepo.deleteSpeaker(this.viewItem.item, speaker.id);
|
||||||
|
});
|
||||||
|
this.finishedSpeakers.forEach(speaker => {
|
||||||
|
this.itemRepo.deleteSpeaker(this.viewItem.item, speaker.id);
|
||||||
|
});
|
||||||
|
if (this.activeSpeaker) {
|
||||||
|
this.itemRepo.deleteSpeaker(this.viewItem.item, this.activeSpeaker.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,6 +125,26 @@ export class AgendaRepositoryService extends BaseRepository<ViewItem, Item> {
|
|||||||
await this.httpService.delete(restUrl);
|
await this.httpService.delete(restUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the current speaker
|
||||||
|
*
|
||||||
|
* @param agenda the target agenda item
|
||||||
|
*/
|
||||||
|
public async closeSpeakerList(agenda: Item): Promise<void> {
|
||||||
|
const restUrl = `rest/agenda/item/${agenda.id}/speak/`;
|
||||||
|
await this.httpService.delete(restUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the current speaker
|
||||||
|
*
|
||||||
|
* @param agenda the target agenda item
|
||||||
|
*/
|
||||||
|
public async openSpeakerList(agenda: Item): Promise<void> {
|
||||||
|
const restUrl = `rest/agenda/item/${agenda.id}/speak/`;
|
||||||
|
await this.httpService.delete(restUrl);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the current speaker
|
* Marks the current speaker
|
||||||
*
|
*
|
||||||
|
@ -159,9 +159,10 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV
|
|||||||
if not isinstance(marked, bool):
|
if not isinstance(marked, bool):
|
||||||
raise ValidationError({"detail": "Marked has to be a bool."})
|
raise ValidationError({"detail": "Marked has to be a bool."})
|
||||||
|
|
||||||
queryset = Speaker.objects.filter(item=item, user=user)
|
queryset = Speaker.objects.filter(item=item, user=user, begin_time=None)
|
||||||
try:
|
try:
|
||||||
# We assume that there aren't multiple entries because this
|
# We assume that there aren't multiple entries for speakers that
|
||||||
|
# did not yet begin to speak, because this
|
||||||
# is forbidden by the Manager's add method. We assume that
|
# is forbidden by the Manager's add method. We assume that
|
||||||
# there is only one speaker instance or none.
|
# there is only one speaker instance or none.
|
||||||
speaker = queryset.get()
|
speaker = queryset.get()
|
||||||
|
Loading…
Reference in New Issue
Block a user