filter potential speakers
- filter by those already on list - filter by present, if configured
This commit is contained in:
parent
1599e91fa5
commit
16477a4e92
@ -109,7 +109,7 @@
|
||||
|
||||
<!-- Search for speakers -->
|
||||
<div *osPerms="'agenda.can_manage_list_of_speakers'">
|
||||
<form *ngIf="users && users.value.length > 0" [formGroup]="addSpeakerForm">
|
||||
<form *ngIf="filteredUsers && filteredUsers.value.length > 0" [formGroup]="addSpeakerForm">
|
||||
<os-search-value-selector
|
||||
class="search-users"
|
||||
ngDefaultControl
|
||||
@ -117,7 +117,7 @@
|
||||
[formControl]="addSpeakerForm.get('user_id')"
|
||||
[multiple]="false"
|
||||
listname="{{ 'Select or search new speaker ...' | translate }}"
|
||||
[InputListValues]="users"
|
||||
[InputListValues]="filteredUsers"
|
||||
></os-search-value-selector>
|
||||
</form>
|
||||
</div>
|
||||
@ -125,7 +125,7 @@
|
||||
<!-- Add me and remove me if OP has correct permission -->
|
||||
<div *osPerms="'agenda.can_be_speaker'" class="add-self-buttons">
|
||||
<div *ngIf="speakers && !closedList">
|
||||
<button mat-stroked-button (click)="addNewSpeaker()" *ngIf="!isOpInList()">
|
||||
<button mat-stroked-button (click)="addNewSpeaker()" *ngIf="!isOpInList() && canAddSelf">
|
||||
<mat-icon>add</mat-icon>
|
||||
<span translate>Add me</span>
|
||||
</button>
|
||||
|
@ -8,18 +8,20 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
||||
|
||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||
import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service';
|
||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||
import { CurrentListOfSpeakersService } from 'app/site/projector/services/current-agenda-item.service';
|
||||
import { CurrentListOfSpeakersSlideService } from 'app/site/projector/services/current-list-of-of-speakers-slide.service';
|
||||
import { DurationService } from 'app/core/ui-services/duration.service';
|
||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||
import { ProjectorRepositoryService } from 'app/core/repositories/projector/projector-repository.service';
|
||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { ViewSpeaker, SpeakerState } from '../../models/view-speaker';
|
||||
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
|
||||
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 { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service';
|
||||
import { CurrentListOfSpeakersService } from 'app/site/projector/services/current-agenda-item.service';
|
||||
import { CurrentListOfSpeakersSlideService } from 'app/site/projector/services/current-list-of-of-speakers-slide.service';
|
||||
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||
|
||||
import { ListOfSpeakersRepositoryService } from 'app/core/repositories/agenda/list-of-speakers-repository.service';
|
||||
import { ViewListOfSpeakers } from '../../models/view-list-of-speakers';
|
||||
|
||||
@ -70,7 +72,12 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
|
||||
/**
|
||||
* Hold the users
|
||||
*/
|
||||
public users: BehaviorSubject<ViewUser[]>;
|
||||
public users = new BehaviorSubject<ViewUser[]>([]);
|
||||
|
||||
/**
|
||||
* A filtered list of users, excluding those not available to be added to the list
|
||||
*/
|
||||
public filteredUsers = new BehaviorSubject<ViewUser[]>([]);
|
||||
|
||||
/**
|
||||
* Required for the user search selector
|
||||
@ -93,6 +100,13 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
|
||||
return !this.activeSpeaker;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns true if the current user can be added to the list of speakers
|
||||
*/
|
||||
public get canAddSelf(): boolean {
|
||||
return !this.config.instant('agenda_present_speakers_only') || this.operator.user.is_present;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to detect changes in the projector reference.
|
||||
*/
|
||||
@ -128,7 +142,8 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
|
||||
private durationService: DurationService,
|
||||
private userRepository: UserRepositoryService,
|
||||
private collectionStringMapper: CollectionStringMapperService,
|
||||
private currentListOfSpeakersSlideService: CurrentListOfSpeakersSlideService
|
||||
private currentListOfSpeakersSlideService: CurrentListOfSpeakersSlideService,
|
||||
private config: ConfigService
|
||||
) {
|
||||
super(title, translate, snackBar);
|
||||
this.addSpeakerForm = new FormGroup({ user_id: new FormControl([]) });
|
||||
@ -160,15 +175,27 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
|
||||
}
|
||||
|
||||
// load and observe users
|
||||
this.users = this.userRepository.getViewModelListBehaviorSubject();
|
||||
|
||||
this.subscriptions.push(
|
||||
/* List of eligible users */
|
||||
this.userRepository.getViewModelListObservable().subscribe(users => {
|
||||
this.users.next(users);
|
||||
this.filterUsers();
|
||||
})
|
||||
);
|
||||
this.subscriptions.push(
|
||||
// detect changes in the form
|
||||
this.addSpeakerForm.valueChanges.subscribe(formResult => {
|
||||
// resetting a form triggers a form.next(null) - check if user_id
|
||||
if (formResult && formResult.user_id) {
|
||||
this.addNewSpeaker(formResult.user_id);
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
this.subscriptions.push(
|
||||
this.config.get('agenda_present_speakers_only').subscribe(() => {
|
||||
this.filterUsers();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public opCanManage(): boolean {
|
||||
@ -224,6 +251,9 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
|
||||
this.viewListOfSpeakers = listOfSpeakers;
|
||||
const allSpeakers = this.viewListOfSpeakers.speakers.sort((a, b) => a.weight - b.weight);
|
||||
this.speakers = allSpeakers.filter(speaker => speaker.state === SpeakerState.WAITING);
|
||||
// Since the speaker repository is not a normal repository, sorting cannot be handled there
|
||||
this.speakers.sort((a: ViewSpeaker, b: ViewSpeaker) => a.weight - b.weight);
|
||||
this.filterUsers();
|
||||
this.finishedSpeakers = allSpeakers.filter(speaker => speaker.state === SpeakerState.FINISHED);
|
||||
|
||||
// convert begin time to date and sort
|
||||
@ -276,15 +306,25 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
|
||||
*
|
||||
* @param speaker the speaker marked in the list
|
||||
*/
|
||||
public onStartButton(speaker: ViewSpeaker): void {
|
||||
this.listOfSpeakersRepo.startSpeaker(this.viewListOfSpeakers, speaker).then(null, this.raiseError);
|
||||
public async onStartButton(speaker: ViewSpeaker): Promise<void> {
|
||||
try {
|
||||
await this.listOfSpeakersRepo.startSpeaker(this.viewListOfSpeakers, speaker);
|
||||
this.filterUsers();
|
||||
} catch (e) {
|
||||
this.raiseError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Click on the mic-cross button
|
||||
*/
|
||||
public onStopButton(): void {
|
||||
this.listOfSpeakersRepo.stopCurrentSpeaker(this.viewListOfSpeakers).then(null, this.raiseError);
|
||||
public async onStopButton(): Promise<void> {
|
||||
try {
|
||||
await this.listOfSpeakersRepo.stopCurrentSpeaker(this.viewListOfSpeakers);
|
||||
this.filterUsers();
|
||||
} catch (e) {
|
||||
this.raiseError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -299,14 +339,18 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
|
||||
}
|
||||
|
||||
/**
|
||||
* Click on the X button
|
||||
* Click on the X button - removes the speaker from the list of speakers
|
||||
*
|
||||
* @param speaker
|
||||
* @param speaker optional speaker to remove. If none is given,
|
||||
* the operator themself is removed
|
||||
*/
|
||||
public onDeleteButton(speaker?: ViewSpeaker): void {
|
||||
this.listOfSpeakersRepo
|
||||
.delete(this.viewListOfSpeakers, speaker ? speaker.id : null)
|
||||
.then(null, this.raiseError);
|
||||
public async onDeleteButton(speaker?: ViewSpeaker): Promise<void> {
|
||||
try {
|
||||
await this.listOfSpeakersRepo.delete(this.viewListOfSpeakers, speaker ? speaker.id : null);
|
||||
this.filterUsers();
|
||||
} catch (e) {
|
||||
this.raiseError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -385,4 +429,18 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
|
||||
);
|
||||
return `${this.durationService.durationToString(duration, 'm')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers an update of the filter for the list of available potential speakers
|
||||
* (triggered on an update of users or config)
|
||||
*/
|
||||
private filterUsers(): void {
|
||||
const presentUsersOnly = this.config.instant('agenda_present_speakers_only');
|
||||
const users = presentUsersOnly ? this.users.getValue().filter(u => u.is_present) : this.users.getValue();
|
||||
if (!this.speakers || !this.speakers.length) {
|
||||
this.filteredUsers.next(users);
|
||||
} else {
|
||||
this.filteredUsers.next(users.filter(u => !this.speakers.some(speaker => speaker.user.id === u.id)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,14 +47,14 @@ export class ViewSpeaker implements Updateable, Identifiable {
|
||||
/**
|
||||
* @returns an ISO datetime string or null
|
||||
*/
|
||||
public get begin_time(): string {
|
||||
public get begin_time(): string | null {
|
||||
return this.speaker.begin_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns an ISO datetime string or null
|
||||
*/
|
||||
public get end_time(): string {
|
||||
public get end_time(): string | null {
|
||||
return this.speaker.end_time;
|
||||
}
|
||||
|
||||
|
@ -115,3 +115,14 @@ def get_config_variables():
|
||||
group="Agenda",
|
||||
subgroup="List of speakers",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="agenda_present_speakers_only",
|
||||
default_value=False,
|
||||
input_type="boolean",
|
||||
label="Only present users can be on the list of speakers",
|
||||
help_text="Users without the status 'present' will not be available for any list of speakers.",
|
||||
weight=250,
|
||||
group="Agenda",
|
||||
subgroup="List of speakers",
|
||||
)
|
||||
|
@ -422,6 +422,8 @@ class SpeakerManager(models.Manager):
|
||||
raise OpenSlidesError(f"{user} is already on the list of speakers.")
|
||||
if isinstance(user, AnonymousUser):
|
||||
raise OpenSlidesError("An anonymous user can not be on lists of speakers.")
|
||||
if config["agenda_present_speakers_only"] and not user.is_present:
|
||||
raise OpenSlidesError("Only present users can be on the lists of speakers.")
|
||||
weight = (
|
||||
self.filter(list_of_speakers=list_of_speakers).aggregate(
|
||||
models.Max("weight")
|
||||
|
Loading…
Reference in New Issue
Block a user