From 46d6e0fc311917411f4bac58c20f670a20108178 Mon Sep 17 00:00:00 2001 From: Bastian Rihm Date: Mon, 8 Apr 2024 11:10:21 +0200 Subject: [PATCH] Add option to customize autopilot content (#3488) --- .../gateways/repositories/base-repository.ts | 26 ++++----- .../pages/autopilot/autopilot.module.ts | 10 +++- .../autopilot-settings.component.html | 17 ++++++ .../autopilot-settings.component.scss | 0 .../autopilot-settings.component.spec.ts | 24 ++++++++ .../autopilot-settings.component.ts | 57 +++++++++++++++++++ .../autopilot/autopilot.component.html | 54 +++++++++++++++--- .../autopilot/autopilot.component.ts | 20 ++++++- .../poll-collection.component.html | 14 +++-- .../poll-collection.component.ts | 6 ++ .../autopilot/services/autopilot.service.ts | 25 ++++++++ 11 files changed, 223 insertions(+), 30 deletions(-) create mode 100644 client/src/app/site/pages/meetings/pages/autopilot/components/autopilot-settings/autopilot-settings.component.html create mode 100644 client/src/app/site/pages/meetings/pages/autopilot/components/autopilot-settings/autopilot-settings.component.scss create mode 100644 client/src/app/site/pages/meetings/pages/autopilot/components/autopilot-settings/autopilot-settings.component.spec.ts create mode 100644 client/src/app/site/pages/meetings/pages/autopilot/components/autopilot-settings/autopilot-settings.component.ts create mode 100644 client/src/app/site/pages/meetings/pages/autopilot/services/autopilot.service.ts diff --git a/client/src/app/gateways/repositories/base-repository.ts b/client/src/app/gateways/repositories/base-repository.ts index 79d5f02650..e63c63bcbf 100644 --- a/client/src/app/gateways/repositories/base-repository.ts +++ b/client/src/app/gateways/repositories/base-repository.ts @@ -434,11 +434,10 @@ export abstract class BaseRepository { + const sortListService = this.sortListServices[key]; this.updateForeignBaseKeys(key); - await this.sortListServices[key].hasLoaded; - this.sortedViewModelLists[key] = await this.sortListServices[key].sort( - Object.values(this.viewModelStore) - ); + await sortListService.hasLoaded; + this.sortedViewModelLists[key] = await sortListService.sort(Object.values(this.viewModelStore)); }, type: PipelineActionType.General, key @@ -590,9 +589,10 @@ export abstract class BaseRepository { + const sortListService = this.sortListServices[key]; this.updateForeignBaseKeys(key); - await this.sortListServices[key].hasLoaded; - this.sortedViewModelLists[key] = await this.sortListServices[key].sort(this.sortedViewModelLists[key]); + await sortListService.hasLoaded; + this.sortedViewModelLists[key] = await sortListService.sort(this.sortedViewModelLists[key]); }, type: PipelineActionType.Reset, key @@ -603,19 +603,17 @@ export abstract class BaseRepository { (this.foreignSortBaseKeySubscriptions[key] ?? []).forEach(subscr => subscr.unsubscribe()); - await this.sortListServices[key].hasLoaded; - this.foreignSortBaseKeys[key] = this.sortListServices[key].currentForeignSortBaseKeys; + + const sortListService = this.sortListServices[key]; + await sortListService.hasLoaded; + this.foreignSortBaseKeys[key] = sortListService.currentForeignSortBaseKeys; this.foreignSortBaseKeySubscriptions[key] = Object.keys(this.foreignSortBaseKeys[key]).map(collection => this.repositoryServiceCollector.getNewKeyUpdatesObservable(collection).subscribe(async keys => { if (this.foreignSortBaseKeys[key][collection].some(key => keys.includes(key))) { - this.sortedViewModelLists[key] = await this.sortListServices[key].sort( - this.sortedViewModelLists[key] - ); + this.sortedViewModelLists[key] = await sortListService.sort(this.sortedViewModelLists[key]); const resortAction = { funct: async () => { - this.sortedViewModelLists[key] = await this.sortListServices[key].sort( - this.sortedViewModelLists[key] - ); + this.sortedViewModelLists[key] = await sortListService.sort(this.sortedViewModelLists[key]); }, type: PipelineActionType.Resort, key diff --git a/client/src/app/site/pages/meetings/pages/autopilot/autopilot.module.ts b/client/src/app/site/pages/meetings/pages/autopilot/autopilot.module.ts index 212ff6d793..53646218d0 100644 --- a/client/src/app/site/pages/meetings/pages/autopilot/autopilot.module.ts +++ b/client/src/app/site/pages/meetings/pages/autopilot/autopilot.module.ts @@ -1,11 +1,13 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { ReactiveFormsModule } from '@angular/forms'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatBadgeModule } from '@angular/material/badge'; import { MatCardModule } from '@angular/material/card'; import { MatDialogModule } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; +import { MatLegacyCheckboxModule as MatCheckboxModule } from '@angular/material/legacy-checkbox'; import { MatLegacyMenuModule as MatMenuModule } from '@angular/material/legacy-menu'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatTabsModule } from '@angular/material/tabs'; @@ -28,25 +30,29 @@ import { InteractionServiceModule } from '../interaction/services/interaction-se import { AutopilotRoutingModule } from './autopilot-routing.module'; import { AutopilotComponent } from './components/autopilot/autopilot.component'; import { AutopilotMainComponent } from './components/autopilot-main/autopilot-main.component'; +import { AutopilotSettingsComponent } from './components/autopilot-settings/autopilot-settings.component'; import { PollCollectionComponent } from './components/poll-collection/poll-collection.component'; @NgModule({ - declarations: [AutopilotMainComponent, AutopilotComponent, PollCollectionComponent], + declarations: [AutopilotMainComponent, AutopilotSettingsComponent, AutopilotComponent, PollCollectionComponent], imports: [ CommonModule, RouterModule, AutopilotRoutingModule, PromptDialogModule, InteractionServiceModule, + FormsModule, ProjectorModule, DirectivesModule, MatCardModule, + MatCheckboxModule, MatProgressBarModule, MatTabsModule, MatTooltipModule, MatFormFieldModule, MatIconModule, MatDialogModule, + MatBadgeModule, ListOfSpeakersContentModule, HeadBarModule, CountdownTimeModule, diff --git a/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot-settings/autopilot-settings.component.html b/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot-settings/autopilot-settings.component.html new file mode 100644 index 0000000000..2cbf0528ce --- /dev/null +++ b/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot-settings/autopilot-settings.component.html @@ -0,0 +1,17 @@ +

Enabled content elements

+
+
+ + {{ option.description | translate }} + +
+ + This settings are only applied locally. +
+ +
+ +
diff --git a/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot-settings/autopilot-settings.component.scss b/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot-settings/autopilot-settings.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot-settings/autopilot-settings.component.spec.ts b/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot-settings/autopilot-settings.component.spec.ts new file mode 100644 index 0000000000..483e0140a9 --- /dev/null +++ b/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot-settings/autopilot-settings.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AutopilotSettingsComponent } from './autopilot-settings.component'; + +xdescribe(`AutopilotSettingsComponent`, () => { + let component: AutopilotSettingsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [AutopilotSettingsComponent] + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AutopilotSettingsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it(`should create`, () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot-settings/autopilot-settings.component.ts b/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot-settings/autopilot-settings.component.ts new file mode 100644 index 0000000000..e17ec74bb9 --- /dev/null +++ b/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot-settings/autopilot-settings.component.ts @@ -0,0 +1,57 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { MatDialogRef } from '@angular/material/dialog'; +import { marker as _ } from '@colsen1991/ngx-translate-extract-marker'; +import { first } from 'rxjs'; + +import { BaseMeetingComponent } from '../../../../base/base-meeting.component'; +import { AutopilotService } from '../../services/autopilot.service'; + +@Component({ + selector: `os-autopilot-settings`, + templateUrl: `./autopilot-settings.component.html`, + styleUrls: [`./autopilot-settings.component.scss`], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AutopilotSettingsComponent extends BaseMeetingComponent implements OnInit { + public readonly autopilotContentElements: { key: string; description: string }[] = [ + { key: `title`, description: _(`Autopilot content title`) }, + { key: `list-of-speakers`, description: _(`List of speakers`) }, + { key: `moderation-note`, description: _(`Moderation note`) }, + { key: `poll-finished`, description: _(`Poll results`) }, + { key: `poll-running`, description: _(`Running poll`) }, + { key: `projector`, description: _(`Projector`) }, + { key: `speaking-times`, description: _(`Speaking times`) } + ]; + + public disabledAutopilotContentElements: { [key: string]: boolean } = {}; + + public constructor( + private dialogRef: MatDialogRef, + private cd: ChangeDetectorRef, + private autopilotService: AutopilotService + ) { + super(); + } + + public ngOnInit(): void { + this.autopilotService.disabledContentElements.pipe(first()).subscribe(keys => { + this.disabledAutopilotContentElements = Object.assign( + this.autopilotContentElements.mapToObject(el => ({ [el.key]: false })), + keys + ); + this.cd.markForCheck(); + }); + } + + public isDisabled(key: string): boolean { + return this.disabledAutopilotContentElements[key] === true; + } + + public updateDisabled(key: string, status: boolean): void { + this.autopilotService.updateContentElementVisibility(key, !status); + } + + public close(): void { + this.dialogRef.close(); + } +} diff --git a/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot/autopilot.component.html b/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot/autopilot.component.html index aaa3926ca7..4b60c3797e 100644 --- a/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot/autopilot.component.html +++ b/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot/autopilot.component.html @@ -2,12 +2,31 @@

{{ 'Autopilot' | translate }}

+ + + + +
- +

@@ -21,7 +40,7 @@

-
+
-
-
+
diff --git a/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot/autopilot.component.ts b/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot/autopilot.component.ts index a9c3ec8039..802738a3c0 100644 --- a/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot/autopilot.component.ts +++ b/client/src/app/site/pages/meetings/pages/autopilot/components/autopilot/autopilot.component.ts @@ -1,5 +1,6 @@ import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { Component, OnInit } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject } from 'rxjs'; import { HasProjectorTitle } from 'src/app/domain/interfaces'; @@ -15,6 +16,8 @@ import { ListOfSpeakersControllerService } from '../../../agenda/modules/list-of import { HasPolls } from '../../../polls'; import { ViewProjection, ViewProjector } from '../../../projectors'; import { ProjectorControllerService } from '../../../projectors/services/projector-controller.service'; +import { AutopilotService } from '../../services/autopilot.service'; +import { AutopilotSettingsComponent } from '../autopilot-settings/autopilot-settings.component'; @Component({ selector: `os-autopilot`, @@ -95,8 +98,14 @@ export class AutopilotComponent extends BaseMeetingComponent implements OnInit { } } + public get numDisabledElements(): number { + return Object.values(this.disabledContentElements).filter(v => v).length; + } + public structureLevelCountdownEnabled = false; + public disabledContentElements: { [key: string]: boolean } = {}; + public showRightCol: BehaviorSubject = new BehaviorSubject(false); private _currentProjection: ViewProjection | null = null; @@ -104,14 +113,19 @@ export class AutopilotComponent extends BaseMeetingComponent implements OnInit { public constructor( protected override translate: TranslateService, private operator: OperatorService, + private autopilotService: AutopilotService, projectorRepo: ProjectorControllerService, closService: CurrentListOfSpeakersService, private listOfSpeakersRepo: ListOfSpeakersControllerService, - breakpoint: BreakpointObserver + breakpoint: BreakpointObserver, + private dialog: MatDialog ) { super(); this.subscriptions.push( + this.autopilotService.disabledContentElements.subscribe(keys => { + this.disabledContentElements = keys || {}; + }), projectorRepo.getReferenceProjectorObservable().subscribe(refProjector => { if (refProjector) { this.projector = refProjector; @@ -150,4 +164,8 @@ export class AutopilotComponent extends BaseMeetingComponent implements OnInit { public get hasCurrentProjection(): boolean { return !!this._currentProjection; } + + public customizeAutopilot(): void { + this.dialog.open(AutopilotSettingsComponent); + } } diff --git a/client/src/app/site/pages/meetings/pages/autopilot/components/poll-collection/poll-collection.component.html b/client/src/app/site/pages/meetings/pages/autopilot/components/poll-collection/poll-collection.component.html index b5aaf28f53..71aa7885ee 100644 --- a/client/src/app/site/pages/meetings/pages/autopilot/components/poll-collection/poll-collection.component.html +++ b/client/src/app/site/pages/meetings/pages/autopilot/components/poll-collection/poll-collection.component.html @@ -1,11 +1,13 @@ - + + + diff --git a/client/src/app/site/pages/meetings/pages/autopilot/components/poll-collection/poll-collection.component.ts b/client/src/app/site/pages/meetings/pages/autopilot/components/poll-collection/poll-collection.component.ts index baae140bf6..c591d17726 100644 --- a/client/src/app/site/pages/meetings/pages/autopilot/components/poll-collection/poll-collection.component.ts +++ b/client/src/app/site/pages/meetings/pages/autopilot/components/poll-collection/poll-collection.component.ts @@ -27,6 +27,12 @@ export class PollCollectionComponent extends BaseCo private _currentProjection: (Partial> & { readonly fqid: string }) | null = null; + @Input() + public disableRunning = false; + + @Input() + public disableFinished = false; + @Input() public set currentProjection(viewModel: (Partial> & { readonly fqid: string }) | null) { this._currentProjection = viewModel; diff --git a/client/src/app/site/pages/meetings/pages/autopilot/services/autopilot.service.ts b/client/src/app/site/pages/meetings/pages/autopilot/services/autopilot.service.ts new file mode 100644 index 0000000000..06a8ca63ba --- /dev/null +++ b/client/src/app/site/pages/meetings/pages/autopilot/services/autopilot.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { StorageService } from 'src/app/gateways/storage.service'; + +@Injectable({ providedIn: `root` }) +export class AutopilotService { + private disabledContentElementsSubject = new BehaviorSubject<{ [key: string]: boolean }>({}); + + public get disabledContentElements(): Observable<{ [key: string]: boolean }> { + return this.disabledContentElementsSubject; + } + + public constructor(private storage: StorageService) { + this.storage.get<{ [key: string]: boolean }>(`autopilot-disabled`).then(keys => { + this.disabledContentElementsSubject.next(keys || {}); + }); + } + + public async updateContentElementVisibility(key: string, status: boolean): Promise { + const disabledContentElements = this.disabledContentElementsSubject.getValue() || {}; + disabledContentElements[key] = status; + this.disabledContentElementsSubject.next(disabledContentElements); + await this.storage.set(`autopilot-disabled`, disabledContentElements); + } +}