Build agenda with optional subtitle

- Updates the `virtual-scroll`-package.
- Updates the `list-view-table.component` for variable row-height.
- Adds config to have optional a subtitle in the `agenda-list`.
This commit is contained in:
GabrielMeyer 2019-09-09 18:20:58 +02:00
parent 534f2d1835
commit 71fdc28413
16 changed files with 169 additions and 15 deletions

View File

@ -47,8 +47,8 @@
"@ngx-pwa/local-storage": "~8.2.1", "@ngx-pwa/local-storage": "~8.2.1",
"@ngx-translate/core": "~11.0.1", "@ngx-translate/core": "~11.0.1",
"@ngx-translate/http-loader": "^4.0.0", "@ngx-translate/http-loader": "^4.0.0",
"@pebula/ngrid": "1.0.0-rc.5", "@pebula/ngrid": "1.0.0-rc.9",
"@pebula/ngrid-material": "1.0.0-rc.5", "@pebula/ngrid-material": "1.0.0-rc.9",
"@pebula/utils": "1.0.0", "@pebula/utils": "1.0.0",
"@tinymce/tinymce-angular": "^3.2.0", "@tinymce/tinymce-angular": "^3.2.0",
"acorn": "^7.0.0", "acorn": "^7.0.0",

View File

@ -91,6 +91,22 @@ export class ItemRepositoryService extends BaseHasContentObjectRepository<
} }
}; };
/**
* Overrides the base function, if implemented.
*
* @returns An optional subtitle as `string`. Defaults to `null`.
*/
public getSubtitle = (titleInformation: ItemTitleInformation) => {
if (titleInformation.contentObject) {
return titleInformation.contentObject.getAgendaSubtitle();
} else {
const repo = this.collectionStringMapperService.getRepository(
titleInformation.contentObjectData.collection
) as BaseIsAgendaItemContentObjectRepository<any, any, any>;
return repo.getAgendaSubtitle(titleInformation.title_information);
}
};
/** /**
* Overrides the base function. * Overrides the base function.
* *
@ -107,6 +123,20 @@ export class ItemRepositoryService extends BaseHasContentObjectRepository<
} }
}; };
/**
* @override The base-function to extends the items with an optional subtitle.
*
* @param model The underlying item.
* @param initialLoading boolean passed to the base-function.
*
* @returns {ViewItem} The modified item extended with the `getSubtitle()`-function.
*/
protected createViewModelWithTitles(model: Item, initialLoading: boolean): ViewItem {
const viewModel = super.createViewModelWithTitles(model, initialLoading);
viewModel.getSubtitle = () => this.getSubtitle(viewModel);
return viewModel;
}
/** /**
* Trigger the automatic numbering sequence on the server * Trigger the automatic numbering sequence on the server
*/ */

View File

@ -59,6 +59,17 @@ export abstract class BaseIsAgendaItemAndListOfSpeakersContentObjectRepository<
return numberPrefix + this.getTitle(titleInformation) + ' (' + this.getVerboseName() + ')'; return numberPrefix + this.getTitle(titleInformation) + ' (' + this.getVerboseName() + ')';
} }
/**
* Overwrites the base function.
*
* @param titleInformation The information about the model.
*
* @returns {string | null} An optional subtitle. `Null`, if it returns no subtitle, otherwise `string`.
*/
public getAgendaSubtitle(titleInformation: T): string | null {
return null;
}
public getAgendaSlideTitle(titleInformation: T): string { public getAgendaSlideTitle(titleInformation: T): string {
const numberPrefix = titleInformation.agenda_item_number ? `${titleInformation.agenda_item_number} · ` : ''; const numberPrefix = titleInformation.agenda_item_number ? `${titleInformation.agenda_item_number} · ` : '';
return numberPrefix + this.getTitle(titleInformation); return numberPrefix + this.getTitle(titleInformation);
@ -88,6 +99,7 @@ export abstract class BaseIsAgendaItemAndListOfSpeakersContentObjectRepository<
viewModel.getAgendaListTitle = () => this.getAgendaListTitle(viewModel); viewModel.getAgendaListTitle = () => this.getAgendaListTitle(viewModel);
viewModel.getAgendaListTitleWithoutItemNumber = () => this.getAgendaListTitleWithoutItemNumber(viewModel); viewModel.getAgendaListTitleWithoutItemNumber = () => this.getAgendaListTitleWithoutItemNumber(viewModel);
viewModel.getAgendaSlideTitle = () => this.getAgendaSlideTitle(viewModel); viewModel.getAgendaSlideTitle = () => this.getAgendaSlideTitle(viewModel);
viewModel.getAgendaSubtitle = () => this.getAgendaSubtitle(viewModel);
viewModel.getListOfSpeakersTitle = () => this.getListOfSpeakersTitle(viewModel); viewModel.getListOfSpeakersTitle = () => this.getListOfSpeakersTitle(viewModel);
viewModel.getListOfSpeakersSlideTitle = () => this.getListOfSpeakersSlideTitle(viewModel); viewModel.getListOfSpeakersSlideTitle = () => this.getListOfSpeakersSlideTitle(viewModel);
return viewModel; return viewModel;

View File

@ -84,6 +84,17 @@ export abstract class BaseIsAgendaItemContentObjectRepository<
return numberPrefix + this.getTitle(titleInformation) + ' (' + this.getVerboseName() + ')'; return numberPrefix + this.getTitle(titleInformation) + ' (' + this.getVerboseName() + ')';
} }
/**
* Overrides the base function. Returns an optional subtitle.
*
* @param titleInformation The information about the underlying model.
*
* @returns A string as subtitle. Defaults to `null`.
*/
public getAgendaSubtitle(titleInformation: T): string | null {
return null;
}
/** /**
* Function to return the title without item-number, in example used for pdf-creation. * Function to return the title without item-number, in example used for pdf-creation.
* *
@ -110,6 +121,7 @@ export abstract class BaseIsAgendaItemContentObjectRepository<
viewModel.getAgendaListTitle = () => this.getAgendaListTitle(viewModel); viewModel.getAgendaListTitle = () => this.getAgendaListTitle(viewModel);
viewModel.getAgendaListTitleWithoutItemNumber = () => this.getAgendaListTitleWithoutItemNumber(viewModel); viewModel.getAgendaListTitleWithoutItemNumber = () => this.getAgendaListTitleWithoutItemNumber(viewModel);
viewModel.getAgendaSlideTitle = () => this.getAgendaSlideTitle(viewModel); viewModel.getAgendaSlideTitle = () => this.getAgendaSlideTitle(viewModel);
viewModel.getAgendaSubtitle = () => this.getAgendaSubtitle(viewModel);
return viewModel; return viewModel;
} }
} }

View File

@ -293,6 +293,13 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
} }
}; };
/**
* @override The base function and returns the submitters as optional subtitle.
*/
public getAgendaSubtitle = (model: ViewMotion) => {
return model.submittersAsUsers.join(', ');
};
/** /**
* @override The base function * @override The base function
*/ */

View File

@ -13,15 +13,11 @@
<!-- vScrollFixed="110" --> <!-- vScrollFixed="110" -->
<!-- vScrollAuto () --> <!-- vScrollAuto () -->
<pbl-ngrid <pbl-ngrid
[ngClass]="{ [ngClass]="cssClasses"
'virtual-scroll-with-head-bar ngrid-hide-head': showFilterBar, [vScrollFixed]="vScrollFixed"
'virtual-scroll-full-page': !showFilterBar,
multiselect: multiSelect
}"
cellTooltip cellTooltip
[showHeader]="!showFilterBar" [showHeader]="!showFilterBar"
matCheckboxSelection="selection" matCheckboxSelection="selection"
vScrollFixed="110"
[dataSource]="dataSource" [dataSource]="dataSource"
[columns]="columnSet" [columns]="columnSet"
[hideColumns]="hiddenColumns" [hideColumns]="hiddenColumns"

View File

@ -1,11 +1,13 @@
@import '~assets/styles/tables.scss'; @import '~assets/styles/tables.scss';
$pbl-height: var(--pbl-height);
.projector-button { .projector-button {
margin: auto; margin: auto;
} }
.pbl-ngrid-row { .pbl-ngrid-row {
height: 110px; height: $pbl-height;
} }
.pbl-ngrid-cell { .pbl-ngrid-cell {

View File

@ -27,6 +27,10 @@ import { BaseProjectableViewModel } from 'app/site/base/base-projectable-view-mo
import { BaseViewModel } from 'app/site/base/base-view-model'; import { BaseViewModel } from 'app/site/base/base-view-model';
import { BaseViewModelWithContentObject } from 'app/site/base/base-view-model-with-content-object'; import { BaseViewModelWithContentObject } from 'app/site/base/base-view-model-with-content-object';
export interface CssClassDefinition {
[key: string]: boolean;
}
/** /**
* To hide columns via restriction * To hide columns via restriction
*/ */
@ -181,6 +185,37 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
@Input() @Input()
public showListOfSpeakers = true; public showListOfSpeakers = true;
/**
* Fix value for the height of the rows in the virtual-scroll-list.
*/
@Input()
public vScrollFixed = 110;
/**
* Option to apply additional classes to the virtual-scrolling-list.
*/
@Input()
public set cssClasses(values: CssClassDefinition) {
this._cssClasses = values;
}
/**
* Returns the list of classes, that are applied to the virtual-scrolling-list.
* Already prepared for the `[ngClass]`-property.
*
* `Warning: The defaultClasses will overwrite custom classes with the same key.`
*
* @returns An object looking like `{ [key: string]: boolean }`.
*/
public get cssClasses(): CssClassDefinition {
const defaultClasses = {
'virtual-scroll-with-head-bar ngrid-hide-head': this.showFilterBar,
'virtual-scroll-full-page': !this.showFilterBar,
multiselect: this.multiSelect
};
return Object.assign(this._cssClasses, defaultClasses);
}
/** /**
* Inform about changes in the dataSource * Inform about changes in the dataSource
*/ */
@ -223,6 +258,11 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
*/ */
private initialLoading = true; private initialLoading = true;
/**
* Private variable to hold all classes for the virtual-scrolling-list.
*/
private _cssClasses: CssClassDefinition = {};
/** /**
* Most, of not all list views require these * Most, of not all list views require these
*/ */
@ -450,6 +490,9 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
.table(...this.defaultStartColumns, ...this.columns, ...this.defaultEndColumns) .table(...this.defaultStartColumns, ...this.columns, ...this.defaultEndColumns)
.build(); .build();
// Sets the row height.
this.changeRowHeight();
// restore scroll position // restore scroll position
if (this.listStorageKey) { if (this.listStorageKey) {
this.scrollToPreviousPosition(this.listStorageKey); this.scrollToPreviousPosition(this.listStorageKey);
@ -567,6 +610,13 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
}); });
} }
/**
* This function changes the height of the row for virtual-scrolling in the relating `.scss`-file.
*/
private changeRowHeight(): void {
document.documentElement.style.setProperty('--pbl-height', this.vScrollFixed + 'px');
}
/** /**
* Checks the array of selected items against the datastore data. This is * Checks the array of selected items against the datastore data. This is
* meant to reselect items by their id even if some of their data changed, * meant to reselect items by their id even if some of their data changed,

View File

@ -15,6 +15,7 @@
<os-list-view-table <os-list-view-table
[repo]="repo" [repo]="repo"
[vScrollFixed]="64"
[filterService]="filterService" [filterService]="filterService"
[columns]="tableColumnDefinition" [columns]="tableColumnDefinition"
[multiSelect]="isMultiSelect" [multiSelect]="isMultiSelect"
@ -36,7 +37,14 @@
></a> ></a>
<div [ngStyle]="{ 'margin-left': item.level * 25 + 'px' }"> <div [ngStyle]="{ 'margin-left': item.level * 25 + 'px' }">
<os-icon-container [icon]="item.closed ? 'check' : null" size="large"> <os-icon-container [icon]="item.closed ? 'check' : null" size="large">
{{ item.getListTitle() }} <div>
<div>
{{ item.getListTitle() }}
</div>
<div *ngIf="showSubtitle" class="subtitle">
{{ item.getSubtitle() }}
</div>
</div>
</os-icon-container> </os-icon-container>
</div> </div>
</div> </div>
@ -52,10 +60,6 @@
{{ durationService.durationToString(item.duration, 'h') }} {{ durationService.durationToString(item.duration, 'h') }}
</os-icon-container> </os-icon-container>
</div> </div>
<div *ngIf="item.comment" class="spacer-top-5">
<os-icon-container icon="comment">{{ item.comment }}</os-icon-container>
</div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,4 @@
@import '~assets/styles/tables.scss'; @import '~assets/styles/tables.scss';
.info-col-items { .info-col-items {
display: inline-block; display: inline-block;
white-space: nowrap; white-space: nowrap;
@ -15,6 +14,9 @@
} }
} }
/*
* Where is this used?
*/
.done-check { .done-check {
margin-right: 10px; margin-right: 10px;
} }

View File

@ -44,6 +44,11 @@ export class AgendaListComponent extends BaseListViewComponent<ViewItem> impleme
*/ */
public isNumberingAllowed: boolean; public isNumberingAllowed: boolean;
/**
* A boolean, that decides, if the optional subtitles should be shown.
*/
public showSubtitle: boolean;
/** /**
* Helper to check main button permissions * Helper to check main button permissions
* *
@ -146,6 +151,7 @@ export class AgendaListComponent extends BaseListViewComponent<ViewItem> impleme
this.config this.config
.get<boolean>('agenda_enable_numbering') .get<boolean>('agenda_enable_numbering')
.subscribe(autoNumbering => (this.isNumberingAllowed = autoNumbering)); .subscribe(autoNumbering => (this.isNumberingAllowed = autoNumbering));
this.config.get<boolean>('agenda_show_subtitle').subscribe(showSubtitle => (this.showSubtitle = showSubtitle));
} }
/** /**

View File

@ -46,6 +46,8 @@ export class ViewItem extends BaseViewModelWithContentObject<Item, BaseViewModel
return this.item.level; return this.item.level;
} }
public getSubtitle: () => string | null;
/** /**
* Gets the string representation of the item type * Gets the string representation of the item type
* @returns The visibility for this item, as defined in {@link itemVisibilityChoices} * @returns The visibility for this item, as defined in {@link itemVisibilityChoices}

View File

@ -47,6 +47,7 @@ export abstract class BaseViewModelWithAgendaItemAndListOfSpeakers<
public getAgendaSlideTitle: () => string; public getAgendaSlideTitle: () => string;
public getAgendaListTitle: () => string; public getAgendaListTitle: () => string;
public getAgendaListTitleWithoutItemNumber: () => string; public getAgendaListTitleWithoutItemNumber: () => string;
public getAgendaSubtitle: () => string;
public getListOfSpeakersTitle: () => string; public getListOfSpeakersTitle: () => string;
public getListOfSpeakersSlideTitle: () => string; public getListOfSpeakersSlideTitle: () => string;

View File

@ -46,6 +46,11 @@ export interface IBaseViewModelWithAgendaItem<M extends BaseModelWithAgendaItem
*/ */
getAgendaListTitle: () => string; getAgendaListTitle: () => string;
/**
* @return an optional subtitle for the agenda.
*/
getAgendaSubtitle: () => string | null;
/** /**
* @return the agenda title with the verbose name of the content object * @return the agenda title with the verbose name of the content object
*/ */
@ -108,6 +113,15 @@ export abstract class BaseViewModelWithAgendaItem<M extends BaseModelWithAgendaI
return ''; return '';
} }
/**
* @override The base-method from `IBaseViewModelWithAgendaItem`.
*
* @returns Defaults to `null`.
*/
public getAgendaSubtitle(): string | null {
return null;
}
public abstract getDetailStateURL(): string; public abstract getDetailStateURL(): string;
/** /**

View File

@ -82,6 +82,12 @@
color: mat-color($foreground, secondary-text); color: mat-color($foreground, secondary-text);
} }
.subtitle {
color: mat-color($foreground, secondary-text);
font-size: 12px;
font-weight: 400;
}
mat-card-header { mat-card-header {
background-color: mat-color($background, app-bar); background-color: mat-color($background, app-bar);
} }

View File

@ -23,6 +23,16 @@ def get_config_variables():
subgroup="General", subgroup="General",
) )
yield ConfigVariable(
name="agenda_show_subtitle",
default_value=False,
input_type="boolean",
label="Show subtitles in the agenda",
weight=201,
group="Agenda",
subgroup="General",
)
# Numbering # Numbering
yield ConfigVariable( yield ConfigVariable(