From d968761377ef3d1ed5322e49018b783a69d50a49 Mon Sep 17 00:00:00 2001 From: Sean Engelhardt Date: Mon, 12 Nov 2018 15:24:23 +0100 Subject: [PATCH] Speaker indicators in Lists --- client/src/app/shared/models/agenda/item.ts | 9 +++++- .../src/app/shared/models/agenda/speaker.ts | 25 +++++++++++++++ client/src/app/shared/shared.module.ts | 5 ++- .../agenda-list/agenda-list.component.css | 0 .../agenda-list/agenda-list.component.html | 23 ++++++++++--- .../agenda-list/agenda-list.component.scss | 19 +++++++++++ .../agenda-list/agenda-list.component.ts | 12 +++++-- .../speaker-list/speaker-list.component.ts | 6 ++-- .../src/app/site/agenda/models/view-item.ts | 4 +++ .../app/site/agenda/models/view-speaker.ts | 25 ++------------- .../motion-list/motion-list.component.html | 22 ++++++++++--- .../motion-list/motion-list.component.scss | 6 ++++ .../motion-list/motion-list.component.ts | 12 +++++-- .../app/site/motions/models/view-motion.ts | 32 +++++++++++++++++-- .../services/motion-repository.service.ts | 6 ++-- 15 files changed, 161 insertions(+), 45 deletions(-) delete mode 100644 client/src/app/site/agenda/components/agenda-list/agenda-list.component.css create mode 100644 client/src/app/site/agenda/components/agenda-list/agenda-list.component.scss diff --git a/client/src/app/shared/models/agenda/item.ts b/client/src/app/shared/models/agenda/item.ts index 1cc600885..caff22036 100644 --- a/client/src/app/shared/models/agenda/item.ts +++ b/client/src/app/shared/models/agenda/item.ts @@ -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; } diff --git a/client/src/app/shared/models/agenda/speaker.ts b/client/src/app/shared/models/agenda/speaker.ts index a6fdd2ece..79ca1da2c 100644 --- a/client/src/app/shared/models/agenda/speaker.ts +++ b/client/src/app/shared/models/agenda/speaker.ts @@ -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. diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 30f43c88e..b02acab96 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts @@ -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, diff --git a/client/src/app/site/agenda/components/agenda-list/agenda-list.component.css b/client/src/app/site/agenda/components/agenda-list/agenda-list.component.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/src/app/site/agenda/components/agenda-list/agenda-list.component.html b/client/src/app/site/agenda/components/agenda-list/agenda-list.component.html index 32842f73b..3e9869fc5 100644 --- a/client/src/app/site/agenda/components/agenda-list/agenda-list.component.html +++ b/client/src/app/site/agenda/components/agenda-list/agenda-list.component.html @@ -9,15 +9,30 @@ Topic - {{ item.getListTitle() }} + {{ item.getListTitle() }} + Duration - {{ item.duration }} + {{ item.duration }} - - + + + Speakers + + + + + + + diff --git a/client/src/app/site/agenda/components/agenda-list/agenda-list.component.scss b/client/src/app/site/agenda/components/agenda-list/agenda-list.component.scss new file mode 100644 index 000000000..fcfed1618 --- /dev/null +++ b/client/src/app/site/agenda/components/agenda-list/agenda-list.component.scss @@ -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; + } +} diff --git a/client/src/app/site/agenda/components/agenda-list/agenda-list.component.ts b/client/src/app/site/agenda/components/agenda-list/agenda-list.component.ts index cb7197516..501b9e97a 100644 --- a/client/src/app/site/agenda/components/agenda-list/agenda-list.component.ts +++ b/client/src/app/site/agenda/components/agenda-list/agenda-list.component.ts @@ -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 implements OnInit { /** @@ -64,6 +62,14 @@ export class AgendaListComponent extends ListViewBaseComponent 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 diff --git a/client/src/app/site/agenda/components/speaker-list/speaker-list.component.ts b/client/src/app/site/agenda/components/speaker-list/speaker-list.component.ts index f4142b538..e21b56268 100644 --- a/client/src/app/site/agenda/components/speaker-list/speaker-list.component.ts +++ b/client/src/app/site/agenda/components/speaker-list/speaker-list.component.ts @@ -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'; diff --git a/client/src/app/site/agenda/models/view-item.ts b/client/src/app/site/agenda/models/view-item.ts index b1731ea67..7ebb60f85 100644 --- a/client/src/app/site/agenda/models/view-item.ts +++ b/client/src/app/site/agenda/models/view-item.ts @@ -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; diff --git a/client/src/app/site/agenda/models/view-speaker.ts b/client/src/app/site/agenda/models/view-speaker.ts index 8584abdb6..c1b78c247 100644 --- a/client/src/app/site/agenda/models/view-speaker.ts +++ b/client/src/app/site/agenda/models/view-speaker.ts @@ -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 { diff --git a/client/src/app/site/motions/components/motion-list/motion-list.component.html b/client/src/app/site/motions/components/motion-list/motion-list.component.html index 36a20ecfe..e2adaabe2 100644 --- a/client/src/app/site/motions/components/motion-list/motion-list.component.html +++ b/client/src/app/site/motions/components/motion-list/motion-list.component.html @@ -25,7 +25,7 @@ Identifier - +
{{ motion.identifier }}
@@ -35,7 +35,7 @@ Title - +
{{ motion.title }}
@@ -50,7 +50,7 @@ State - + @@ -60,8 +60,22 @@ + + + Speakers + + + + + - + diff --git a/client/src/app/site/motions/components/motion-list/motion-list.component.scss b/client/src/app/site/motions/components/motion-list/motion-list.component.scss index ebdc94b63..a62c88def 100644 --- a/client/src/app/site/motions/components/motion-list/motion-list.component.scss +++ b/client/src/app/site/motions/components/motion-list/motion-list.component.scss @@ -41,4 +41,10 @@ font-size: 150%; } } + + /** Speakers indicator */ + .mat-column-speakers { + flex: 0 0 100px; + justify-content: flex-end !important; + } } diff --git a/client/src/app/site/motions/components/motion-list/motion-list.component.ts b/client/src/app/site/motions/components/motion-list/motion-list.component.ts index c5e5212cc..1e260b8bc 100644 --- a/client/src/app/site/motions/components/motion-list/motion-list.component.ts +++ b/client/src/app/site/motions/components/motion-list/motion-list.component.ts @@ -22,14 +22,14 @@ export class MotionListComponent extends ListViewBaseComponent 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 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 */ diff --git a/client/src/app/site/motions/models/view-motion.ts b/client/src/app/site/motions/models/view-motion.ts index 552c29395..05f07f036 100644 --- a/client/src/app/site/motions/models/view-motion.ts +++ b/client/src/app/site/motions/models/view-motion.ts @@ -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); } diff --git a/client/src/app/site/motions/services/motion-repository.service.ts b/client/src/app/site/motions/services/motion-repository.service.ts index 3ba541fad..afc2fb7b6 100644 --- a/client/src/app/site/motions/services/motion-repository.service.ts +++ b/client/src/app/site/motions/services/motion-repository.service.ts @@ -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 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 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); } /**