Subtitles for projected elements in the projector detail view

This commit is contained in:
FinnStutzenstein 2020-04-22 18:46:48 +02:00 committed by Sean
parent 6a488eb78e
commit c8faa982ac
15 changed files with 105 additions and 139 deletions

View File

@ -25,6 +25,11 @@ import { HttpService } from './http.service';
import { ProjectorDataService } from './projector-data.service';
import { ViewModelStoreService } from './view-model-store.service';
export interface ProjectorTitle {
title: string;
subtitle?: string;
}
/**
* This service cares about Projectables being projected and manage all projection-related
* actions.
@ -325,7 +330,7 @@ export class ProjectorService {
/**
*/
public getSlideTitle(element: ProjectorElement): string {
public getSlideTitle(element: ProjectorElement): ProjectorTitle {
if (this.slideManager.canSlideBeMappedToModel(element.name)) {
const idElement = this.slideManager.getIdentifialbeProjectorElement(element);
const viewModel = this.getViewModelFromProjectorElement(idElement);
@ -338,7 +343,7 @@ export class ProjectorService {
return configuration.getSlideTitle(element, this.translate, this.viewModelStore);
}
return this.translate.instant(this.slideManager.getSlideVerboseName(element.name));
return { title: this.translate.instant(this.slideManager.getSlideVerboseName(element.name)) };
}
/**

View File

@ -14,6 +14,7 @@ import { Identifiable } from 'app/shared/models/base/identifiable';
import { ItemTitleInformation, ViewItem } from 'app/site/agenda/models/view-item';
import { ViewAssignment } from 'app/site/assignments/models/view-assignment';
import {
AgendaListTitle,
BaseViewModelWithAgendaItem,
isBaseViewModelWithAgendaItem
} from 'app/site/base/base-view-model-with-agenda-item';
@ -79,7 +80,7 @@ export class ItemRepositoryService extends BaseHasContentObjectRepository<
return this.translate.instant(plural ? 'Items' : 'Item');
};
public getTitle = (titleInformation: ItemTitleInformation) => {
private getAgendaTitle(titleInformation: ItemTitleInformation): AgendaListTitle {
if (titleInformation.contentObject) {
return titleInformation.contentObject.getAgendaListTitle();
} else {
@ -88,36 +89,14 @@ export class ItemRepositoryService extends BaseHasContentObjectRepository<
) as BaseIsAgendaItemContentObjectRepository<any, any, any>;
return repo.getAgendaListTitle(titleInformation.title_information);
}
}
public getTitle = (titleInformation: ItemTitleInformation) => {
return this.getAgendaTitle(titleInformation).title;
};
/**
* Overrides the base function, if implemented.
*
* @returns An optional subtitle as `string`. Defaults to `null`.
*/
public getSubtitle = (viewItem: ViewItem) => {
if (viewItem.contentObject) {
return viewItem.contentObject.getAgendaSubtitle();
} else {
// The subtitle is not present in the title_information yet.
return null;
}
};
/**
* Overrides the base function.
*
* @returns The title without any prefix like item number.
*/
public getTitleWithoutItemNumber = (titleInformation: ItemTitleInformation) => {
if (titleInformation.contentObject) {
return titleInformation.contentObject.getAgendaListTitleWithoutItemNumber();
} else {
const repo = this.collectionStringMapperService.getRepository(
titleInformation.contentObjectData.collection
) as BaseIsAgendaItemContentObjectRepository<any, any, any>;
return repo.getAgendaListTitleWithoutItemNumber(titleInformation.title_information);
}
public getSubtitle = (titleInformation: ItemTitleInformation) => {
return this.getAgendaTitle(titleInformation).subtitle;
};
/**

View File

@ -3,6 +3,7 @@ import { ViewItem } from 'app/site/agenda/models/view-item';
import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers';
import { BaseProjectableViewModel } from 'app/site/base/base-projectable-view-model';
import {
AgendaListTitle,
BaseViewModelWithAgendaItem,
TitleInformationWithAgendaItem
} from 'app/site/base/base-view-model-with-agenda-item';
@ -52,14 +53,11 @@ export abstract class BaseIsAgendaItemAndListOfSpeakersContentObjectRepository<
});
}
public getAgendaListTitle(titleInformation: T): string {
public getAgendaListTitle(titleInformation: T): AgendaListTitle {
// Return the agenda title with the model's verbose name appended
const numberPrefix = titleInformation.agenda_item_number() ? `${titleInformation.agenda_item_number()} · ` : '';
return numberPrefix + this.getTitle(titleInformation) + ' (' + this.getVerboseName() + ')';
}
public getAgendaSubtitle(viewModel: V): string | null {
return null;
const title = numberPrefix + this.getTitle(titleInformation) + ' (' + this.getVerboseName() + ')';
return { title };
}
public getAgendaSlideTitle(titleInformation: T): string {
@ -68,19 +66,8 @@ export abstract class BaseIsAgendaItemAndListOfSpeakersContentObjectRepository<
return numberPrefix + this.getTitle(titleInformation);
}
/**
* Function to get the list-title without the item-number.
*
* @param titleInformation The title-information for an object.
*
* @returns {string} The title without any prefix like item-number.
*/
public getAgendaListTitleWithoutItemNumber(titleInformation: T): string {
return this.getTitle(titleInformation) + ' (' + this.getVerboseName() + ')';
}
public getListOfSpeakersTitle = (titleInformation: T) => {
return this.getAgendaListTitle(titleInformation);
return this.getAgendaListTitle(titleInformation).title;
};
public getListOfSpeakersSlideTitle = (titleInformation: T) => {
@ -90,9 +77,7 @@ export abstract class BaseIsAgendaItemAndListOfSpeakersContentObjectRepository<
protected createViewModelWithTitles(model: M): V {
const viewModel = super.createViewModelWithTitles(model);
viewModel.getAgendaListTitle = () => this.getAgendaListTitle(viewModel);
viewModel.getAgendaListTitleWithoutItemNumber = () => this.getAgendaListTitleWithoutItemNumber(viewModel);
viewModel.getAgendaSlideTitle = () => this.getAgendaSlideTitle(viewModel);
viewModel.getAgendaSubtitle = () => this.getAgendaSubtitle(viewModel);
viewModel.getListOfSpeakersTitle = () => this.getListOfSpeakersTitle(viewModel);
viewModel.getListOfSpeakersSlideTitle = () => this.getListOfSpeakersSlideTitle(viewModel);
return viewModel;

View File

@ -2,6 +2,7 @@ import { TranslateService } from '@ngx-translate/core';
import { ViewItem } from 'app/site/agenda/models/view-item';
import {
AgendaListTitle,
BaseViewModelWithAgendaItem,
TitleInformationWithAgendaItem
} from 'app/site/base/base-view-model-with-agenda-item';
@ -29,8 +30,7 @@ export interface IBaseIsAgendaItemContentObjectRepository<
M extends BaseModel,
T extends TitleInformationWithAgendaItem
> extends BaseRepository<V, M, T> {
getAgendaListTitle: (titleInformation: T) => string;
getAgendaListTitleWithoutItemNumber: (titleInformation: T) => string;
getAgendaListTitle: (titleInformation: T) => AgendaListTitle;
getAgendaSlideTitle: (titleInformation: T) => string;
}
@ -77,31 +77,11 @@ export abstract class BaseIsAgendaItemContentObjectRepository<
* @returns the agenda title for the agenda item list. Should
* be `<item number> · <title> (<type>)`. E.g. `7 · the is an election (Election)`.
*/
public getAgendaListTitle(titleInformation: T): string {
public getAgendaListTitle(titleInformation: T): AgendaListTitle {
// Return the agenda title with the model's verbose name appended
const numberPrefix = titleInformation.agenda_item_number() ? `${titleInformation.agenda_item_number()} · ` : '';
return numberPrefix + this.getTitle(titleInformation) + ' (' + this.getVerboseName() + ')';
}
/**
* Overrides the base function. Returns an optional subtitle.
*
* @param viewModel The model to get the subtitle from.
* @returns A string as subtitle. Defaults to `null`.
*/
public getAgendaSubtitle(viewModel: V): string | null {
return null;
}
/**
* Function to return the title without item-number, in example used for pdf-creation.
*
* @param titleInformation The title information.
*
* @returns {string} The title without any prefix like the item-number.
*/
public getAgendaListTitleWithoutItemNumber(titleInformation: T): string {
return this.getTitle(titleInformation) + ' (' + this.getVerboseName() + ')';
const title = numberPrefix + this.getTitle(titleInformation) + ' (' + this.getVerboseName() + ')';
return { title };
}
/**
@ -117,9 +97,7 @@ export abstract class BaseIsAgendaItemContentObjectRepository<
protected createViewModelWithTitles(model: M): V {
const viewModel = super.createViewModelWithTitles(model);
viewModel.getAgendaListTitle = () => this.getAgendaListTitle(viewModel);
viewModel.getAgendaListTitleWithoutItemNumber = () => this.getAgendaListTitleWithoutItemNumber(viewModel);
viewModel.getAgendaSlideTitle = () => this.getAgendaSlideTitle(viewModel);
viewModel.getAgendaSubtitle = () => this.getAgendaSubtitle(viewModel);
return viewModel;
}
}

View File

@ -17,6 +17,7 @@ import { Motion } from 'app/shared/models/motions/motion';
import { Submitter } from 'app/shared/models/motions/submitter';
import { ViewUnifiedChange, ViewUnifiedChangeType } from 'app/shared/models/motions/view-unified-change';
import { PersonalNoteContent } from 'app/shared/models/users/personal-note';
import { AgendaListTitle } from 'app/site/base/base-view-model-with-agenda-item';
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
import { ViewCategory } from 'app/site/motions/models/view-category';
import { MotionTitleInformation, ViewMotion } from 'app/site/motions/models/view-motion';
@ -269,46 +270,40 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
public getAgendaListTitle = (titleInformation: MotionTitleInformation) => {
const numberPrefix = titleInformation.agenda_item_number() ? `${titleInformation.agenda_item_number()} · ` : '';
// Append the verbose name only, if not the special format 'Motion <identifier>' is used.
let title;
if (titleInformation.identifier) {
return `${numberPrefix}${this.translate.instant('Motion')} ${titleInformation.identifier} · ${
title = `${numberPrefix}${this.translate.instant('Motion')} ${titleInformation.identifier} · ${
titleInformation.title
}`;
} else {
return `${numberPrefix}${titleInformation.title} (${this.getVerboseName()})`;
title = `${numberPrefix}${titleInformation.title} (${this.getVerboseName()})`;
}
};
const agendaTitle: AgendaListTitle = { title };
/**
* @override The base function and returns the submitters as optional subtitle.
*/
public getAgendaSubtitle = (motion: ViewMotion) => {
if (motion.submittersAsUsers && motion.submittersAsUsers.length) {
return `${this.translate.instant('by')} ${motion.submittersAsUsers.join(', ')}`;
} else {
return null;
}
};
/**
* @override The base function
*/
public getAgendaListTitleWithoutItemNumber = (titleInformation: MotionTitleInformation) => {
if (titleInformation.identifier) {
return this.translate.instant('Motion') + ' ' + titleInformation.identifier;
} else {
return titleInformation.title + `(${this.getVerboseName()})`;
// Subtitle.
// This is a bit hacky: If one has not motions.can_see, the titleinformation is nut sufficient for
// submitters. So try-cast titleInformation to a ViewMotion and check, if submittersAsUsers is available
const viewMotion: ViewMotion = titleInformation as ViewMotion;
if (viewMotion.submittersAsUsers && viewMotion.submittersAsUsers.length) {
agendaTitle.subtitle = `${this.translate.instant('by')} ${viewMotion.submittersAsUsers.join(', ')}`;
}
return agendaTitle;
};
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Motions' : 'Motion');
};
public getProjectorTitle = (viewMotion: ViewMotion) => {
const subtitle = viewMotion.item && viewMotion.item.comment ? viewMotion.item.comment : null;
return { title: this.getAgendaSlideTitle(viewMotion), subtitle };
};
protected createViewModelWithTitles(model: Motion): ViewMotion {
const viewModel = super.createViewModelWithTitles(model);
viewModel.getIdentifierOrTitle = () => this.getIdentifierOrTitle(viewModel);
viewModel.getProjectorTitle = () => this.getAgendaSlideTitle(viewModel);
viewModel.getProjectorTitle = () => this.getProjectorTitle(viewModel);
return viewModel;
}

View File

@ -61,7 +61,7 @@ export class TopicRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCon
public getAgendaListTitle = (titleInformation: TopicTitleInformation) => {
// Do not append ' (Topic)' to the title.
return this.getTitle(titleInformation);
return { title: this.getTitle(titleInformation) };
};
public getAgendaSlideTitle = (titleInformation: TopicTitleInformation) => {
@ -69,15 +69,6 @@ export class TopicRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCon
return this.getTitle(titleInformation);
};
/**
* @override The base function.
*
* @returns The plain title.
*/
public getAgendaListTitleWithoutItemNumber = (titleInformation: TopicTitleInformation) => {
return titleInformation.title;
};
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Topics' : 'Topic');
};

View File

@ -1,3 +1,4 @@
import { ProjectorTitle } from 'app/core/core-services/projector.service';
import { ListOfSpeakers, ListOfSpeakersWithoutNestedModels } from 'app/shared/models/agenda/list-of-speakers';
import { ContentObject } from 'app/shared/models/base/content-object';
import { BaseViewModelWithContentObject } from 'app/site/base/base-view-model-with-content-object';
@ -34,8 +35,8 @@ export class ViewListOfSpeakers extends BaseViewModelWithContentObject<ListOfSpe
return `/agenda/speakers/${this.id}`;
}
public getProjectorTitle(): string {
return this.getTitle();
public getProjectorTitle(): ProjectorTitle {
return { title: this.getTitle() };
}
public getSlide(): ProjectorElementBuildDeskriptor {

View File

@ -87,7 +87,7 @@ export class AgendaPdfService {
text: nodeItem.item.item_number
},
{
text: nodeItem.item.contentObject.getAgendaListTitleWithoutItemNumber()
text: nodeItem.item.contentObject.getListTitle()
}
]
};

View File

@ -1,3 +1,4 @@
import { ProjectorTitle } from 'app/core/core-services/projector.service';
import { ConfigService } from 'app/core/ui-services/config.service';
import { BaseModel } from 'app/shared/models/base/base-model';
import { BaseViewModel } from './base-view-model';
@ -13,7 +14,7 @@ export abstract class BaseProjectableViewModel<M extends BaseModel = any> extend
/**
* @returns the projector title used for managing projector elements.
*/
public getProjectorTitle = () => {
return this.getTitle();
};
public getProjectorTitle(): ProjectorTitle {
return { title: this.getTitle() };
}
}

View File

@ -1,3 +1,4 @@
import { ProjectorTitle } from 'app/core/core-services/projector.service';
import { SearchRepresentation } from 'app/core/ui-services/search.service';
import { BaseModelWithAgendaItem } from 'app/shared/models/base/base-model-with-agenda-item';
import { DetailNavigable, isDetailNavigable } from 'app/shared/models/base/detail-navigable';
@ -5,6 +6,11 @@ import { BaseProjectableViewModel } from './base-projectable-view-model';
import { TitleInformation } from './base-view-model';
import { isSearchable, Searchable } from './searchable';
export interface AgendaListTitle {
title: string;
subtitle?: string;
}
export function isBaseViewModelWithAgendaItem(obj: any): obj is BaseViewModelWithAgendaItem {
const model = <BaseViewModelWithAgendaItem>obj;
return (
@ -13,7 +19,6 @@ export function isBaseViewModelWithAgendaItem(obj: any): obj is BaseViewModelWit
isSearchable(model) &&
model.getAgendaSlideTitle !== undefined &&
model.getAgendaListTitle !== undefined &&
model.getAgendaSubtitle !== undefined &&
model.getCSVExportText !== undefined &&
model.item !== undefined &&
model.getModel !== undefined &&
@ -42,12 +47,7 @@ export interface BaseViewModelWithAgendaItem<M extends BaseModelWithAgendaItem =
/**
* @return the agenda title with the verbose name of the content object
*/
getAgendaListTitle: () => string;
/**
* @return the agenda title with the verbose name of the content object
*/
getAgendaListTitleWithoutItemNumber: () => string;
getAgendaListTitle: () => AgendaListTitle;
}
/**
@ -62,6 +62,15 @@ export abstract class BaseViewModelWithAgendaItem<
return this.item && this.item.item_number ? this.item.item_number : null;
}
/**
* @returns the projector title used for managing projector elements.
* Appends the agneda item comment as the subtitle, if this model has an agenda item
*/
public getProjectorTitle(): ProjectorTitle {
const subtitle = this.item.comment || null;
return { title: this.getTitle(), subtitle };
}
/**
* @returns the (optional) descriptive text to be exported in the CSV.
* May be overridden by inheriting classes
@ -70,15 +79,6 @@ export abstract class BaseViewModelWithAgendaItem<
return '';
}
/**
* @override The base-method from `IBaseViewModelWithAgendaItem`.
*
* @returns Defaults to `null`.
*/
public getAgendaSubtitle(): string | null {
return null;
}
public abstract getDetailStateURL(): string;
/**

View File

@ -1,3 +1,4 @@
import { ProjectorTitle } from 'app/core/core-services/projector.service';
import { ConfigService } from 'app/core/ui-services/config.service';
import { IdentifiableProjectorElement, ProjectorElementOptions } from 'app/shared/models/core/projector';
import { Displayable } from 'app/site/base/displayable';
@ -32,7 +33,7 @@ export function isProjectable(obj: any): obj is Projectable {
* Interface for every model, that should be projectable.
*/
export interface Projectable extends Displayable {
getProjectorTitle: () => string;
getProjectorTitle: () => ProjectorTitle;
getSlide(configSerice?: ConfigService): ProjectorElementBuildDeskriptor;
}

View File

@ -169,8 +169,18 @@
<mat-icon>videocam</mat-icon>
</button>
<!-- Slide title and subtitle -->
<span class="ellipsis-overflow current-element-text">
{{ getSlideTitle(element) }}
<os-icon-container
class="subtitle-nocolor"
size="small"
icon="comment"
[noWrap]="true"
*ngIf="getSlideSubtitle(element)"
>
{{ getSlideSubtitle(element) }}
</os-icon-container>
</span>
<button type="button" mat-icon-button (click)="unprojectCurrent(element)">
@ -207,8 +217,18 @@
<mat-icon>videocam</mat-icon>
</button>
</div>
<!-- Slide title ans subtitle -->
<div class="name">
<span>{{ getSlideTitle(element) }}</span>
<span class="ellipsis-overflow">{{ getSlideTitle(element) }}</span>
<os-icon-container
class="subtitle"
size="small"
icon="comment"
[noWrap]="true"
*ngIf="getSlideSubtitle(element)"
>
{{ getSlideSubtitle(element) }}
</os-icon-container>
</div>
<div class="button-right" *ngIf="editQueue">
<div>
@ -335,6 +355,7 @@
<ol>
<li *ngFor="let elements of projector.elements_history">
{{ getSlideTitle(elements[0]) }}
{{ getSlideSubtitle(elements[0]) }}
</li>
</ol>
</mat-expansion-panel>

View File

@ -208,7 +208,11 @@ export class ProjectorDetailComponent extends BaseViewComponent implements OnIni
}
public getSlideTitle(element: ProjectorElement): string {
return this.projectorService.getSlideTitle(element);
return this.projectorService.getSlideTitle(element).title;
}
public getSlideSubtitle(element: ProjectorElement): string | null {
return this.projectorService.getSlideTitle(element).subtitle;
}
public isProjected(obj: Projectable): boolean {

View File

@ -3,6 +3,7 @@ import { LoadChildrenCallback } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { ProjectorTitle } from 'app/core/core-services/projector.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { IdentifiableProjectorElement, ProjectorElement } from 'app/shared/models/core/projector';
@ -30,7 +31,7 @@ export interface SlideDynamicConfiguration {
element: ProjectorElement,
translate: TranslateService,
viewModelStore: ViewModelStoreService
) => string;
) => ProjectorTitle;
}
/**

View File

@ -93,12 +93,16 @@
color: mat-color($foreground, secondary-text);
}
.subtitle {
color: mat-color($foreground, secondary-text);
.subtitle-nocolor {
font-size: 12px;
font-weight: 400;
}
.subtitle {
@extend .subtitle-nocolor;
color: mat-color($foreground, secondary-text);
}
.user-subtitle {
color: mat-color($foreground, secondary-text);
}