Merge pull request #4008 from tsiegleauq/speaker-indicator-in-list
Speaker indicators in Lists
This commit is contained in:
commit
759ae91aa0
@ -1,5 +1,5 @@
|
||||
import { ProjectableBaseModel } from '../base/projectable-base-model';
|
||||
import { Speaker } from './speaker';
|
||||
import { Speaker, SpeakerState } from './speaker';
|
||||
|
||||
/**
|
||||
* The representation of the content object for agenda items. The unique combination
|
||||
@ -44,6 +44,13 @@ export class Item extends ProjectableBaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount of waiting speakers
|
||||
*/
|
||||
public get speakerAmount(): number {
|
||||
return this.speakers.filter(speaker => speaker.state === SpeakerState.WAITING).length;
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
return this.title;
|
||||
}
|
||||
|
@ -1,5 +1,14 @@
|
||||
import { Deserializer } from '../base/deserializer';
|
||||
|
||||
/**
|
||||
* Determine the state of the speaker
|
||||
*/
|
||||
export enum SpeakerState {
|
||||
WAITING,
|
||||
CURRENT,
|
||||
FINISHED
|
||||
}
|
||||
|
||||
/**
|
||||
* Representation of a speaker in an agenda item.
|
||||
*
|
||||
@ -24,6 +33,22 @@ export class Speaker extends Deserializer {
|
||||
super(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns
|
||||
* - waiting if there is no begin nor end time
|
||||
* - current if there is a begin time and not end time
|
||||
* - finished if there are both begin and end time
|
||||
*/
|
||||
public get state(): SpeakerState {
|
||||
if (!this.begin_time && !this.end_time) {
|
||||
return SpeakerState.WAITING;
|
||||
} else if (this.begin_time && !this.end_time) {
|
||||
return SpeakerState.CURRENT;
|
||||
} else {
|
||||
return SpeakerState.FINISHED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting the title of a speaker does not make much sense.
|
||||
* Usually it would refer to the title of a user.
|
||||
|
@ -21,7 +21,8 @@ import {
|
||||
MatNativeDateModule,
|
||||
DateAdapter,
|
||||
MatIconModule,
|
||||
MatButtonToggleModule
|
||||
MatButtonToggleModule,
|
||||
MatBadgeModule
|
||||
} from '@angular/material';
|
||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatChipsModule } from '@angular/material';
|
||||
@ -90,6 +91,7 @@ import { SpeakerListComponent } from 'app/site/agenda/components/speaker-list/sp
|
||||
MatSnackBarModule,
|
||||
MatChipsModule,
|
||||
MatTooltipModule,
|
||||
MatBadgeModule,
|
||||
// TODO: there is an error with missing icons
|
||||
// we either wait or include a fixed version manually (dirty)
|
||||
// https://github.com/google/material-design-icons/issues/786
|
||||
@ -125,6 +127,7 @@ import { SpeakerListComponent } from 'app/site/agenda/components/speaker-list/sp
|
||||
MatSnackBarModule,
|
||||
MatChipsModule,
|
||||
MatTooltipModule,
|
||||
MatBadgeModule,
|
||||
MatIconModule,
|
||||
MatRadioModule,
|
||||
MatButtonToggleModule,
|
||||
|
@ -9,15 +9,30 @@
|
||||
<!-- title column -->
|
||||
<ng-container matColumnDef="title">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Topic</mat-header-cell>
|
||||
<mat-cell *matCellDef="let item">{{ item.getListTitle() }}</mat-cell>
|
||||
<mat-cell *matCellDef="let item" (click)="selectAgendaItem(item)">{{ item.getListTitle() }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Duration column -->
|
||||
<ng-container matColumnDef="duration">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Duration</mat-header-cell>
|
||||
<mat-cell *matCellDef="let item">{{ item.duration }}</mat-cell>
|
||||
<mat-cell *matCellDef="let item" (click)="selectAgendaItem(item)">{{ item.duration }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="['title', 'duration']"></mat-header-row>
|
||||
<mat-row (click)="selectAgendaItem(row)" *matRowDef="let row; columns: ['title', 'duration']"></mat-row>
|
||||
<!-- Speakers column -->
|
||||
<ng-container matColumnDef="speakers">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Speakers</mat-header-cell>
|
||||
<mat-cell *matCellDef="let item">
|
||||
<button mat-icon-button (click)="onSpeakerIcon(item)">
|
||||
<mat-icon
|
||||
[matBadge]="item.speakerAmount > 0 ? item.speakerAmount : null"
|
||||
matBadgeColor="accent">
|
||||
mic
|
||||
</mat-icon>
|
||||
</button>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="['title', 'duration', 'speakers']"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: ['title', 'duration', 'speakers']"></mat-row>
|
||||
</mat-table>
|
||||
<mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator>
|
||||
|
@ -0,0 +1,19 @@
|
||||
.os-listview-table {
|
||||
|
||||
/** Title */
|
||||
.mat-column-title {
|
||||
padding-left: 26px;
|
||||
flex: 1 0 200px;
|
||||
}
|
||||
|
||||
/** Duration */
|
||||
.mat-column-duration {
|
||||
flex: 0 0 100px;
|
||||
}
|
||||
|
||||
/** Speakers indicator */
|
||||
.mat-column-speakers {
|
||||
flex: 0 0 100px;
|
||||
justify-content: flex-end !important;
|
||||
}
|
||||
}
|
@ -10,13 +10,11 @@ import { AgendaRepositoryService } from '../../services/agenda-repository.servic
|
||||
|
||||
/**
|
||||
* List view for the agenda.
|
||||
*
|
||||
* TODO: Not yet implemented
|
||||
*/
|
||||
@Component({
|
||||
selector: 'os-agenda-list',
|
||||
templateUrl: './agenda-list.component.html',
|
||||
styleUrls: ['./agenda-list.component.css']
|
||||
styleUrls: ['./agenda-list.component.scss']
|
||||
})
|
||||
export class AgendaListComponent extends ListViewBaseComponent<ViewItem> implements OnInit {
|
||||
/**
|
||||
@ -64,6 +62,14 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
|
||||
this.router.navigate([contentObject.getDetailStateURL()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the speakers button
|
||||
* @param item indicates the row that was clicked on
|
||||
*/
|
||||
public onSpeakerIcon(item: ViewItem): void {
|
||||
this.router.navigate([`${item.id}/speakers`], { relativeTo: this.route });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the plus button.
|
||||
* Comes from the HeadBar Component
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ViewSpeaker, SpeakerState } from '../../models/view-speaker';
|
||||
import { User } from 'app/shared/models/users/user';
|
||||
import { FormGroup, FormControl } from '@angular/forms';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { SpeakerState } from 'app/shared/models/agenda/speaker';
|
||||
import { User } from 'app/shared/models/users/user';
|
||||
import { ViewSpeaker } from '../../models/view-speaker';
|
||||
import { DataStoreService } from 'app/core/services/data-store.service';
|
||||
import { AgendaRepositoryService } from '../../services/agenda-repository.service';
|
||||
import { ViewItem } from '../../models/view-item';
|
||||
|
@ -22,6 +22,10 @@ export class ViewItem extends BaseViewModel {
|
||||
return this.item ? this.item.duration : null;
|
||||
}
|
||||
|
||||
public get speakerAmount(): number {
|
||||
return this.item ? this.item.speakerAmount : null;
|
||||
}
|
||||
|
||||
public constructor(item: Item, contentObject: AgendaBaseModel) {
|
||||
super();
|
||||
this._item = item;
|
||||
|
@ -1,17 +1,8 @@
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
import { Speaker } from 'app/shared/models/agenda/speaker';
|
||||
import { Speaker, SpeakerState } from 'app/shared/models/agenda/speaker';
|
||||
import { User } from 'app/shared/models/users/user';
|
||||
import { Selectable } from 'app/shared/components/selectable';
|
||||
|
||||
/**
|
||||
* Determine the state of the speaker
|
||||
*/
|
||||
export enum SpeakerState {
|
||||
WAITING,
|
||||
CURRENT,
|
||||
FINISHED
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides "safe" access to a speaker with all it's components
|
||||
*/
|
||||
@ -47,20 +38,8 @@ export class ViewSpeaker extends BaseViewModel implements Selectable {
|
||||
return this.speaker ? this.speaker.end_time : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns:
|
||||
* - waiting if there is no begin nor end time
|
||||
* - current if there is a begin time and not end time
|
||||
* - finished if there are both begin and end time
|
||||
*/
|
||||
public get state(): SpeakerState {
|
||||
if (!this.begin_time && !this.end_time) {
|
||||
return SpeakerState.WAITING;
|
||||
} else if (this.begin_time && !this.end_time) {
|
||||
return SpeakerState.CURRENT;
|
||||
} else {
|
||||
return SpeakerState.FINISHED;
|
||||
}
|
||||
return this.speaker ? this.speaker.state : null;
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
|
@ -25,7 +25,7 @@
|
||||
<!-- identifier column -->
|
||||
<ng-container matColumnDef="identifier">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Identifier</mat-header-cell>
|
||||
<mat-cell *matCellDef="let motion">
|
||||
<mat-cell *matCellDef="let motion" (click)="selectMotion(motion)">
|
||||
<div class="innerTable">
|
||||
{{ motion.identifier }}
|
||||
</div>
|
||||
@ -35,7 +35,7 @@
|
||||
<!-- title column -->
|
||||
<ng-container matColumnDef="title">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Title</mat-header-cell>
|
||||
<mat-cell *matCellDef="let motion">
|
||||
<mat-cell *matCellDef="let motion" (click)="selectMotion(motion)">
|
||||
<div class="innerTable">
|
||||
<span class="motion-list-title">{{ motion.title }}</span>
|
||||
<br>
|
||||
@ -50,7 +50,7 @@
|
||||
<!-- state column -->
|
||||
<ng-container matColumnDef="state">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>State</mat-header-cell>
|
||||
<mat-cell *matCellDef="let motion">
|
||||
<mat-cell *matCellDef="let motion" (click)="selectMotion(motion)">
|
||||
<!--div *ngIf='isDisplayIcon(motion.state) && motion.state' class='innerTable'>
|
||||
<mat-icon>{{ getStateIcon(motion.state) }}</mat-icon>
|
||||
</div>-->
|
||||
@ -60,8 +60,22 @@
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Speakers column -->
|
||||
<ng-container matColumnDef="speakers">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Speakers</mat-header-cell>
|
||||
<mat-cell *matCellDef="let motion">
|
||||
<button mat-icon-button (click)="onSpeakerIcon(motion)">
|
||||
<mat-icon
|
||||
[matBadge]="motion.agendaSpeakerAmount > 0 ? motion.agendaSpeakerAmount : null"
|
||||
matBadgeColor="accent">
|
||||
mic
|
||||
</mat-icon>
|
||||
</button>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="columnsToDisplayMinWidth"></mat-header-row>
|
||||
<mat-row (click)="selectMotion(row)" *matRowDef="let row; columns: columnsToDisplayMinWidth"></mat-row>
|
||||
<mat-row *matRowDef="let row; columns: columnsToDisplayMinWidth"></mat-row>
|
||||
</mat-table>
|
||||
|
||||
<mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator>
|
||||
|
@ -41,4 +41,10 @@
|
||||
font-size: 150%;
|
||||
}
|
||||
}
|
||||
|
||||
/** Speakers indicator */
|
||||
.mat-column-speakers {
|
||||
flex: 0 0 100px;
|
||||
justify-content: flex-end !important;
|
||||
}
|
||||
}
|
||||
|
@ -22,14 +22,14 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
/**
|
||||
* Use for minimal width
|
||||
*/
|
||||
public columnsToDisplayMinWidth = ['identifier', 'title', 'state'];
|
||||
public columnsToDisplayMinWidth = ['identifier', 'title', 'state', 'speakers'];
|
||||
|
||||
/**
|
||||
* Use for maximal width
|
||||
*
|
||||
* TODO: Needs vp.desktop check
|
||||
*/
|
||||
public columnsToDisplayFullWidth = ['identifier', 'title', 'meta', 'state'];
|
||||
public columnsToDisplayFullWidth = ['identifier', 'title', 'state', 'speakers'];
|
||||
|
||||
/**
|
||||
* Constructor implements title and translation Module.
|
||||
@ -110,6 +110,14 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the speakers button
|
||||
* @param motion indicates the row that was clicked on
|
||||
*/
|
||||
public onSpeakerIcon(motion: ViewMotion): void {
|
||||
this.router.navigate([`/agenda/${motion.agenda_item_id}/speakers`]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the plus button
|
||||
*/
|
||||
|
@ -7,6 +7,7 @@ import { BaseModel } from '../../../shared/models/base/base-model';
|
||||
import { BaseViewModel } from '../../base/base-view-model';
|
||||
import { ViewMotionCommentSection } from './view-motion-comment-section';
|
||||
import { MotionComment } from '../../../shared/models/motions/motion-comment';
|
||||
import { Item } from 'app/shared/models/agenda/item';
|
||||
|
||||
export enum LineNumberingMode {
|
||||
None,
|
||||
@ -35,6 +36,7 @@ export class ViewMotion extends BaseViewModel {
|
||||
private _supporters: User[];
|
||||
private _workflow: Workflow;
|
||||
private _state: WorkflowState;
|
||||
private _item: Item;
|
||||
|
||||
/**
|
||||
* Indicates the LineNumberingMode Mode.
|
||||
@ -164,13 +166,22 @@ export class ViewMotion extends BaseViewModel {
|
||||
this._motion.submitters_id = users.map(user => user.id);
|
||||
}
|
||||
|
||||
public get item(): Item {
|
||||
return this._item;
|
||||
}
|
||||
|
||||
public get agendaSpeakerAmount(): number {
|
||||
return this.item ? this.item.speakerAmount : null
|
||||
}
|
||||
|
||||
public constructor(
|
||||
motion?: Motion,
|
||||
category?: Category,
|
||||
submitters?: User[],
|
||||
supporters?: User[],
|
||||
workflow?: Workflow,
|
||||
state?: WorkflowState
|
||||
state?: WorkflowState,
|
||||
item?: Item,
|
||||
) {
|
||||
super();
|
||||
|
||||
@ -180,6 +191,7 @@ export class ViewMotion extends BaseViewModel {
|
||||
this._supporters = supporters;
|
||||
this._workflow = workflow;
|
||||
this._state = state;
|
||||
this._item = item;
|
||||
|
||||
// TODO: Should be set using a a config variable
|
||||
this.lnMode = LineNumberingMode.Outside;
|
||||
@ -216,13 +228,16 @@ export class ViewMotion extends BaseViewModel {
|
||||
this.updateWorkflow(update as Workflow);
|
||||
} else if (update instanceof Category) {
|
||||
this.updateCategory(update as Category);
|
||||
} else if (update instanceof Item) {
|
||||
this.updateItem(update as Item);
|
||||
}
|
||||
// TODO: There is no way (yet) to add Submitters to a motion
|
||||
// Thus, this feature could not be tested
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Category
|
||||
* Update routine for the category
|
||||
* @param update potentially the changed category. Needs manual verification
|
||||
*/
|
||||
public updateCategory(update: Category): void {
|
||||
if (this.motion && update.id === this.motion.category_id) {
|
||||
@ -231,7 +246,8 @@ export class ViewMotion extends BaseViewModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* updates the Workflow
|
||||
* Update routine for the workflow
|
||||
* @param update potentially the changed workflow (state). Needs manual verification
|
||||
*/
|
||||
public updateWorkflow(update: Workflow): void {
|
||||
if (this.motion && update.id === this.motion.workflow_id) {
|
||||
@ -239,6 +255,16 @@ export class ViewMotion extends BaseViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update routine for the agenda Item
|
||||
* @param update potentially the changed agenda Item. Needs manual verification
|
||||
*/
|
||||
public updateItem(update: Item): void {
|
||||
if (this.motion && update.id === this.motion.agenda_item_id) {
|
||||
this._item = update as Item;
|
||||
}
|
||||
}
|
||||
|
||||
public hasSupporters(): boolean {
|
||||
return !!(this.supporters && this.supporters.length > 0);
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import { CollectionStringModelMapperService } from '../../../core/services/colle
|
||||
import { HttpService } from 'app/core/services/http.service';
|
||||
import { ConfigService } from 'app/core/services/config.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Item } from 'app/shared/models/agenda/item';
|
||||
|
||||
/**
|
||||
* Repository Services for motions (and potentially categories)
|
||||
@ -53,7 +54,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
||||
private readonly lineNumbering: LinenumberingService,
|
||||
private readonly diff: DiffService
|
||||
) {
|
||||
super(DS, mapperService, Motion, [Category, User, Workflow]);
|
||||
super(DS, mapperService, Motion, [Category, User, Workflow, Item]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,11 +70,12 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
||||
const submitters = this.DS.getMany(User, motion.submitterIds);
|
||||
const supporters = this.DS.getMany(User, motion.supporters_id);
|
||||
const workflow = this.DS.get(Workflow, motion.workflow_id);
|
||||
const item = this.DS.get(Item, motion.agenda_item_id);
|
||||
let state: WorkflowState = null;
|
||||
if (workflow) {
|
||||
state = workflow.getStateById(motion.state_id);
|
||||
}
|
||||
return new ViewMotion(motion, category, submitters, supporters, workflow, state);
|
||||
return new ViewMotion(motion, category, submitters, supporters, workflow, state, item);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user