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">
|
||||
<!-- Title -->
|
||||
<div class="title-slot">
|
||||
<h2>
|
||||
<span translate>List of speakers</span>
|
||||
</h2>
|
||||
<h2><span translate>List of speakers</span></h2>
|
||||
</div>
|
||||
<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>
|
||||
</os-head-bar>
|
||||
|
||||
@ -27,8 +30,12 @@
|
||||
<!-- <span translate>minutes</span> -->
|
||||
<span> (</span> <span translate>Start time</span> <span>: {{ speaker.begin_time }})</span>
|
||||
</div>
|
||||
<button mat-stroked-button matTooltip="{{ 'Remove' | translate }}"
|
||||
*osPerms="'agenda.can_manage_list_of_speakers'" (click)="onDeleteButton(speaker)">
|
||||
<button
|
||||
mat-stroked-button
|
||||
matTooltip="{{ 'Remove' | translate }}"
|
||||
*osPerms="'agenda.can_manage_list_of_speakers'"
|
||||
(click)="onDeleteButton(speaker)"
|
||||
>
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</mat-list-item>
|
||||
@ -40,30 +47,34 @@
|
||||
<mat-icon class="speaking-icon">play_arrow</mat-icon>
|
||||
<span class="speaking-name">{{ activeSpeaker }}</span>
|
||||
|
||||
<button mat-stroked-button matTooltip="{{ 'End speech' | translate }}"
|
||||
*osPerms="'agenda.can_manage_list_of_speakers'" (click)="onStopButton()">
|
||||
<button
|
||||
mat-stroked-button
|
||||
matTooltip="{{ 'End speech' | translate }}"
|
||||
*osPerms="'agenda.can_manage_list_of_speakers'"
|
||||
(click)="onStopButton()"
|
||||
>
|
||||
<mat-icon>mic_off</mat-icon>
|
||||
<span translate>Stop</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Waiting speakers -->
|
||||
<div *osPerms="'agenda.can_manage_list_of_speakers'">
|
||||
<div>
|
||||
<div class="waiting-list" *ngIf="speakers && speakers.length > 0">
|
||||
<os-sorting-list [input]="speakers" [live]="true" [count]="true" (sortEvent)="onSortingChange($event)">
|
||||
<!-- implicit item references into the component using ng-template slot -->
|
||||
<ng-template let-item>
|
||||
<span *osPerms="'agenda.can_manage_list_of_speakers'">
|
||||
<span *ngIf="hasSpokenCount(item)" class="red-warning-text speaker-warning">
|
||||
<span translate>Call</span><span> {{ hasSpokenCount(item) + 1 }}</span>
|
||||
</span>
|
||||
<mat-button-toggle-group>
|
||||
<mat-button-toggle matTooltip="{{ 'Begin speech' | translate }}"
|
||||
(click)="onStartButton(item)">
|
||||
</span>
|
||||
<mat-button-toggle-group *osPerms="'agenda.can_manage_list_of_speakers'">
|
||||
<mat-button-toggle matTooltip="{{ 'Begin speech' | translate }}" (click)="onStartButton(item)">
|
||||
<mat-icon>mic</mat-icon>
|
||||
<span translate>Start</span>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle matTooltip="{{ 'Mark speaker' | translate }}"
|
||||
(click)="onMarkButton(item)">
|
||||
<mat-button-toggle matTooltip="{{ 'Mark speaker' | translate }}" (click)="onMarkButton(item)">
|
||||
<mat-icon>{{ item.marked ? 'star' : 'star_border' }}</mat-icon>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle matTooltip="{{ 'Remove' | translate }}" (click)="onDeleteButton(item)">
|
||||
@ -92,12 +103,11 @@
|
||||
|
||||
<!-- Add me and remove me if OP has correct permission -->
|
||||
<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()">
|
||||
<mat-icon>add</mat-icon>
|
||||
<span translate>Add me</span>
|
||||
</button>
|
||||
|
||||
<button mat-raised-button (click)="onDeleteButton()" *ngIf="isOpInList()">
|
||||
<mat-icon>remove</mat-icon>
|
||||
<span translate>Remove me</span>
|
||||
@ -105,3 +115,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</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 { TranslateService } from '@ngx-translate/core';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
import { PromptService } from 'app/core/services/prompt.service';
|
||||
|
||||
/**
|
||||
* The list of speakers for agenda items.
|
||||
@ -54,6 +55,22 @@ export class SpeakerListComponent extends BaseViewComponent implements OnInit {
|
||||
*/
|
||||
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
|
||||
* @param title
|
||||
@ -71,7 +88,8 @@ export class SpeakerListComponent extends BaseViewComponent implements OnInit {
|
||||
private route: ActivatedRoute,
|
||||
private DS: DataStoreService,
|
||||
private itemRepo: AgendaRepositoryService,
|
||||
private op: OperatorService
|
||||
private op: OperatorService,
|
||||
private promptService: PromptService
|
||||
) {
|
||||
super(title, translate, snackBar);
|
||||
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.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 {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
|
@ -159,9 +159,10 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV
|
||||
if not isinstance(marked, 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:
|
||||
# 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
|
||||
# there is only one speaker instance or none.
|
||||
speaker = queryset.get()
|
||||
|
Loading…
Reference in New Issue
Block a user