Merge pull request #4913 from tsiegleauq/smarter-motion-tiles
Add Smarter motion tiles and filters
This commit is contained in:
commit
8555516a53
@ -38,6 +38,7 @@ export interface OsFilterOption {
|
||||
condition: OsFilterOptionCondition;
|
||||
isActive?: boolean;
|
||||
isChild?: boolean;
|
||||
children?: OsFilterOption[];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,12 +55,13 @@ export interface OsFilterIndicator {
|
||||
*/
|
||||
interface HierarchyModel extends BaseViewModel {
|
||||
parent: BaseViewModel;
|
||||
children: BaseViewModel<any>[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the type of a filter condition
|
||||
*/
|
||||
type OsFilterOptionCondition = string | boolean | number | number[];
|
||||
export type OsFilterOptionCondition = string | boolean | number | number[];
|
||||
|
||||
/**
|
||||
* Filter for the list view. List views can subscribe to its' dataService (providing filter definitions)
|
||||
@ -292,7 +294,16 @@ export abstract class BaseFilterListService<V extends BaseViewModel> {
|
||||
return {
|
||||
condition: model.id,
|
||||
label: model.getTitle(),
|
||||
isChild: !!model.parent
|
||||
isChild: !!model.parent,
|
||||
children:
|
||||
model.children && model.children.length
|
||||
? model.children.map(child => {
|
||||
return {
|
||||
label: child.getTitle(),
|
||||
condition: child.id
|
||||
};
|
||||
})
|
||||
: undefined
|
||||
};
|
||||
});
|
||||
|
||||
@ -373,6 +384,7 @@ export abstract class BaseFilterListService<V extends BaseViewModel> {
|
||||
*/
|
||||
protected addFilterOption(filterProperty: string, option: OsFilterOption): void {
|
||||
const filter = this.filterDefinitions.find(f => f.property === filterProperty);
|
||||
|
||||
if (filter) {
|
||||
const filterOption = filter.options.find(
|
||||
o => typeof o !== 'string' && o.condition === option.condition
|
||||
@ -387,6 +399,12 @@ export abstract class BaseFilterListService<V extends BaseViewModel> {
|
||||
} else {
|
||||
filter.count += 1;
|
||||
}
|
||||
|
||||
if (filterOption.children && filterOption.children.length) {
|
||||
for (const child of filterOption.children) {
|
||||
this.addFilterOption(filterProperty, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -420,6 +438,12 @@ export abstract class BaseFilterListService<V extends BaseViewModel> {
|
||||
if (filter.count) {
|
||||
filter.count -= 1;
|
||||
}
|
||||
|
||||
if (filterOption.children && filterOption.children.length) {
|
||||
for (const child of filterOption.children) {
|
||||
this.removeFilterOption(filterProperty, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,14 @@ export class ViewCategory extends BaseViewModel<Category> implements CategoryTit
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
public get oldestParent(): ViewCategory {
|
||||
if (!this.parent_id) {
|
||||
return this;
|
||||
} else {
|
||||
return this.parent.oldestParent;
|
||||
}
|
||||
}
|
||||
|
||||
public get children(): ViewCategory[] {
|
||||
return this._children || [];
|
||||
}
|
||||
@ -64,6 +72,17 @@ export class ViewCategory extends BaseViewModel<Category> implements CategoryTit
|
||||
return this.prefix ? this.prefix + ' - ' + this.name : this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount of all motions on this category and all children
|
||||
*/
|
||||
public get totalAmountOfMotions(): number {
|
||||
let totalAmount = this.motions.length;
|
||||
for (const child of this.children) {
|
||||
totalAmount += child.totalAmountOfMotions;
|
||||
}
|
||||
return totalAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the name with all parents in brackets: "<Cat> (<CatParent>, <CatParentParent>)"
|
||||
*/
|
||||
|
@ -133,7 +133,7 @@
|
||||
<div class="column-state innerTable">
|
||||
<!-- Category -->
|
||||
<div class="ellipsis-overflow" *ngIf="motion.category">
|
||||
<os-icon-container icon="device_hub">
|
||||
<os-icon-container icon="category">
|
||||
{{ motion.category.nameWithParentAbove }}
|
||||
</os-icon-container>
|
||||
</div>
|
||||
@ -169,32 +169,38 @@
|
||||
<ng-template #tiles>
|
||||
<os-grid-layout>
|
||||
<os-block-tile
|
||||
*ngFor="let tileCategory of tileCategories"
|
||||
(clicked)="changeToViewWithTileCategory(tileCategory)"
|
||||
*ngFor="let tile of listTiles"
|
||||
(clicked)="changeToViewWithTileCategory(tile)"
|
||||
[orientation]="'horizontal'"
|
||||
[only]="'title'"
|
||||
[blockType]="'node'"
|
||||
[data]="tileCategory"
|
||||
title="{{ tileCategory.name | translate }}"
|
||||
[data]="tile"
|
||||
title="{{ tile.name | translate }}"
|
||||
>
|
||||
<ng-container class="block-node">
|
||||
<table
|
||||
matTooltip="{{ tileCategory.amountOfMotions }} {{ 'Motions' | translate }} – {{
|
||||
tileCategory.name | translate
|
||||
}}"
|
||||
matTooltip="{{ tile.amountOfMotions }} {{ 'Motions' | translate }} – {{ tile.name | translate }}"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<span
|
||||
class="tile-block-title"
|
||||
[matBadge]="tileCategory.amountOfMotions"
|
||||
[matBadge]="tile.amountOfMotions"
|
||||
[matBadgeColor]="'accent'"
|
||||
[ngSwitch]="tileCategory.name"
|
||||
[ngSwitch]="tile.name"
|
||||
>
|
||||
<span *ngSwitchCase="'Favorites'"><mat-icon>star</mat-icon></span>
|
||||
<span *ngSwitchCase="'No category'"><mat-icon>block</mat-icon></span>
|
||||
<span *ngSwitchDefault>{{ tileCategory.prefix }}</span>
|
||||
<span *ngSwitchCase="'Personal Note'"><mat-icon>speaker_notes</mat-icon></span>
|
||||
<span *ngSwitchDefault>
|
||||
<span *ngIf="tile.prefix">
|
||||
{{ tile.prefix }}
|
||||
</span>
|
||||
<span *ngIf="!tile.prefix">
|
||||
<mat-icon>category</mat-icon>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@ -221,7 +227,7 @@
|
||||
</div>
|
||||
<div *ngIf="perms.isAllowed('manage') || categories.length">
|
||||
<button mat-menu-item routerLink="category">
|
||||
<mat-icon>device_hub</mat-icon>
|
||||
<mat-icon>category</mat-icon>
|
||||
<span translate>Categories</span>
|
||||
</button>
|
||||
</div>
|
||||
@ -305,7 +311,7 @@
|
||||
*ngIf="categories.length"
|
||||
(click)="multiselectWrapper(multiselectService.setCategory(selectedRows))"
|
||||
>
|
||||
<mat-icon>device_hub</mat-icon>
|
||||
<mat-icon>category</mat-icon>
|
||||
<!-- TODO: icon -->
|
||||
<span translate>Set category</span>
|
||||
</button>
|
||||
|
@ -14,6 +14,7 @@ import { MotionBlockRepositoryService } from 'app/core/repositories/motions/moti
|
||||
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
||||
import { WorkflowRepositoryService } from 'app/core/repositories/motions/workflow-repository.service';
|
||||
import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service';
|
||||
import { OsFilterOptionCondition } from 'app/core/ui-services/base-filter-list.service';
|
||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||
import { ColumnRestriction } from 'app/shared/components/list-view-table/list-view-table.component';
|
||||
import { infoDialogSettings, largeDialogSettings } from 'app/shared/utils/dialog-settings';
|
||||
@ -40,7 +41,7 @@ interface TileCategoryInformation {
|
||||
filter: string;
|
||||
name: string;
|
||||
prefix?: string;
|
||||
condition: number | boolean | null;
|
||||
condition: OsFilterOptionCondition;
|
||||
amountOfMotions: number;
|
||||
}
|
||||
|
||||
@ -157,12 +158,11 @@ export class MotionListComponent extends BaseListViewComponent<ViewMotion> imple
|
||||
* List of `TileCategoryInformation`.
|
||||
* Necessary to not iterate over the values of the map below.
|
||||
*/
|
||||
public tileCategories: TileCategoryInformation[] = [];
|
||||
public listTiles: TileCategoryInformation[];
|
||||
|
||||
/**
|
||||
* Map of information about the categories relating to their id.
|
||||
*/
|
||||
public informationOfMotionsInTileCategories: { [id: number]: TileCategoryInformation } = {};
|
||||
private motionTiles: TileCategoryInformation[] = [];
|
||||
|
||||
private categoryTiles: TileCategoryInformation[] = [];
|
||||
|
||||
/**
|
||||
* The verbose name for the motions.
|
||||
@ -257,34 +257,87 @@ export class MotionListComponent extends BaseListViewComponent<ViewMotion> imple
|
||||
|
||||
this.motionRepo.getViewModelListObservable().subscribe(motions => {
|
||||
if (motions && motions.length) {
|
||||
this.createTiles(motions);
|
||||
this.createMotionTiles(motions);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private createTiles(motions: ViewMotion[]): void {
|
||||
this.informationOfMotionsInTileCategories = {};
|
||||
/**
|
||||
* Publishes the tileList
|
||||
*/
|
||||
private createTileList(): void {
|
||||
this.listTiles = this.categoryTiles.concat(this.motionTiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the tiles for categories.
|
||||
* Filters thous without parent, sorts them by theit weight, maps them to TileInfo and publishes
|
||||
* the result
|
||||
*/
|
||||
private createCategoryTiles(categories: ViewCategory[]): void {
|
||||
this.categoryTiles = categories
|
||||
.filter(category => !category.parent_id && !!category.totalAmountOfMotions)
|
||||
.sort((a, b) => a.weight - b.weight)
|
||||
.map(category => {
|
||||
return {
|
||||
filter: 'category',
|
||||
name: category.name,
|
||||
condition: category.id,
|
||||
amountOfMotions: category.totalAmountOfMotions,
|
||||
prefix: category.prefix
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the tiles for motions
|
||||
* @param motions
|
||||
*/
|
||||
private createMotionTiles(motions: ViewMotion[]): void {
|
||||
this.motionTiles = [];
|
||||
let favoriteMotions = 0;
|
||||
let motionsWithNotes = 0;
|
||||
let motionsWithoutCategory = 0;
|
||||
const localCategories = new Set<ViewCategory>();
|
||||
|
||||
for (const motion of motions) {
|
||||
if (motion.star) {
|
||||
this.countMotions(-1, true, 'star', 'Favorites');
|
||||
}
|
||||
|
||||
if (motion.category_id) {
|
||||
this.countMotions(
|
||||
motion.category_id,
|
||||
motion.category_id,
|
||||
'category',
|
||||
motion.category.name,
|
||||
motion.category.prefix
|
||||
);
|
||||
if (!motion.category_id) {
|
||||
motionsWithoutCategory++;
|
||||
} else {
|
||||
this.countMotions(-2, null, 'category', 'No category');
|
||||
localCategories.add(motion.category.oldestParent);
|
||||
}
|
||||
favoriteMotions += +this.motionHasProp(motion, 'star');
|
||||
motionsWithNotes += +this.motionHasProp(motion, 'hasNotes');
|
||||
}
|
||||
|
||||
this.tileCategories = Object.values(this.informationOfMotionsInTileCategories).sort((a, b) =>
|
||||
('' + a.prefix).localeCompare(b.prefix)
|
||||
);
|
||||
this.addToTileInfo('Favorites', 'star', true, favoriteMotions);
|
||||
this.addToTileInfo('Personal Note', 'hasNote', true, motionsWithNotes);
|
||||
this.addToTileInfo('No category', 'category', null, motionsWithoutCategory);
|
||||
|
||||
this.createCategoryTiles(Array.from(localCategories));
|
||||
|
||||
this.createTileList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns true if the motion has the given prop
|
||||
*/
|
||||
private motionHasProp(motion: ViewMotion, property: string, positive: boolean = true): boolean {
|
||||
return !!motion[property] === positive ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to add new tile information to the tileCategories-List
|
||||
*/
|
||||
private addToTileInfo(name: string, filter: string, condition: OsFilterOptionCondition, amount: number): void {
|
||||
if (amount) {
|
||||
this.motionTiles.push({
|
||||
filter: filter,
|
||||
name: name,
|
||||
condition: condition,
|
||||
amountOfMotions: amount
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -338,37 +391,6 @@ export class MotionListComponent extends BaseListViewComponent<ViewMotion> imple
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to count the motions in their related categories.
|
||||
*
|
||||
* @param id The key of TileCategory in `informationOfMotionsInTileCategories` object
|
||||
* @param condition The condition, if the tile is selected
|
||||
* @param filter The filter, if the tile is selected
|
||||
* @param name The title of the tile
|
||||
* @param prefix The prefix of the category
|
||||
*/
|
||||
private countMotions(
|
||||
id: number,
|
||||
condition: number | boolean | null,
|
||||
filter: string,
|
||||
name: string,
|
||||
prefix?: string
|
||||
): void {
|
||||
let info = this.informationOfMotionsInTileCategories[id];
|
||||
if (info) {
|
||||
++info.amountOfMotions;
|
||||
} else {
|
||||
info = {
|
||||
filter,
|
||||
name,
|
||||
condition,
|
||||
prefix,
|
||||
amountOfMotions: 1
|
||||
};
|
||||
}
|
||||
this.informationOfMotionsInTileCategories[id] = info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps multiselect actions to close the multiselect mode or throw an error if one happens.
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user