Skip to content

Commit

Permalink
Add option to customize autopilot content (#3488)
Browse files Browse the repository at this point in the history
  • Loading branch information
bastianjoel authored Apr 8, 2024
1 parent 3479aed commit 46d6e0f
Show file tree
Hide file tree
Showing 11 changed files with 223 additions and 30 deletions.
26 changes: 12 additions & 14 deletions client/src/app/gateways/repositories/base-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,11 +434,10 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
this.sortListServices[key] = sortService;
this.pushToPipeline({
funct: async () => {
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
Expand Down Expand Up @@ -590,9 +589,10 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
private resortAndUpdateForeignBaseKeys(key: string): void {
const resortAction = {
funct: async () => {
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
Expand All @@ -603,19 +603,17 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode

private async updateForeignBaseKeys(key: string): Promise<void> {
(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
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<h1 mat-dialog-title translate>Enabled content elements</h1>
<div mat-dialog-content>
<div *ngFor="let option of autopilotContentElements" class="option">
<mat-checkbox
(ngModelChange)="updateDisabled(option.key, $event)"
[ngModel]="!disabledAutopilotContentElements[option.key]"
>
{{ option.description | translate }}
</mat-checkbox>
</div>

<small translate>This settings are only applied locally.</small>
</div>

<div mat-dialog-actions>
<button mat-button (click)="close()" translate>Close</button>
</div>
Original file line number Diff line number Diff line change
@@ -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<AutopilotSettingsComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AutopilotSettingsComponent]
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(AutopilotSettingsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it(`should create`, () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -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<AutopilotSettingsComponent>,
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,31 @@
<div class="title-slot">
<h2>{{ 'Autopilot' | translate }}</h2>
</div>

<!-- Menu -->
<ng-container class="menu-slot">
<button
type="button"
mat-icon-button
[matTooltip]="'Customize autopilot' | translate"
(click)="customizeAutopilot()"
>
<mat-icon
[matBadge]="numDisabledElements"
[matBadgeHidden]="numDisabledElements < 1"
matBadgeSize="small"
matBadgeColor="warn"
>
settings
</mat-icon>
</button>
</ng-container>
</os-head-bar>
<div class="content-container">
<div class="col-wrapper">
<div class="col-main">
<!-- Title Card -->
<mat-card class="os-card" *ngIf="!!title">
<mat-card class="os-card" *ngIf="!!title && disabledContentElements['title'] !== true">
<mat-card-content>
<a [routerLink]="viewModelUrl || null" [target]="lowerProjectionTarget" [state]="{ back: 'true' }">
<h1 class="line-and-icon">
Expand All @@ -21,7 +40,7 @@ <h1 class="line-and-icon">
<os-list-of-speakers-content
*osPerms="
[permission.listOfSpeakersCanSee, permission.listOfSpeakersCanBeSpeaker];
and: !!listOfSpeakers
and: !!listOfSpeakers && disabledContentElements['list-of-speakers'] !== true
"
[listOfSpeakers]="listOfSpeakers!"
(canReaddLastSpeakerEvent)="canReaddLastSpeaker = $event"
Expand Down Expand Up @@ -79,24 +98,37 @@ <h1 class="line-and-icon">
</os-list-of-speakers-content>

<ng-container *osPerms="permission.listOfSpeakersCanSee">
<div *ngIf="structureLevelCountdownEnabled && listOfSpeakers && (showRightCol | async) === false">
<div
*ngIf="
structureLevelCountdownEnabled &&
listOfSpeakers &&
(showRightCol | async) === false &&
disabledContentElements['speaking-times'] !== true
"
>
<ng-container [ngTemplateOutlet]="speakingTimes"></ng-container>
</div>
</ng-container>

<!-- Moderator Note-->
<div *ngIf="hasCurrentProjection">
<div *ngIf="hasCurrentProjection && disabledContentElements['moderation-note'] !== true">
<os-moderation-note [listOfSpeakers]="listOfSpeakers"></os-moderation-note>
</div>

<!-- Poll-Collection -->
<os-poll-collection
*ngIf="showPollCollection"
*ngIf="
showPollCollection &&
(disabledContentElements['poll-finished'] !== true ||
disabledContentElements['poll-running'] !== true)
"
[disableRunning]="disabledContentElements['poll-running'] === true"
[disableFinished]="disabledContentElements['poll-finished'] === true"
[currentProjection]="projectedViewModel"
></os-poll-collection>

<!-- Projector -->
<mat-card class="os-card spacer-bottom-60">
<mat-card *ngIf="disabledContentElements['projector'] !== true" class="os-card spacer-bottom-60">
<mat-card-content>
<a [routerLink]="projectorUrl" [target]="projectionTarget" [state]="{ back: 'true' }">
<p class="subtitle-text">{{ projectorTitle | translate }}</p>
Expand All @@ -112,7 +144,15 @@ <h1 class="line-and-icon">
</div>

<ng-container *osPerms="permission.listOfSpeakersCanSee">
<div *ngIf="structureLevelCountdownEnabled && listOfSpeakers && showRightCol | async" class="col-right">
<div
*ngIf="
disabledContentElements['speaking-times'] !== true &&
structureLevelCountdownEnabled &&
listOfSpeakers &&
showRightCol | async
"
class="col-right"
>
<ng-container [ngTemplateOutlet]="speakingTimes"></ng-container>
</div>
</ng-container>
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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`,
Expand Down Expand Up @@ -95,23 +98,34 @@ 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<boolean> = new BehaviorSubject(false);

private _currentProjection: ViewProjection | null = null;

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;
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
<ng-container
*ngFor="let poll of polls; trackBy: identifyPoll"
[ngTemplateOutlet]="pollArea"
[ngTemplateOutletContext]="{ poll: poll, last: false }"
></ng-container>
<ng-container *ngIf="!disableRunning">
<ng-container
*ngFor="let poll of polls; trackBy: identifyPoll"
[ngTemplateOutlet]="pollArea"
[ngTemplateOutletContext]="{ poll: poll, last: false }"
></ng-container>
</ng-container>

<ng-container
*ngIf="lastPublishedPoll && !hasProjectedModelOpenPolls"
*ngIf="lastPublishedPoll && !hasProjectedModelOpenPolls && !disableFinished"
[ngTemplateOutlet]="pollArea"
[ngTemplateOutletContext]="{ poll: lastPublishedPoll, last: true }"
></ng-container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ export class PollCollectionComponent<C extends PollContentObject> extends BaseCo

private _currentProjection: (Partial<HasPolls<C>> & { readonly fqid: string }) | null = null;

@Input()
public disableRunning = false;

@Input()
public disableFinished = false;

@Input()
public set currentProjection(viewModel: (Partial<HasPolls<C>> & { readonly fqid: string }) | null) {
this._currentProjection = viewModel;
Expand Down
Loading

0 comments on commit 46d6e0f

Please sign in to comment.