Merge pull request #4730 from FinnStutzenstein/motionBlockSlideLayout

Improved motion block slide layout
This commit is contained in:
Emanuel Schütze 2019-05-27 14:39:10 +02:00 committed by GitHub
commit 6f36094cde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 109 additions and 49 deletions

View File

@ -7,6 +7,9 @@ export interface MotionBlockSlideMotionRepresentation extends MotionTitleInforma
css_class: string;
};
recommendation_extension?: string;
// This property will be calculated and saved here.
recommendationLabel?: string;
}
export interface MotionBlockSlideData {

View File

@ -4,17 +4,24 @@
<h2><span translate>Motion block</span> {{ data.data.motions.length }} <span translate>motions</span></h2>
</div>
<div *ngIf="commonRecommendation" class="spacer-bottom-20">
<mat-basic-chip class="large" [ngClass]="getStateCssColor(0, 0)">
{{ commonRecommendation }}
</mat-basic-chip>
</div>
<table>
<tr *ngFor="let i of rowsArray">
<td *ngFor="let j of columnsArray">
<ng-container *ngIf="getMotion(i, j)">
<div class="ellipsis-overflow">
<div class="ellipsis-overflow spacer-top-5">
{{ getMotionTitle(i, j) }}
</div>
<div class="white">
<mat-basic-chip *ngIf="hasRecommendation(i, j)" disableRipple [ngClass]="getStateCssColor(i, j)" class="block-recommendation">
{{ getRecommendationLabel(i, j) }}
</mat-basic-chip>
<br *ngIf="!shortDisplayStyle"/>
<span class="white" *ngIf="!commonRecommendation && getMotion(i, j).recommendationLabel">
<mat-basic-chip disableRipple [ngClass]="getStateCssColor(i, j)" class="block-recommendation">
{{ getMotion(i, j).recommendationLabel }}
</mat-basic-chip>
</span>
</div>
</ng-container>
</td>

View File

@ -6,4 +6,9 @@ table {
td {
min-width: 50%;
vertical-align: top;
}
.mat-basic-chip.large {
font-size: 100%;
}

View File

@ -8,10 +8,20 @@ import { StateCssClassMapping } from 'app/site/motions/models/view-workflow';
import { BaseMotionSlideComponent } from '../base/base-motion-slide';
import { SlideData } from 'app/core/core-services/projector-data.service';
/**
* The row threshold to switch from one to a two column layout
*/
const TWO_COLUMNS_THRESHOLD = 8;
// Layout:
// 1) Long layout: Motion title is shown and the motions are
// displayed in two lines: title and recommendation. This
// mode is used until #motions<=SHORT_LAYOUT_THRESHOLD. There
// are ROWS_PER_COLUMN_SHORT rows per column, is MAX_COLUMNS
// is reached. If so, thhe rows per columns will be ignored.
// 2) Short Layout: Just motion identifier and the recommendation
// in one line. This mode is used if #motions>SHORT_LAYOUT_THRESHOLD.
// The same as in the log layout holds, just with ROWS_PER_COLUMN_SHORT.
const ROWS_PER_COLUMN_SHORT = 8;
const ROWS_PER_COLUMN_LONG = 16;
const SHORT_LAYOUT_THRESHOLD = 8;
const MAX_COLUMNS = 3;
@Component({
selector: 'os-motion-block-slide',
@ -38,6 +48,36 @@ export class MotionBlockSlideComponent extends BaseMotionSlideComponent<MotionBl
this.motionRepo.getIdentifierOrTitle(b)
)
);
// Populate the motion with the recommendation_label
data.data.motions.forEach(motion => {
if (motion.recommendation) {
let recommendation = this.translate.instant(motion.recommendation.name);
if (motion.recommendation_extension) {
recommendation +=
' ' +
this.replaceReferencedMotions(
motion.recommendation_extension,
data.data.referenced_motions
);
}
motion.recommendationLabel = recommendation;
} else {
motion.recommendationLabel = null;
}
});
// Check, if all motions have the same recommendation label
if (data.data.motions.length > 0) {
const recommendationLabel = data.data.motions[0].recommendationLabel;
if (data.data.motions.every(motion => motion.recommendationLabel === recommendationLabel)) {
this.commonRecommendation = recommendationLabel;
}
} else {
this.commonRecommendation = null;
}
} else {
this.commonRecommendation = null;
}
this._data = data;
}
@ -46,6 +86,11 @@ export class MotionBlockSlideComponent extends BaseMotionSlideComponent<MotionBl
return this._data;
}
/**
* If this is set, all motions have the same recommendation, saved in this variable.
*/
public commonRecommendation: string | null = null;
/**
* @returns the amount of motions in this block
*/
@ -57,38 +102,39 @@ export class MotionBlockSlideComponent extends BaseMotionSlideComponent<MotionBl
}
}
public get shortDisplayStyle(): boolean {
return this.motionsAmount > SHORT_LAYOUT_THRESHOLD;
}
/**
* @returns the amount of rows to display.
*/
public get rows(): number {
let rows = this.motionsAmount;
if (this.motionsAmount > TWO_COLUMNS_THRESHOLD) {
rows = Math.ceil(rows / 2);
}
return rows;
return Math.ceil(this.motionsAmount / this.columns);
}
/**
* @returns an aray with [1, ..., this.rows]
* @returns an aray with [0, ..., this.rows-1]
*/
public get rowsArray(): number[] {
const indices = [];
const rows = this.rows;
for (let i = 0; i < rows; i++) {
indices.push(i);
return this.makeIndicesArray(this.rows);
}
public get columns(): number {
const rowsPerColumn = this.shortDisplayStyle ? ROWS_PER_COLUMN_SHORT : ROWS_PER_COLUMN_LONG;
const columns = Math.ceil(this.motionsAmount / rowsPerColumn);
if (columns > MAX_COLUMNS) {
return MAX_COLUMNS;
} else {
return columns;
}
return indices;
}
/**
* @returns [0] or [0, 1] if one or two columns are used
* @returns an aray with [0, ..., this.columns-1]
*/
public get columnsArray(): number[] {
if (this.motionsAmount > TWO_COLUMNS_THRESHOLD) {
return [0, 1];
} else {
return [0];
}
return this.makeIndicesArray(this.columns);
}
public constructor(translate: TranslateService, motionRepo: MotionRepositoryService) {
@ -96,6 +142,17 @@ export class MotionBlockSlideComponent extends BaseMotionSlideComponent<MotionBl
this.languageCollator = new Intl.Collator(this.translate.currentLang);
}
/**
* @returns an array [0, ..., n-1]
*/
public makeIndicesArray(n: number): number[] {
const indices = [];
for (let i = 0; i < n; i++) {
indices.push(i);
}
return indices;
}
/**
* Get the motion for the cell given by i and j
*
@ -108,10 +165,15 @@ export class MotionBlockSlideComponent extends BaseMotionSlideComponent<MotionBl
}
/**
* @returns the title of the given motion.
* @returns the title of the given motion. If no title should be shown, just the
* identifier is returned.
*/
public getMotionTitle(i: number, j: number): string {
return this.motionRepo.getTitle(this.getMotion(i, j));
if (this.shortDisplayStyle) {
return this.motionRepo.getIdentifierOrTitle(this.getMotion(i, j));
} else {
return this.motionRepo.getTitle(this.getMotion(i, j));
}
}
/**
@ -121,30 +183,10 @@ export class MotionBlockSlideComponent extends BaseMotionSlideComponent<MotionBl
return this.motionRepo.getIdentifierOrTitle(this.getMotion(i, j));
}
/**
* @returns true if the motion in cell i and j has a recommendation
*/
public hasRecommendation(i: number, j: number): boolean {
return !!this.getMotion(i, j).recommendation;
}
/**
* @returns the css color for the state of the motion in cell i and j
*/
public getStateCssColor(i: number, j: number): string {
return StateCssClassMapping[this.getMotion(i, j).recommendation.css_class] || '';
}
/**
* @returns the recommendation label for motion in cell i and j
*/
public getRecommendationLabel(i: number, j: number): string {
const motion = this.getMotion(i, j);
let recommendation = this.translate.instant(motion.recommendation.name);
if (motion.recommendation_extension) {
recommendation +=
' ' + this.replaceReferencedMotions(motion.recommendation_extension, this.data.data.referenced_motions);
}
return recommendation;
}
}

View File

@ -505,6 +505,9 @@ button.mat-menu-item.selected {
.spacer-top-3 {
margin-top: 3px !important;
}
.spacer-top-5 {
margin-top: 5px !important;
}
.spacer-top-10 {
margin-top: 10px !important;
}