Rework sort and filter
More observable based, more scaleable filtering based on an old branch of @FinnStutzenstein. cleans some functions and provides some differend features.
This commit is contained in:
parent
65dbf37106
commit
850fcbe243
@ -136,7 +136,7 @@ export class DataStoreUpdateManagerService {
|
|||||||
if (this.currentUpdateSlot) {
|
if (this.currentUpdateSlot) {
|
||||||
const request = new Deferred();
|
const request = new Deferred();
|
||||||
this.updateSlotRequests.push(request);
|
this.updateSlotRequests.push(request);
|
||||||
await request.promise;
|
await request;
|
||||||
}
|
}
|
||||||
this.currentUpdateSlot = new UpdateSlot(DS);
|
this.currentUpdateSlot = new UpdateSlot(DS);
|
||||||
return this.currentUpdateSlot;
|
return this.currentUpdateSlot;
|
||||||
|
@ -120,7 +120,7 @@ export class OperatorService implements OnAfterAppsLoaded {
|
|||||||
private readonly _loaded: Deferred<void> = new Deferred();
|
private readonly _loaded: Deferred<void> = new Deferred();
|
||||||
|
|
||||||
public get loaded(): Promise<void> {
|
public get loaded(): Promise<void> {
|
||||||
return this._loaded.promise;
|
return this._loaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -51,7 +51,7 @@ export class PingService {
|
|||||||
isStable.resolve();
|
isStable.resolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all([gotConstants.promise, isStable.promise]);
|
await Promise.all([gotConstants, isStable]);
|
||||||
|
|
||||||
// Connects the ping-pong mechanism to the opening and closing of the connection.
|
// Connects the ping-pong mechanism to the opening and closing of the connection.
|
||||||
this.websocketService.closeEvent.subscribe(() => this.stopPing());
|
this.websocketService.closeEvent.subscribe(() => this.stopPing());
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
* //
|
* //
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export class Deferred<T = void> {
|
export class Deferred<T = void> extends Promise<T> {
|
||||||
/**
|
/**
|
||||||
* The promise to wait for
|
* The promise to wait for
|
||||||
*/
|
*/
|
||||||
@ -28,9 +28,11 @@ export class Deferred<T = void> {
|
|||||||
* Creates the promise and overloads the resolve function
|
* Creates the promise and overloads the resolve function
|
||||||
*/
|
*/
|
||||||
public constructor() {
|
public constructor() {
|
||||||
this.promise = new Promise<T>(resolve => {
|
let preResolve: (val?: T) => void;
|
||||||
this.resolve = resolve;
|
super(resolve => {
|
||||||
|
preResolve = resolve;
|
||||||
});
|
});
|
||||||
|
this._resolve = preResolve;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,19 +1,16 @@
|
|||||||
import { auditTime } from 'rxjs/operators';
|
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
|
||||||
|
|
||||||
import { BaseRepository } from 'app/core/repositories/base-repository';
|
import { BaseModel } from 'app/shared/models/base/base-model';
|
||||||
import { BaseModel } from '../../shared/models/base/base-model';
|
import { BaseRepository } from '../repositories/base-repository';
|
||||||
import { BaseViewModel } from '../../site/base/base-view-model';
|
import { BaseViewModel } from '../../site/base/base-view-model';
|
||||||
import { StorageService } from '../core-services/storage.service';
|
import { StorageService } from '../core-services/storage.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes the available filters for a listView.
|
* Describes the available filters for a listView.
|
||||||
* @param isActive: the current state of the filter
|
|
||||||
* @param property: the ViewModel's property or method to filter by
|
* @param property: the ViewModel's property or method to filter by
|
||||||
* @param label: An optional, different label (if not present, the property will be used)
|
* @param label: An optional, different label (if not present, the property will be used)
|
||||||
* @param condition: The conditions to be met for a successful display of data. These will
|
|
||||||
* be updated by the {@link filterMenu}
|
|
||||||
* @param options a list of available options for a filter
|
* @param options a list of available options for a filter
|
||||||
|
* @param count
|
||||||
*/
|
*/
|
||||||
export interface OsFilter {
|
export interface OsFilter {
|
||||||
property: string;
|
property: string;
|
||||||
@ -37,248 +34,296 @@ export type OsFilterOptions = (OsFilterOption | string)[];
|
|||||||
*/
|
*/
|
||||||
export interface OsFilterOption {
|
export interface OsFilterOption {
|
||||||
label: string;
|
label: string;
|
||||||
condition: string | boolean | number | number[];
|
condition: OsFilterOptionCondition;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the type of a filter condition
|
||||||
|
*/
|
||||||
|
type OsFilterOptionCondition = string | boolean | number | number[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter for the list view. List views can subscribe to its' dataService (providing filter definitions)
|
* Filter for the list view. List views can subscribe to its' dataService (providing filter definitions)
|
||||||
* and will receive their filtered data as observable
|
* and will receive their filtered data as observable
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export abstract class BaseFilterListService<V extends BaseViewModel> {
|
export abstract class BaseFilterListService<V extends BaseViewModel> {
|
||||||
/**
|
/**
|
||||||
* stores the currently used raw data to be used for the filter
|
* stores the currently used raw data to be used for the filter
|
||||||
*/
|
*/
|
||||||
protected currentRawData: V[];
|
private inputData: V[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription for the inputData list.
|
||||||
|
* Acts as an semaphore for new filtered data
|
||||||
|
*/
|
||||||
|
protected inputDataSubscription: Subscription;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The currently used filters.
|
* The currently used filters.
|
||||||
*/
|
*/
|
||||||
public filterDefinitions: OsFilter[];
|
public filterDefinitions: OsFilter[];
|
||||||
|
|
||||||
/**
|
|
||||||
* The observable output for the filtered data
|
|
||||||
*/
|
|
||||||
public filterDataOutput = new BehaviorSubject<V[]>([]);
|
|
||||||
|
|
||||||
protected filteredData: V[];
|
|
||||||
|
|
||||||
protected name: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns the total count of items before the filter
|
* @returns the total count of items before the filter
|
||||||
*/
|
*/
|
||||||
public get totalCount(): number {
|
public get unfilteredCount(): number {
|
||||||
return this.currentRawData ? this.currentRawData.length : 0;
|
return this.inputData ? this.inputData.length : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The observable output for the filtered data
|
||||||
|
*/
|
||||||
|
private readonly outputSubject = new BehaviorSubject<V[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Observable data for the filtered output subject
|
||||||
|
*/
|
||||||
|
public get outputObservable(): Observable<V[]> {
|
||||||
|
return this.outputSubject.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns the amount of items that pass the filter service's filters
|
* @returns the amount of items that pass the filter service's filters
|
||||||
*/
|
*/
|
||||||
public get filteredCount(): number {
|
public get filteredCount(): number {
|
||||||
return this.filteredData ? this.filteredData.length : 0;
|
return this.outputSubject.getValue().length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the amount of filters currently in use by this filter Service
|
* @returns the amount of currently active filters
|
||||||
*
|
|
||||||
* @returns a number of filters
|
|
||||||
*/
|
*/
|
||||||
public get activeFilterCount(): number {
|
public get activeFilterCount(): number {
|
||||||
if (!this.filterDefinitions || !this.filterDefinitions.length) {
|
return this.filterDefinitions ? this.filterDefinitions.filter(filter => filter.count).length : 0;
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
let filters = 0;
|
|
||||||
for (const filter of this.filterDefinitions) {
|
|
||||||
if (filter.count) {
|
|
||||||
filters += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filters;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boolean indicationg if there are any filters described in this service
|
* Boolean indicating if there are any filters described in this service
|
||||||
*
|
*
|
||||||
* @returns true if there are defined filters (regardless of current state)
|
* @returns true if there are defined filters (regardless of current state)
|
||||||
*/
|
*/
|
||||||
public get hasFilterOptions(): boolean {
|
public get hasFilterOptions(): boolean {
|
||||||
return this.filterDefinitions && this.filterDefinitions.length ? true : false;
|
return !!this.filterDefinitions && this.filterDefinitions.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param name the name of the filter service
|
||||||
|
* @param store storage service, to read saved filter variables
|
||||||
*/
|
*/
|
||||||
public constructor(protected store: StorageService, protected repo: BaseRepository<V, BaseModel>) {}
|
public constructor(protected name: string, private store: StorageService) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the filterService. Returns the filtered data as Observable
|
* Initializes the filterService.
|
||||||
|
*
|
||||||
|
* @param inputData Observable array with ViewModels
|
||||||
*/
|
*/
|
||||||
public filter(): Observable<V[]> {
|
public async initFilters(inputData: Observable<V[]>): Promise<void> {
|
||||||
this.repo
|
const storedFilter = await this.store.get<OsFilter[]>('filter_' + this.name);
|
||||||
.getViewModelListObservable()
|
|
||||||
.pipe(auditTime(10))
|
if (storedFilter) {
|
||||||
.subscribe(data => {
|
this.filterDefinitions = storedFilter;
|
||||||
this.currentRawData = data;
|
} else {
|
||||||
this.filteredData = this.filterData(data);
|
this.filterDefinitions = this.getFilterDefinitions();
|
||||||
this.filterDataOutput.next(this.filteredData);
|
this.storeActiveFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.inputDataSubscription) {
|
||||||
|
this.inputDataSubscription.unsubscribe();
|
||||||
|
this.inputDataSubscription = null;
|
||||||
|
}
|
||||||
|
this.inputDataSubscription = inputData.subscribe(data => {
|
||||||
|
this.inputData = data;
|
||||||
|
this.updateFilteredData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enforce children implement a method that returns actual filter definitions
|
||||||
|
*/
|
||||||
|
protected abstract getFilterDefinitions(): OsFilter[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes the filter definition from children and using {@link getFilterDefinitions}
|
||||||
|
* and sets/updates {@link filterDefinitions}
|
||||||
|
*/
|
||||||
|
public setFilterDefinitions(): void {
|
||||||
|
if (this.filterDefinitions) {
|
||||||
|
const newDefinitions = this.getFilterDefinitions();
|
||||||
|
this.store.get('filter_' + this.name).then((storedDefinition: OsFilter[]) => {
|
||||||
|
for (const newDef of newDefinitions) {
|
||||||
|
let count = 0;
|
||||||
|
const matchingExistingFilter = storedDefinition.find(oldDef => oldDef.property === newDef.property);
|
||||||
|
for (const option of newDef.options) {
|
||||||
|
if (typeof option === 'object') {
|
||||||
|
if (matchingExistingFilter && matchingExistingFilter.options) {
|
||||||
|
const existingOption = matchingExistingFilter.options.find(
|
||||||
|
o =>
|
||||||
|
typeof o !== 'string' &&
|
||||||
|
JSON.stringify(o.condition) === JSON.stringify(option.condition)
|
||||||
|
) as OsFilterOption;
|
||||||
|
if (existingOption) {
|
||||||
|
option.isActive = existingOption.isActive;
|
||||||
|
}
|
||||||
|
if (option.isActive) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newDef.count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.filterDefinitions = newDefinitions;
|
||||||
|
this.storeActiveFilters();
|
||||||
});
|
});
|
||||||
this.loadStorageDefinition(this.filterDefinitions);
|
}
|
||||||
return this.filterDataOutput;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to get the `viewModelListObservable` of a given repository object and creates dynamic filters for them
|
||||||
|
*
|
||||||
|
* @param repo repository to create dynamic filters from
|
||||||
|
* @param filter the OSFilter for the filter property
|
||||||
|
* @param noneOptionLabel The label of the non option, if set
|
||||||
|
* @param exexcludeIds Set if certain ID's should be excluded from filtering
|
||||||
|
*/
|
||||||
|
protected updateFilterForRepo(
|
||||||
|
repo: BaseRepository<BaseViewModel, BaseModel>,
|
||||||
|
filter: OsFilter,
|
||||||
|
noneOptionLabel?: string,
|
||||||
|
excludeIds?: number[]
|
||||||
|
): void {
|
||||||
|
repo.getViewModelListObservable().subscribe(viewModel => {
|
||||||
|
if (viewModel && viewModel.length) {
|
||||||
|
let filterProperties: (OsFilterOption | string)[];
|
||||||
|
|
||||||
|
filterProperties = viewModel
|
||||||
|
.filter(model => (excludeIds && excludeIds.length ? !excludeIds.includes(model.id) : true))
|
||||||
|
.map(model => {
|
||||||
|
return {
|
||||||
|
condition: model.id,
|
||||||
|
label: model.getTitle()
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
filterProperties.push('-');
|
||||||
|
filterProperties.push({
|
||||||
|
condition: null,
|
||||||
|
label: noneOptionLabel
|
||||||
|
});
|
||||||
|
|
||||||
|
filter.options = filterProperties;
|
||||||
|
this.setFilterDefinitions();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the filtered data and store the current filter options
|
||||||
|
*/
|
||||||
|
public storeActiveFilters(): void {
|
||||||
|
this.updateFilteredData();
|
||||||
|
this.store.set('filter_' + this.name, this.filterDefinitions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies current filters in {@link filterDefinitions} to the {@link inputData} list
|
||||||
|
* and publishes the filtered data to the observable {@link outputSubject}
|
||||||
|
*/
|
||||||
|
private updateFilteredData(): void {
|
||||||
|
let filteredData: V[];
|
||||||
|
if (!this.inputData) {
|
||||||
|
filteredData = [];
|
||||||
|
} else {
|
||||||
|
const preFilteredList = this.preFilter(this.inputData);
|
||||||
|
if (preFilteredList) {
|
||||||
|
this.inputData = preFilteredList;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.filterDefinitions || !this.filterDefinitions.length) {
|
||||||
|
filteredData = this.inputData;
|
||||||
|
} else {
|
||||||
|
filteredData = this.inputData.filter(item =>
|
||||||
|
this.filterDefinitions.every(filter => !filter.count || this.checkIncluded(item, filter))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.outputSubject.next(filteredData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Had to be overwritten by children if required
|
||||||
|
* Adds the possibility to filter the inputData before the user applied filter
|
||||||
|
*
|
||||||
|
* @param rawInputData will be set to {@link this.inputData}
|
||||||
|
* @returns should be a filtered version of `rawInputData`. Returns void if unused
|
||||||
|
*/
|
||||||
|
protected preFilter(rawInputData: V[]): V[] | void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles a filter option, to be called after a checkbox state has changed.
|
||||||
|
*
|
||||||
|
* @param filterName a filter name as string
|
||||||
|
* @param option filter option
|
||||||
|
*/
|
||||||
|
public toggleFilterOption(filterName: string, option: OsFilterOption): void {
|
||||||
|
option.isActive ? this.removeFilterOption(filterName, option) : this.addFilterOption(filterName, option);
|
||||||
|
this.storeActiveFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply a newly created filter
|
* Apply a newly created filter
|
||||||
* @param filter
|
*
|
||||||
|
* @param filterProperty new filter as string
|
||||||
|
* @param option filter option
|
||||||
*/
|
*/
|
||||||
public addFilterOption(filterName: string, option: OsFilterOption): void {
|
protected addFilterOption(filterProperty: string, option: OsFilterOption): void {
|
||||||
const filter = this.filterDefinitions.find(f => f.property === filterName);
|
const filter = this.filterDefinitions.find(f => f.property === filterProperty);
|
||||||
if (filter) {
|
if (filter) {
|
||||||
const filterOption = filter.options.find(
|
const filterOption = filter.options.find(
|
||||||
o => typeof o !== 'string' && o.condition === option.condition
|
o => typeof o !== 'string' && o.condition === option.condition
|
||||||
) as OsFilterOption;
|
) as OsFilterOption;
|
||||||
if (filterOption && !filterOption.isActive) {
|
if (filterOption && !filterOption.isActive) {
|
||||||
filterOption.isActive = true;
|
filterOption.isActive = true;
|
||||||
filter.count += 1;
|
if (!filter.count) {
|
||||||
|
filter.count = 1;
|
||||||
|
} else {
|
||||||
|
filter.count += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (filter.count === 1) {
|
|
||||||
this.filteredData = this.filterData(this.filteredData);
|
|
||||||
} else {
|
|
||||||
this.filteredData = this.filterData(this.currentRawData);
|
|
||||||
}
|
|
||||||
this.filterDataOutput.next(this.filteredData);
|
|
||||||
this.setStorageDefinition();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a filter option.
|
* Remove a filter option.
|
||||||
*
|
*
|
||||||
* @param filterName: The property name of this filter
|
* @param filterName The property name of this filter
|
||||||
* @param option: The option to disable
|
* @param option The option to disable
|
||||||
*/
|
*/
|
||||||
public removeFilterOption(filterName: string, option: OsFilterOption): void {
|
protected removeFilterOption(filterProperty: string, option: OsFilterOption): void {
|
||||||
const filter = this.filterDefinitions.find(f => f.property === filterName);
|
const filter = this.filterDefinitions.find(f => f.property === filterProperty);
|
||||||
if (filter) {
|
if (filter) {
|
||||||
const filterOption = filter.options.find(
|
const filterOption = filter.options.find(
|
||||||
o => typeof o !== 'string' && o.condition === option.condition
|
o => typeof o !== 'string' && o.condition === option.condition
|
||||||
) as OsFilterOption;
|
) as OsFilterOption;
|
||||||
if (filterOption && filterOption.isActive) {
|
if (filterOption && filterOption.isActive) {
|
||||||
filterOption.isActive = false;
|
filterOption.isActive = false;
|
||||||
filter.count -= 1;
|
if (filter.count) {
|
||||||
this.filteredData = this.filterData(this.currentRawData);
|
filter.count -= 1;
|
||||||
this.filterDataOutput.next(this.filteredData);
|
|
||||||
this.setStorageDefinition();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles a filter option, to be called after a checkbox state has changed.
|
|
||||||
* @param filterName
|
|
||||||
* @param option
|
|
||||||
*/
|
|
||||||
public toggleFilterOption(filterName: string, option: OsFilterOption): void {
|
|
||||||
option.isActive ? this.removeFilterOption(filterName, option) : this.addFilterOption(filterName, option);
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateFilterDefinitions(filters: OsFilter[]): void {
|
|
||||||
this.loadStorageDefinition(filters);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the currently saved filter definition from the StorageService,
|
|
||||||
* check their match with current definitions and set the current filter
|
|
||||||
* @param definitions: Currently defined Filter definitions
|
|
||||||
*/
|
|
||||||
protected loadStorageDefinition(definitions: OsFilter[]): void {
|
|
||||||
if (!definitions || !definitions.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const me = this;
|
|
||||||
this.store.get('filter_' + this.name).then(
|
|
||||||
function(storedData: { name: string; data: OsFilter[] }): void {
|
|
||||||
const storedFilters = storedData && storedData.data ? storedData.data : [];
|
|
||||||
definitions.forEach(definedFilter => {
|
|
||||||
const matchingStoreFilter = storedFilters.find(f => f.property === definedFilter.property);
|
|
||||||
let count = 0;
|
|
||||||
definedFilter.options.forEach(option => {
|
|
||||||
if (typeof option === 'string') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (matchingStoreFilter && matchingStoreFilter.options) {
|
|
||||||
const storedOption = matchingStoreFilter.options.find(
|
|
||||||
o =>
|
|
||||||
typeof o !== 'string' &&
|
|
||||||
(o.condition === option.condition ||
|
|
||||||
(Array.isArray(o.condition) &&
|
|
||||||
Array.isArray(option.condition) &&
|
|
||||||
o.label === option.label))
|
|
||||||
) as OsFilterOption;
|
|
||||||
if (storedOption) {
|
|
||||||
option.isActive = storedOption.isActive;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (option.isActive) {
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
definedFilter.count = count;
|
|
||||||
});
|
|
||||||
me.filterDefinitions = definitions;
|
|
||||||
me.filteredData = me.filterData(me.currentRawData);
|
|
||||||
me.filterDataOutput.next(me.filteredData);
|
|
||||||
},
|
|
||||||
function(error: any): void {
|
|
||||||
me.filteredData = me.filterData(me.currentRawData);
|
|
||||||
me.filterDataOutput.next(me.filteredData);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save the current filter definitions via StorageService
|
|
||||||
*/
|
|
||||||
private setStorageDefinition(): void {
|
|
||||||
this.store.set('filter_' + this.name, {
|
|
||||||
name: 'filter_' + this.name,
|
|
||||||
data: this.filterDefinitions
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes an array of data and applies current filters
|
|
||||||
*/
|
|
||||||
protected filterData(data: V[]): V[] {
|
|
||||||
const filteredData = [];
|
|
||||||
if (!data) {
|
|
||||||
return filteredData;
|
|
||||||
}
|
|
||||||
if (!this.filterDefinitions || !this.filterDefinitions.length) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
data.forEach(newItem => {
|
|
||||||
let excluded = false;
|
|
||||||
for (const filter of this.filterDefinitions) {
|
|
||||||
if (filter.count && !this.checkIncluded(newItem, filter)) {
|
|
||||||
excluded = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!excluded) {
|
}
|
||||||
filteredData.push(newItem);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return filteredData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a given ViewBaseModel passes the filter.
|
* Checks if a given ViewBaseModel passes the filter.
|
||||||
*
|
*
|
||||||
* @param item
|
* @param item Usually a view model
|
||||||
* @param filter
|
* @param filter The filter to check
|
||||||
* @returns true if the item is to be dispalyed according to the filter
|
* @returns true if the item is to be displayed according to the filter
|
||||||
*/
|
*/
|
||||||
private checkIncluded(item: V, filter: OsFilter): boolean {
|
private checkIncluded(item: V, filter: OsFilter): boolean {
|
||||||
const nullFilter = filter.options.find(
|
const nullFilter = filter.options.find(
|
||||||
@ -380,22 +425,30 @@ export abstract class BaseFilterListService<V extends BaseViewModel> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all active options of a given filter, clearing it
|
* Removes all active options of a given filter, clearing it
|
||||||
|
*
|
||||||
* @param filter
|
* @param filter
|
||||||
|
* @param update
|
||||||
*/
|
*/
|
||||||
public clearFilter(filter: OsFilter): void {
|
public clearFilter(filter: OsFilter, update: boolean = true): void {
|
||||||
filter.options.forEach(option => {
|
filter.options.forEach(option => {
|
||||||
if (typeof option === 'object' && option.isActive) {
|
if (typeof option === 'object' && option.isActive) {
|
||||||
this.removeFilterOption(filter.property, option);
|
this.removeFilterOption(filter.property, option);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (update) {
|
||||||
|
this.storeActiveFilters();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all filters currently in use from this filterService
|
* Removes all filters currently in use from this filterService
|
||||||
*/
|
*/
|
||||||
public clearAllFilters(): void {
|
public clearAllFilters(): void {
|
||||||
this.filterDefinitions.forEach(filter => {
|
if (this.filterDefinitions && this.filterDefinitions.length) {
|
||||||
this.clearFilter(filter);
|
this.filterDefinitions.forEach(filter => {
|
||||||
});
|
this.clearFilter(filter, false);
|
||||||
|
});
|
||||||
|
this.storeActiveFilters();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,155 +1,191 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { BehaviorSubject, Subscription, Observable } from 'rxjs';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
|
||||||
import { BaseViewModel } from '../../site/base/base-view-model';
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { BaseViewModel } from '../../site/base/base-view-model';
|
||||||
import { StorageService } from '../core-services/storage.service';
|
import { StorageService } from '../core-services/storage.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes the sorting columns of an associated ListView, and their state.
|
* Describes the sorting columns of an associated ListView, and their state.
|
||||||
*/
|
*/
|
||||||
export interface OsSortingDefinition<V> {
|
export interface OsSortingDefinition<V> {
|
||||||
sortProperty: keyof V;
|
sortProperty: keyof V;
|
||||||
sortAscending?: boolean;
|
sortAscending: boolean;
|
||||||
options: OsSortingItem<V>[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A sorting property (data may be a string, a number, a function, or an object
|
* A sorting property (data may be a string, a number, a function, or an object
|
||||||
* with a toString method) to sort after. Sorting will be done in {@link filterData}
|
* with a toString method) to sort after. Sorting will be done in {@link filterData}
|
||||||
*/
|
*/
|
||||||
export interface OsSortingItem<V> {
|
export interface OsSortingOption<V> {
|
||||||
property: keyof V;
|
property: keyof V;
|
||||||
label?: string;
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
/**
|
||||||
providedIn: 'root'
|
* Base class for generic sorting purposes
|
||||||
})
|
*/
|
||||||
export abstract class BaseSortListService<V extends BaseViewModel> {
|
export abstract class BaseSortListService<V extends BaseViewModel> {
|
||||||
/**
|
|
||||||
* Observable output that submits the newly sorted data each time a sorting has been done
|
|
||||||
*/
|
|
||||||
public sortedData = new BehaviorSubject<V[]>([]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data to be sorted. See also the setter for {@link data}
|
* The data to be sorted. See also the setter for {@link data}
|
||||||
*/
|
*/
|
||||||
private unsortedData: V[];
|
private inputData: V[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription for the inputData list.
|
||||||
|
* Acts as an semaphore for new filtered data
|
||||||
|
*/
|
||||||
|
private inputDataSubscription: Subscription | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable output that submits the newly sorted data each time a sorting has been done
|
||||||
|
*/
|
||||||
|
private outputSubject = new BehaviorSubject<V[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns the sorted output subject as observable
|
||||||
|
*/
|
||||||
|
public get outputObservable(): Observable<V[]> {
|
||||||
|
return this.outputSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current sorting definitions
|
* The current sorting definitions
|
||||||
*/
|
*/
|
||||||
public sortOptions: OsSortingDefinition<V>;
|
private sortDefinition: OsSortingDefinition<V>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* used for the key in the StorageService to save/load the correct sorting definitions.
|
* The sorting function according to current settings.
|
||||||
*/
|
|
||||||
protected name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The sorting function according to current settings. Set via {@link updateSortFn}.
|
|
||||||
*/
|
*/
|
||||||
private sortFn: (a: V, b: V) => number;
|
private sortFn: (a: V, b: V) => number;
|
||||||
|
|
||||||
/**
|
|
||||||
* default sorting to use if the client was not initialized before
|
|
||||||
*/
|
|
||||||
protected defaultSorting: keyof V = 'id';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor. Does nothing. TranslateService is used for localeCompeare.
|
|
||||||
*/
|
|
||||||
public constructor(protected translate: TranslateService, private store: StorageService) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put an array of data that you want sorted.
|
|
||||||
*/
|
|
||||||
public set data(data: V[]) {
|
|
||||||
this.unsortedData = data;
|
|
||||||
this.doAsyncSorting();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the sorting properties, and returns an observable with sorted data
|
|
||||||
* @param name arbitrary name, used to save/load correct saved settings from StorageService
|
|
||||||
* @param definitions The definitions of the possible options
|
|
||||||
*/
|
|
||||||
public sort(): BehaviorSubject<V[]> {
|
|
||||||
this.loadStorageDefinition();
|
|
||||||
this.updateSortFn();
|
|
||||||
return this.sortedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the current sorting order
|
* Set the current sorting order
|
||||||
|
*
|
||||||
|
* @param ascending ascending sorting if true, descending sorting if false
|
||||||
*/
|
*/
|
||||||
public set ascending(ascending: boolean) {
|
public set ascending(ascending: boolean) {
|
||||||
this.sortOptions.sortAscending = ascending;
|
this.sortDefinition.sortAscending = ascending;
|
||||||
this.updateSortFn();
|
this.updateSortDefinitions();
|
||||||
this.saveStorageDefinition();
|
|
||||||
this.doAsyncSorting();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get the current sorting order
|
* @param returns wether current the sorting is ascending or descending
|
||||||
*/
|
*/
|
||||||
public get ascending(): boolean {
|
public get ascending(): boolean {
|
||||||
return this.sortOptions.sortAscending;
|
return this.sortDefinition.sortAscending;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set the property of the viewModel the sorting will be based on.
|
* set the property of the viewModel the sorting will be based on.
|
||||||
* If the property stays the same, only the sort direction will be toggled,
|
* If the property stays the same, only the sort direction will be toggled,
|
||||||
* new sortProperty will result in an ascending order.
|
* new sortProperty will result in an ascending order.
|
||||||
|
*
|
||||||
|
* @param property a part of a view model
|
||||||
*/
|
*/
|
||||||
public set sortProperty(property: string) {
|
public set sortProperty(property: keyof V) {
|
||||||
if (this.sortOptions.sortProperty === (property as keyof V)) {
|
if (this.sortDefinition.sortProperty === property) {
|
||||||
this.ascending = !this.ascending;
|
this.ascending = !this.ascending;
|
||||||
this.updateSortFn();
|
|
||||||
} else {
|
} else {
|
||||||
this.sortOptions.sortProperty = property as keyof V;
|
this.sortDefinition.sortProperty = property;
|
||||||
this.sortOptions.sortAscending = true;
|
this.sortDefinition.sortAscending = true;
|
||||||
this.updateSortFn();
|
|
||||||
this.doAsyncSorting();
|
|
||||||
}
|
}
|
||||||
this.saveStorageDefinition();
|
this.updateSortDefinitions();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get the property of the viewModel the sorting is based on.
|
* @returns the current sorting property
|
||||||
*/
|
*/
|
||||||
public get sortProperty(): string {
|
public get sortProperty(): keyof V {
|
||||||
return this.sortOptions ? (this.sortOptions.sortProperty as string) : '';
|
return this.sortDefinition.sortProperty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns wether sorting is active or not
|
||||||
|
*/
|
||||||
public get isActive(): boolean {
|
public get isActive(): boolean {
|
||||||
return this.sortOptions && this.sortOptions.options.length > 0;
|
return this.sortDefinition && this.sortOptions.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enforce children to implement sortOptions
|
||||||
|
*/
|
||||||
|
public abstract sortOptions: OsSortingOption<V>[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param name the name of the sort view, required for store access
|
||||||
|
* @param translate required for language sensitive comparing
|
||||||
|
* @param store to save and load sorting preferences
|
||||||
|
*/
|
||||||
|
public constructor(protected name: string, protected translate: TranslateService, private store: StorageService) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enforce children to implement a method that returns the fault sorting
|
||||||
|
*/
|
||||||
|
protected abstract async getDefaultDefinition(): Promise<OsSortingDefinition<V>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the sorting properties, and returns an observable with sorted data
|
||||||
|
*
|
||||||
|
* @param name arbitrary name, used to save/load correct saved settings from StorageService
|
||||||
|
* @param definitions The definitions of the possible options
|
||||||
|
*/
|
||||||
|
public async initSorting(inputObservable: Observable<V[]>): Promise<void> {
|
||||||
|
if (this.inputDataSubscription) {
|
||||||
|
this.inputDataSubscription.unsubscribe();
|
||||||
|
this.inputDataSubscription = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.sortDefinition) {
|
||||||
|
this.sortDefinition = await this.store.get<OsSortingDefinition<V> | null>('sorting_' + this.name);
|
||||||
|
if (this.sortDefinition && this.sortDefinition.sortProperty) {
|
||||||
|
this.updateSortedData();
|
||||||
|
} else {
|
||||||
|
this.sortDefinition = await this.getDefaultDefinition();
|
||||||
|
this.updateSortDefinitions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.inputDataSubscription = inputObservable.subscribe(data => {
|
||||||
|
this.inputData = data;
|
||||||
|
this.updateSortedData();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the property and the sorting direction at the same time
|
* Change the property and the sorting direction at the same time
|
||||||
* @param property
|
*
|
||||||
* @param ascending
|
* @param property a sorting property of a view model
|
||||||
|
* @param ascending ascending or descending
|
||||||
*/
|
*/
|
||||||
public setSorting(property: string, ascending: boolean): void {
|
public setSorting(property: keyof V, ascending: boolean): void {
|
||||||
this.sortOptions.sortProperty = property as keyof V;
|
this.sortDefinition.sortProperty = property;
|
||||||
this.sortOptions.sortAscending = ascending;
|
this.sortDefinition.sortAscending = ascending;
|
||||||
this.saveStorageDefinition();
|
this.updateSortDefinitions();
|
||||||
this.updateSortFn();
|
|
||||||
this.doAsyncSorting();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the currently active icon for an option.
|
* Retrieves the currently active icon for an option.
|
||||||
|
*
|
||||||
* @param option
|
* @param option
|
||||||
|
* @returns the name of the sorting icon, fit to material icon ligatures
|
||||||
*/
|
*/
|
||||||
public getSortIcon(option: OsSortingItem<V>): string {
|
public getSortIcon(option: OsSortingOption<V>): string {
|
||||||
if (!this.sortProperty || this.sortProperty !== (option.property as string)) {
|
if (this.sortProperty !== option.property) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return this.ascending ? 'arrow_downward' : 'arrow_upward';
|
return this.ascending ? 'arrow_downward' : 'arrow_upward';
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSortLabel(option: OsSortingItem<V>): string {
|
/**
|
||||||
|
* Determines and returns an untranslated sorting label as string
|
||||||
|
*
|
||||||
|
* @param option The sorting option to a ViewModel
|
||||||
|
* @returns a sorting label as string
|
||||||
|
*/
|
||||||
|
public getSortLabel(option: OsSortingOption<V>): string {
|
||||||
if (option.label) {
|
if (option.label) {
|
||||||
return option.label;
|
return option.label;
|
||||||
}
|
}
|
||||||
@ -158,51 +194,17 @@ export abstract class BaseSortListService<V extends BaseViewModel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the currently saved sorting definition from the borwser's
|
* Saves the current sorting definitions to the local store
|
||||||
* store
|
|
||||||
*/
|
*/
|
||||||
private async loadStorageDefinition(): Promise<void> {
|
private updateSortDefinitions(): void {
|
||||||
const sorting: OsSortingDefinition<V> | null = await this.store.get('sorting_' + this.name);
|
this.updateSortedData();
|
||||||
if (sorting) {
|
this.store.set('sorting_' + this.name, this.sortDefinition);
|
||||||
if (sorting.sortProperty) {
|
|
||||||
this.sortOptions.sortProperty = sorting.sortProperty;
|
|
||||||
if (sorting.sortAscending !== undefined) {
|
|
||||||
this.sortOptions.sortAscending = sorting.sortAscending;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.sortOptions.sortProperty = this.defaultSorting;
|
|
||||||
this.sortOptions.sortAscending = true;
|
|
||||||
}
|
|
||||||
this.updateSortFn();
|
|
||||||
this.doAsyncSorting();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SSaves the current sorting definitions to the local store
|
|
||||||
*/
|
|
||||||
private saveStorageDefinition(): void {
|
|
||||||
this.store.set('sorting_' + this.name, {
|
|
||||||
sortProperty: this.sortProperty,
|
|
||||||
ascending: this.ascending
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* starts sorting, and
|
|
||||||
*/
|
|
||||||
private doAsyncSorting(): Promise<void> {
|
|
||||||
const me = this;
|
|
||||||
return new Promise(function(): void {
|
|
||||||
const data = me.unsortedData.sort(me.sortFn);
|
|
||||||
me.sortedData.next(data);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sorts an array of data synchronously, using the currently configured sorting
|
* Sorts an array of data synchronously, using the currently configured sorting
|
||||||
*
|
*
|
||||||
* @param data
|
* @param data Array of ViewModels
|
||||||
* @returns the data, sorted with the definitions of this service
|
* @returns the data, sorted with the definitions of this service
|
||||||
*/
|
*/
|
||||||
public sortSync(data: V[]): V[] {
|
public sortSync(data: V[]): V[] {
|
||||||
@ -213,60 +215,62 @@ export abstract class BaseSortListService<V extends BaseViewModel> {
|
|||||||
* Recreates the sorting function. Is supposed to be called on init and
|
* Recreates the sorting function. Is supposed to be called on init and
|
||||||
* every time the sorting (property, ascending/descending) or the language changes
|
* every time the sorting (property, ascending/descending) or the language changes
|
||||||
*/
|
*/
|
||||||
protected updateSortFn(): void {
|
protected updateSortedData(): void {
|
||||||
const property = this.sortProperty as string;
|
if (this.inputData) {
|
||||||
const ascending = this.ascending;
|
const property = this.sortProperty as string;
|
||||||
const intl = new Intl.Collator(this.translate.currentLang); // TODO: observe and update sorting on language change
|
const intl = new Intl.Collator(this.translate.currentLang);
|
||||||
|
this.outputSubject.next(
|
||||||
this.sortFn = function(itemA: V, itemB: V): number {
|
this.inputData.sort((itemA, itemB) => {
|
||||||
const firstProperty = ascending ? itemA[property] : itemB[property];
|
const firstProperty = this.ascending ? itemA[property] : itemB[property];
|
||||||
const secondProperty = ascending ? itemB[property] : itemA[property];
|
const secondProperty = this.ascending ? itemB[property] : itemA[property];
|
||||||
if (typeof firstProperty !== typeof secondProperty) {
|
if (typeof firstProperty !== typeof secondProperty) {
|
||||||
// undefined/null items should always land at the end
|
// undefined/null items should always land at the end
|
||||||
if (!firstProperty) {
|
|
||||||
return 1;
|
|
||||||
} else if (!secondProperty) {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
throw new TypeError('sorting of items failed because of mismatched types');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (
|
|
||||||
(firstProperty === null || firstProperty === undefined) &&
|
|
||||||
(secondProperty === null || secondProperty === undefined)
|
|
||||||
) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
switch (typeof firstProperty) {
|
|
||||||
case 'boolean':
|
|
||||||
if (firstProperty === false && secondProperty === true) {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
case 'number':
|
|
||||||
return firstProperty > secondProperty ? 1 : -1;
|
|
||||||
case 'string':
|
|
||||||
if (!firstProperty) {
|
if (!firstProperty) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
} else if (!secondProperty) {
|
||||||
return intl.compare(firstProperty, secondProperty);
|
return -1;
|
||||||
case 'function':
|
|
||||||
const a = firstProperty();
|
|
||||||
const b = secondProperty();
|
|
||||||
return intl.compare(a, b);
|
|
||||||
case 'object':
|
|
||||||
if (firstProperty instanceof Date) {
|
|
||||||
return firstProperty > secondProperty ? 1 : -1;
|
|
||||||
} else {
|
} else {
|
||||||
return intl.compare(firstProperty.toString(), secondProperty.toString());
|
throw new TypeError('sorting of items failed because of mismatched types');
|
||||||
}
|
}
|
||||||
case 'undefined':
|
} else {
|
||||||
return 1;
|
if (
|
||||||
default:
|
(firstProperty === null || firstProperty === undefined) &&
|
||||||
return -1;
|
(secondProperty === null || secondProperty === undefined)
|
||||||
}
|
) {
|
||||||
}
|
return 1;
|
||||||
};
|
}
|
||||||
|
switch (typeof firstProperty) {
|
||||||
|
case 'boolean':
|
||||||
|
if (firstProperty === false && secondProperty === true) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
case 'number':
|
||||||
|
return firstProperty > secondProperty ? 1 : -1;
|
||||||
|
case 'string':
|
||||||
|
if (!firstProperty) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return intl.compare(firstProperty, secondProperty);
|
||||||
|
case 'function':
|
||||||
|
const a = firstProperty();
|
||||||
|
const b = secondProperty();
|
||||||
|
return intl.compare(a, b);
|
||||||
|
case 'object':
|
||||||
|
if (firstProperty instanceof Date) {
|
||||||
|
return firstProperty > secondProperty ? 1 : -1;
|
||||||
|
} else {
|
||||||
|
return intl.compare(firstProperty.toString(), secondProperty.toString());
|
||||||
|
}
|
||||||
|
case 'undefined':
|
||||||
|
return 1;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
div.indent {
|
|
||||||
margin-left: 24px;
|
|
||||||
}
|
|
||||||
mat-divider {
|
|
||||||
margin-top: 5px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
div.filter-subtitle {
|
|
||||||
margin-top: 5px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
opacity: 0.9;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
<mat-nav-list class='main-nav'>
|
|
||||||
<mat-list-item *ngFor="let option of this.data.sortOptions.options" (click)="clickedOption(option.property)">
|
|
||||||
<button mat-menu-item>
|
|
||||||
<mat-icon>{{ data.getSortIcon(option) }}</mat-icon>
|
|
||||||
<span>{{ data.getSortLabel(option) | translate}}</span>
|
|
||||||
</button>
|
|
||||||
</mat-list-item>
|
|
||||||
</mat-nav-list>
|
|
@ -1,9 +1,9 @@
|
|||||||
<mat-accordion (keyup)=checkKeyEvent($event)>
|
<mat-accordion (keyup)="checkKeyEvent($event)">
|
||||||
<mat-expansion-panel *ngFor="let filter of service.filterDefinitions">
|
<mat-expansion-panel *ngFor="let filter of service.filterDefinitions">
|
||||||
<mat-expansion-panel-header *ngIf="filter.options && filter.options.length">
|
<mat-expansion-panel-header *ngIf="filter.options && filter.options.length">
|
||||||
<mat-panel-title>
|
<mat-panel-title>
|
||||||
<mat-icon>
|
<mat-icon>
|
||||||
{{ filter.count ? 'checked' : ''}}
|
{{ filter.count ? 'checked' : '' }}
|
||||||
</mat-icon>
|
</mat-icon>
|
||||||
<span>{{ service.getFilterName(filter) | translate }}</span>
|
<span>{{ service.getFilterName(filter) | translate }}</span>
|
||||||
</mat-panel-title>
|
</mat-panel-title>
|
||||||
@ -12,7 +12,11 @@
|
|||||||
<mat-action-list class="filtermenu-expanded">
|
<mat-action-list class="filtermenu-expanded">
|
||||||
<div *ngFor="let option of filter.options">
|
<div *ngFor="let option of filter.options">
|
||||||
<div *ngIf="isFilter(option)">
|
<div *ngIf="isFilter(option)">
|
||||||
<mat-checkbox [checked]="option.isActive" (change)="service.toggleFilterOption(filter.property, option)">
|
<mat-checkbox
|
||||||
|
class="filter-title"
|
||||||
|
[checked]="option.isActive"
|
||||||
|
(change)="service.toggleFilterOption(filter.property, option)"
|
||||||
|
>
|
||||||
{{ option.label | translate }}
|
{{ option.label | translate }}
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
</div>
|
</div>
|
@ -0,0 +1,30 @@
|
|||||||
|
div.indent {
|
||||||
|
margin-left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-divider {
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-expansion-panel {
|
||||||
|
width: 400px;
|
||||||
|
max-width: 95vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-subtitle {
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
opacity: 0.9;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
// adds breaks to mat-checkboxes with long labels
|
||||||
|
::ng-deep .mat-checkbox-layout {
|
||||||
|
white-space: normal !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// rather than center the checkbox, put the checkbox in the first line
|
||||||
|
::ng-deep .mat-checkbox-inner-container {
|
||||||
|
margin-top: 3px !important;
|
||||||
|
}
|
@ -58,6 +58,7 @@ export class FilterMenuComponent implements OnInit {
|
|||||||
this.dismissed.next(true);
|
this.dismissed.next(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public isFilter(option: OsFilterOption): boolean {
|
public isFilter(option: OsFilterOption): boolean {
|
||||||
return typeof option === 'string' ? false : true;
|
return typeof option === 'string' ? false : true;
|
||||||
}
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
<mat-nav-list class="main-nav">
|
||||||
|
<mat-list-item *ngFor="let option of this.data.sortOptions" (click)="clickedOption(option.property)">
|
||||||
|
<button mat-menu-item>
|
||||||
|
<mat-icon>{{ data.getSortIcon(option) }}</mat-icon>
|
||||||
|
<span>{{ data.getSortLabel(option) | translate }}</span>
|
||||||
|
</button>
|
||||||
|
</mat-list-item>
|
||||||
|
</mat-nav-list>
|
@ -1,11 +1,10 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { E2EImportsModule } from 'e2e-imports.module';
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
|
||||||
import { OsSortBottomSheetComponent } from './os-sort-bottom-sheet.component';
|
import { SortBottomSheetComponent } from './sort-bottom-sheet.component';
|
||||||
|
|
||||||
describe('OsSortBottomSheetComponent', () => {
|
describe('SortBottomSheetComponent', () => {
|
||||||
// let component: OsSortBottomSheetComponent<any>;
|
let fixture: ComponentFixture<SortBottomSheetComponent<any>>;
|
||||||
let fixture: ComponentFixture<OsSortBottomSheetComponent<any>>;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@ -14,7 +13,7 @@ describe('OsSortBottomSheetComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(OsSortBottomSheetComponent);
|
fixture = TestBed.createComponent(SortBottomSheetComponent);
|
||||||
// component = fixture.componentInstance;
|
// component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
@ -9,17 +9,17 @@ import { BaseViewModel } from 'app/site/base/base-view-model';
|
|||||||
* usage:
|
* usage:
|
||||||
* ```
|
* ```
|
||||||
* @ViewChild('sortBottomSheet')
|
* @ViewChild('sortBottomSheet')
|
||||||
* public sortBottomSheet: OsSortBottomSheetComponent<V>;
|
* public sortBottomSheet: SortBottomSheetComponent<V>;
|
||||||
* ...
|
* ...
|
||||||
* this.bottomSheet.open(OsSortBottomSheetComponent, { data: SortService });
|
* this.bottomSheet.open(SortBottomSheetComponent, { data: SortService });
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'os-sort-bottom-sheet',
|
selector: 'os-sort-bottom-sheet',
|
||||||
templateUrl: './os-sort-bottom-sheet.component.html',
|
templateUrl: './sort-bottom-sheet.component.html',
|
||||||
styleUrls: ['./os-sort-bottom-sheet.component.scss']
|
styleUrls: ['./sort-bottom-sheet.component.scss']
|
||||||
})
|
})
|
||||||
export class OsSortBottomSheetComponent<V extends BaseViewModel> implements OnInit {
|
export class SortBottomSheetComponent<V extends BaseViewModel> implements OnInit {
|
||||||
/**
|
/**
|
||||||
* Constructor. Gets a reference to itself (for closing after interaction)
|
* Constructor. Gets a reference to itself (for closing after interaction)
|
||||||
* @param data
|
* @param data
|
||||||
@ -31,10 +31,10 @@ export class OsSortBottomSheetComponent<V extends BaseViewModel> implements OnIn
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* init fucntion. Closes inmediately if no sorting is available.
|
* init function. Closes immediately if no sorting is available.
|
||||||
*/
|
*/
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
if (!this.data || !this.data.sortOptions || !this.data.sortOptions.options.length) {
|
if (!this.data || !this.data.sortOptions || !this.data.sortOptions.length) {
|
||||||
throw new Error('No sorting available for a sorting list');
|
throw new Error('No sorting available for a sorting list');
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
<div class="custom-table-header flex-spaced on-transition-fade">
|
<div class="custom-table-header flex-spaced on-transition-fade">
|
||||||
<div class="filter-count" *ngIf="filterService">
|
<div class="filter-count" *ngIf="filterService">
|
||||||
<span>{{ displayedCount }} </span><span translate>of</span>
|
<span>{{ displayedCount }} </span><span translate>of</span>
|
||||||
<span> {{ filterService.totalCount }}</span>
|
<span> {{ totalCount }}</span>
|
||||||
<span *ngIf="extraItemInfo"> · {{ extraItemInfo }}</span>
|
<span *ngIf="extraItemInfo"> · {{ extraItemInfo }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="current-filters" *ngIf="filterService && filterService.activeFilterCount">
|
<div class="current-filters" *ngIf="filterService && filterService.activeFilterCount">
|
||||||
@ -61,12 +61,9 @@
|
|||||||
<!-- non-mobile sorting menu -->
|
<!-- non-mobile sorting menu -->
|
||||||
<mat-menu #menu>
|
<mat-menu #menu>
|
||||||
<div *ngIf="hasSorting">
|
<div *ngIf="hasSorting">
|
||||||
<mat-list-item
|
<mat-list-item *ngFor="let option of sortOptions" (click)="sortOption = option">
|
||||||
*ngFor="let option of sortService.sortOptions.options"
|
|
||||||
(click)="sortService.sortProperty = option.property"
|
|
||||||
>
|
|
||||||
<button mat-menu-item>
|
<button mat-menu-item>
|
||||||
<mat-icon>{{ sortService.getSortIcon(option) }}</mat-icon>
|
<mat-icon>{{ getSortIcon(option) }}</mat-icon>
|
||||||
<span>{{ sortService.getSortLabel(option) | translate }}</span>
|
<span>{{ sortService.getSortLabel(option) | translate }}</span>
|
||||||
</button>
|
</button>
|
||||||
</mat-list-item>
|
</mat-list-item>
|
@ -1,10 +1,10 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { E2EImportsModule } from 'e2e-imports.module';
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
import { OsSortFilterBarComponent } from './os-sort-filter-bar.component';
|
import { SortFilterBarComponent } from './sort-filter-bar.component';
|
||||||
|
|
||||||
describe('OsSortFilterBarComponent', () => {
|
describe('OsSortFilterBarComponent', () => {
|
||||||
let component: OsSortFilterBarComponent<any>;
|
let component: SortFilterBarComponent<any>;
|
||||||
let fixture: ComponentFixture<any>;
|
let fixture: ComponentFixture<any>;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
@ -14,7 +14,7 @@ describe('OsSortFilterBarComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(OsSortFilterBarComponent);
|
fixture = TestBed.createComponent(SortFilterBarComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
@ -4,9 +4,9 @@ import { MatBottomSheet } from '@angular/material';
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||||
import { OsSortBottomSheetComponent } from './os-sort-bottom-sheet/os-sort-bottom-sheet.component';
|
import { SortBottomSheetComponent } from './sort-bottom-sheet/sort-bottom-sheet.component';
|
||||||
import { FilterMenuComponent } from './filter-menu/filter-menu.component';
|
import { FilterMenuComponent } from './filter-menu/filter-menu.component';
|
||||||
import { OsSortingItem } from 'app/core/ui-services/base-sort-list.service';
|
import { OsSortingOption } from 'app/core/ui-services/base-sort-list.service';
|
||||||
import { BaseSortListService } from 'app/core/ui-services/base-sort-list.service';
|
import { BaseSortListService } from 'app/core/ui-services/base-sort-list.service';
|
||||||
import { ViewportService } from 'app/core/ui-services/viewport.service';
|
import { ViewportService } from 'app/core/ui-services/viewport.service';
|
||||||
import { BaseFilterListService } from 'app/core/ui-services/base-filter-list.service';
|
import { BaseFilterListService } from 'app/core/ui-services/base-filter-list.service';
|
||||||
@ -27,10 +27,10 @@ import { BaseFilterListService } from 'app/core/ui-services/base-filter-list.ser
|
|||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'os-sort-filter-bar',
|
selector: 'os-sort-filter-bar',
|
||||||
templateUrl: './os-sort-filter-bar.component.html',
|
templateUrl: './sort-filter-bar.component.html',
|
||||||
styleUrls: ['./os-sort-filter-bar.component.scss']
|
styleUrls: ['./sort-filter-bar.component.scss']
|
||||||
})
|
})
|
||||||
export class OsSortFilterBarComponent<V extends BaseViewModel> {
|
export class SortFilterBarComponent<V extends BaseViewModel> {
|
||||||
/**
|
/**
|
||||||
* The currently active sorting service for the list view
|
* The currently active sorting service for the list view
|
||||||
*/
|
*/
|
||||||
@ -58,6 +58,7 @@ export class OsSortFilterBarComponent<V extends BaseViewModel> {
|
|||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
public searchFieldChange = new EventEmitter<string>();
|
public searchFieldChange = new EventEmitter<string>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The filter side drawer
|
* The filter side drawer
|
||||||
*/
|
*/
|
||||||
@ -68,7 +69,7 @@ export class OsSortFilterBarComponent<V extends BaseViewModel> {
|
|||||||
* The bottom sheet used to alter sorting in mobile view
|
* The bottom sheet used to alter sorting in mobile view
|
||||||
*/
|
*/
|
||||||
@ViewChild('sortBottomSheet')
|
@ViewChild('sortBottomSheet')
|
||||||
public sortBottomSheet: OsSortBottomSheetComponent<V>;
|
public sortBottomSheet: SortBottomSheetComponent<V>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The 'opened/active' state of the fulltext filter input field
|
* The 'opened/active' state of the fulltext filter input field
|
||||||
@ -87,6 +88,21 @@ export class OsSortFilterBarComponent<V extends BaseViewModel> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the total count of potential filters
|
||||||
|
*/
|
||||||
|
public get totalCount(): number {
|
||||||
|
return this.filterService.unfilteredCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get sortOptions(): any {
|
||||||
|
return this.sortService.sortOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set sortOption(option: OsSortingOption<V>) {
|
||||||
|
this.sortService.sortProperty = option.property;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor. Also creates a filtermenu component and a bottomSheet
|
* Constructor. Also creates a filtermenu component and a bottomSheet
|
||||||
* @param translate
|
* @param translate
|
||||||
@ -106,7 +122,7 @@ export class OsSortFilterBarComponent<V extends BaseViewModel> {
|
|||||||
*/
|
*/
|
||||||
public openSortDropDown(): void {
|
public openSortDropDown(): void {
|
||||||
if (this.vp.isMobile) {
|
if (this.vp.isMobile) {
|
||||||
const bottomSheetRef = this.bottomSheet.open(OsSortBottomSheetComponent, { data: this.sortService });
|
const bottomSheetRef = this.bottomSheet.open(SortBottomSheetComponent, { data: this.sortService });
|
||||||
bottomSheetRef.afterDismissed().subscribe(result => {
|
bottomSheetRef.afterDismissed().subscribe(result => {
|
||||||
if (result) {
|
if (result) {
|
||||||
this.sortService.sortProperty = result;
|
this.sortService.sortProperty = result;
|
||||||
@ -136,23 +152,18 @@ export class OsSortFilterBarComponent<V extends BaseViewModel> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if there is an active FilterService present
|
* Checks if there is an active FilterService present
|
||||||
|
* @returns wether the filters are present or not
|
||||||
*/
|
*/
|
||||||
public get hasFilters(): boolean {
|
public get hasFilters(): boolean {
|
||||||
if (this.filterService && this.filterService.hasFilterOptions) {
|
return this.filterService && this.filterService.hasFilterOptions;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the currently active icon for an option.
|
* Retrieves the currently active icon for an option.
|
||||||
* @param option
|
* @param option
|
||||||
*/
|
*/
|
||||||
public getSortIcon(option: OsSortingItem<V>): string {
|
public getSortIcon(option: OsSortingOption<V>): string {
|
||||||
if (this.sortService.sortProperty !== option.property) {
|
return this.sortService.getSortIcon(option);
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return this.sortService.ascending ? 'arrow_downward' : 'arrow_upward';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -160,7 +171,7 @@ export class OsSortFilterBarComponent<V extends BaseViewModel> {
|
|||||||
* the property is used.
|
* the property is used.
|
||||||
* @param option
|
* @param option
|
||||||
*/
|
*/
|
||||||
public getSortLabel(option: OsSortingItem<V>): string {
|
public getSortLabel(option: OsSortingOption<V>): string {
|
||||||
if (option.label) {
|
if (option.label) {
|
||||||
return option.label;
|
return option.label;
|
||||||
}
|
}
|
@ -68,9 +68,9 @@ import { PromptDialogComponent } from './components/prompt-dialog/prompt-dialog.
|
|||||||
import { SortingListComponent } from './components/sorting-list/sorting-list.component';
|
import { SortingListComponent } from './components/sorting-list/sorting-list.component';
|
||||||
import { SortingTreeComponent } from './components/sorting-tree/sorting-tree.component';
|
import { SortingTreeComponent } from './components/sorting-tree/sorting-tree.component';
|
||||||
import { ChoiceDialogComponent } from './components/choice-dialog/choice-dialog.component';
|
import { ChoiceDialogComponent } from './components/choice-dialog/choice-dialog.component';
|
||||||
import { OsSortFilterBarComponent } from './components/os-sort-filter-bar/os-sort-filter-bar.component';
|
import { SortFilterBarComponent } from './components/sort-filter-bar/sort-filter-bar.component';
|
||||||
import { OsSortBottomSheetComponent } from './components/os-sort-filter-bar/os-sort-bottom-sheet/os-sort-bottom-sheet.component';
|
import { SortBottomSheetComponent } from './components/sort-filter-bar/sort-bottom-sheet/sort-bottom-sheet.component';
|
||||||
import { FilterMenuComponent } from './components/os-sort-filter-bar/filter-menu/filter-menu.component';
|
import { FilterMenuComponent } from './components/sort-filter-bar/filter-menu/filter-menu.component';
|
||||||
import { LogoComponent } from './components/logo/logo.component';
|
import { LogoComponent } from './components/logo/logo.component';
|
||||||
import { C4DialogComponent, CopyrightSignComponent } from './components/copyright-sign/copyright-sign.component';
|
import { C4DialogComponent, CopyrightSignComponent } from './components/copyright-sign/copyright-sign.component';
|
||||||
import { ProjectorButtonComponent } from './components/projector-button/projector-button.component';
|
import { ProjectorButtonComponent } from './components/projector-button/projector-button.component';
|
||||||
@ -191,7 +191,7 @@ import { PrecisionPipe } from './pipes/precision.pipe';
|
|||||||
SortingListComponent,
|
SortingListComponent,
|
||||||
EditorModule,
|
EditorModule,
|
||||||
SortingTreeComponent,
|
SortingTreeComponent,
|
||||||
OsSortFilterBarComponent,
|
SortFilterBarComponent,
|
||||||
LogoComponent,
|
LogoComponent,
|
||||||
CopyrightSignComponent,
|
CopyrightSignComponent,
|
||||||
C4DialogComponent,
|
C4DialogComponent,
|
||||||
@ -220,8 +220,8 @@ import { PrecisionPipe } from './pipes/precision.pipe';
|
|||||||
SortingListComponent,
|
SortingListComponent,
|
||||||
SortingTreeComponent,
|
SortingTreeComponent,
|
||||||
ChoiceDialogComponent,
|
ChoiceDialogComponent,
|
||||||
OsSortFilterBarComponent,
|
SortFilterBarComponent,
|
||||||
OsSortBottomSheetComponent,
|
SortBottomSheetComponent,
|
||||||
FilterMenuComponent,
|
FilterMenuComponent,
|
||||||
LogoComponent,
|
LogoComponent,
|
||||||
CopyrightSignComponent,
|
CopyrightSignComponent,
|
||||||
@ -241,10 +241,10 @@ import { PrecisionPipe } from './pipes/precision.pipe';
|
|||||||
SearchValueSelectorComponent,
|
SearchValueSelectorComponent,
|
||||||
SortingListComponent,
|
SortingListComponent,
|
||||||
SortingTreeComponent,
|
SortingTreeComponent,
|
||||||
OsSortFilterBarComponent,
|
SortFilterBarComponent,
|
||||||
OsSortBottomSheetComponent,
|
SortBottomSheetComponent,
|
||||||
DecimalPipe
|
DecimalPipe
|
||||||
],
|
],
|
||||||
entryComponents: [OsSortBottomSheetComponent, C4DialogComponent]
|
entryComponents: [SortBottomSheetComponent, C4DialogComponent]
|
||||||
})
|
})
|
||||||
export class SharedModule {}
|
export class SharedModule {}
|
||||||
|
@ -30,7 +30,8 @@ import { StorageService } from 'app/core/core-services/storage.service';
|
|||||||
templateUrl: './agenda-list.component.html',
|
templateUrl: './agenda-list.component.html',
|
||||||
styleUrls: ['./agenda-list.component.scss']
|
styleUrls: ['./agenda-list.component.scss']
|
||||||
})
|
})
|
||||||
export class AgendaListComponent extends ListViewBaseComponent<ViewItem, Item> implements OnInit {
|
export class AgendaListComponent extends ListViewBaseComponent<ViewItem, Item, ItemRepositoryService>
|
||||||
|
implements OnInit {
|
||||||
/**
|
/**
|
||||||
* Determine the display columns in desktop view
|
* Determine the display columns in desktop view
|
||||||
*/
|
*/
|
||||||
@ -106,7 +107,7 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem, Item> i
|
|||||||
private agendaPdfService: AgendaPdfService,
|
private agendaPdfService: AgendaPdfService,
|
||||||
private pdfService: PdfDocumentService
|
private pdfService: PdfDocumentService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar, route, storage, filterService);
|
super(titleService, translate, matSnackBar, repo, route, storage, filterService);
|
||||||
|
|
||||||
// activate multiSelect mode for this listview
|
// activate multiSelect mode for this listview
|
||||||
this.canMultiSelect = true;
|
this.canMultiSelect = true;
|
||||||
@ -125,14 +126,6 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem, Item> i
|
|||||||
this.setFulltextFilter();
|
this.setFulltextFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onFilter(): void {
|
|
||||||
this.filterService.filter().subscribe(newAgendaItems => {
|
|
||||||
newAgendaItems.sort((a, b) => a.weight - b.weight);
|
|
||||||
this.dataSource.data = newAgendaItems;
|
|
||||||
this.checkSelection();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Links to the content object.
|
* Links to the content object.
|
||||||
*
|
*
|
||||||
|
@ -1,32 +1,34 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { auditTime, map } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { BaseFilterListService, OsFilter, OsFilterOption } from 'app/core/ui-services/base-filter-list.service';
|
import { BaseFilterListService, OsFilter, OsFilterOption } from 'app/core/ui-services/base-filter-list.service';
|
||||||
import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
|
import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||||
import { ViewItem } from '../models/view-item';
|
import { ViewItem } from '../models/view-item';
|
||||||
import { StorageService } from 'app/core/core-services/storage.service';
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter the agenda list
|
||||||
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class AgendaFilterListService extends BaseFilterListService<ViewItem> {
|
export class AgendaFilterListService extends BaseFilterListService<ViewItem> {
|
||||||
protected name = 'Agenda';
|
|
||||||
|
|
||||||
public filterOptions: OsFilter[] = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor. Also creates the dynamic filter options
|
* Constructor. Also creates the dynamic filter options
|
||||||
|
*
|
||||||
* @param store
|
* @param store
|
||||||
* @param repo
|
|
||||||
* @param translate Translation service
|
* @param translate Translation service
|
||||||
*/
|
*/
|
||||||
public constructor(store: StorageService, repo: ItemRepositoryService, private translate: TranslateService) {
|
public constructor(store: StorageService, private translate: TranslateService) {
|
||||||
super(store, repo);
|
super('Agenda', store);
|
||||||
this.filterOptions = [
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns the filter definition
|
||||||
|
*/
|
||||||
|
protected getFilterDefinitions(): OsFilter[] {
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
label: 'Visibility',
|
label: 'Visibility',
|
||||||
property: 'type',
|
property: 'type',
|
||||||
@ -41,37 +43,26 @@ export class AgendaFilterListService extends BaseFilterListService<ViewItem> {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
this.updateFilterDefinitions(this.filterOptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override from base filter list service: Added custom filtering of items
|
* @override from base filter list service
|
||||||
* Initializes the filterService. Returns the filtered data as Observable
|
*
|
||||||
|
* @returns the list of ViewItems without the types
|
||||||
*/
|
*/
|
||||||
public filter(): Observable<ViewItem[]> {
|
protected preFilter(viewItems: ViewItem[]): ViewItem[] {
|
||||||
this.repo
|
return viewItems.filter(item => item.type !== undefined);
|
||||||
.getViewModelListObservable()
|
|
||||||
.pipe(auditTime(10))
|
|
||||||
// Exclude items that are just there to provide a list of speakers. They have many
|
|
||||||
// restricted fields and must not be shown in the agenda!
|
|
||||||
.pipe(map(itemList => itemList.filter(item => item.type !== undefined)))
|
|
||||||
.subscribe(data => {
|
|
||||||
this.currentRawData = data;
|
|
||||||
this.filteredData = this.filterData(data);
|
|
||||||
this.filterDataOutput.next(this.filteredData);
|
|
||||||
});
|
|
||||||
this.loadStorageDefinition(this.filterDefinitions);
|
|
||||||
return this.filterDataOutput;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* helper function to create options for visibility filters
|
||||||
|
*
|
||||||
|
* @returns a list of choices to filter from
|
||||||
|
*/
|
||||||
private createVisibilityFilterOptions(): OsFilterOption[] {
|
private createVisibilityFilterOptions(): OsFilterOption[] {
|
||||||
const options = [];
|
return itemVisibilityChoices.map(choice => ({
|
||||||
itemVisibilityChoices.forEach(choice => {
|
condition: choice.key as number,
|
||||||
options.push({
|
label: choice.name
|
||||||
condition: choice.key as number,
|
}));
|
||||||
label: choice.name
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return options;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,9 @@ import { ViewAssignment, AssignmentPhases } from '../../models/view-assignment';
|
|||||||
templateUrl: './assignment-list.component.html',
|
templateUrl: './assignment-list.component.html',
|
||||||
styleUrls: ['./assignment-list.component.scss']
|
styleUrls: ['./assignment-list.component.scss']
|
||||||
})
|
})
|
||||||
export class AssignmentListComponent extends ListViewBaseComponent<ViewAssignment, Assignment> implements OnInit {
|
export class AssignmentListComponent
|
||||||
|
extends ListViewBaseComponent<ViewAssignment, Assignment, AssignmentRepositoryService>
|
||||||
|
implements OnInit {
|
||||||
/**
|
/**
|
||||||
* The different phases of an assignment. Info is fetched from server
|
* The different phases of an assignment. Info is fetched from server
|
||||||
*/
|
*/
|
||||||
@ -57,7 +59,7 @@ export class AssignmentListComponent extends ListViewBaseComponent<ViewAssignmen
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
public operator: OperatorService
|
public operator: OperatorService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar, route, storage, filterService, sortService);
|
super(titleService, translate, matSnackBar, repo, route, storage, filterService, sortService);
|
||||||
// activate multiSelect mode for this list view
|
// activate multiSelect mode for this list view
|
||||||
this.canMultiSelect = true;
|
this.canMultiSelect = true;
|
||||||
}
|
}
|
||||||
|
@ -1,53 +1,45 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { AssignmentRepositoryService } from 'app/core/repositories/assignments/assignment-repository.service';
|
import { BaseFilterListService, OsFilter, OsFilterOption } from 'app/core/ui-services/base-filter-list.service';
|
||||||
import { BaseFilterListService, OsFilter } from 'app/core/ui-services/base-filter-list.service';
|
|
||||||
import { StorageService } from 'app/core/core-services/storage.service';
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
import { ViewAssignment, AssignmentPhases } from '../models/view-assignment';
|
import { ViewAssignment, AssignmentPhases } from '../models/view-assignment';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter service for the assignment list
|
||||||
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class AssignmentFilterListService extends BaseFilterListService<ViewAssignment> {
|
export class AssignmentFilterListService extends BaseFilterListService<ViewAssignment> {
|
||||||
protected name = 'Assignment';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter for the current filter options
|
|
||||||
*
|
|
||||||
* @returns filter definitions to use
|
|
||||||
*/
|
|
||||||
public get filterOptions(): OsFilter[] {
|
|
||||||
return [this.phaseFilter];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter for assignment phases. Defined in the servers' constants
|
|
||||||
*/
|
|
||||||
public phaseFilter: OsFilter = {
|
|
||||||
property: 'phase',
|
|
||||||
options: []
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor. Activates the phase options subscription
|
* Constructor. Activates the phase options subscription
|
||||||
*
|
*
|
||||||
* @param store StorageService
|
* @param store StorageService
|
||||||
* @param assignmentRepo Repository
|
* @param translate translate service
|
||||||
* @param constants the openslides constant service to get the assignment options
|
|
||||||
*/
|
*/
|
||||||
public constructor(store: StorageService, assignmentRepo: AssignmentRepositoryService) {
|
public constructor(store: StorageService) {
|
||||||
super(store, assignmentRepo);
|
super('Assignments', store);
|
||||||
this.createPhaseOptions();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribes to the phases of an assignment that are defined in the server's
|
* @returns the filter definition
|
||||||
* constants
|
|
||||||
*/
|
*/
|
||||||
private createPhaseOptions(): void {
|
protected getFilterDefinitions(): OsFilter[] {
|
||||||
this.phaseFilter.options = AssignmentPhases.map(ap => {
|
return [
|
||||||
|
{
|
||||||
|
label: 'Phase',
|
||||||
|
property: 'phase',
|
||||||
|
options: this.createPhaseOptions()
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates options for assignment phases
|
||||||
|
*/
|
||||||
|
private createPhaseOptions(): OsFilterOption[] {
|
||||||
|
return AssignmentPhases.map(ap => {
|
||||||
return { label: ap.display_name, condition: ap.value, isActive: false };
|
return { label: ap.display_name, condition: ap.value, isActive: false };
|
||||||
});
|
});
|
||||||
this.updateFilterDefinitions(this.filterOptions);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,46 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { BaseSortListService, OsSortingDefinition } from 'app/core/ui-services/base-sort-list.service';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { BaseSortListService, OsSortingDefinition, OsSortingOption } from 'app/core/ui-services/base-sort-list.service';
|
||||||
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
import { ViewAssignment } from '../models/view-assignment';
|
import { ViewAssignment } from '../models/view-assignment';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorting service for the assignment list
|
||||||
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class AssignmentSortListService extends BaseSortListService<ViewAssignment> {
|
export class AssignmentSortListService extends BaseSortListService<ViewAssignment> {
|
||||||
public sortOptions: OsSortingDefinition<ViewAssignment> = {
|
/**
|
||||||
sortProperty: 'assignment',
|
* Define the sort options
|
||||||
sortAscending: true,
|
*/
|
||||||
options: [
|
public sortOptions: OsSortingOption<ViewAssignment>[] = [
|
||||||
{ property: 'assignment', label: 'Name' },
|
{ property: 'assignment', label: 'Name' },
|
||||||
{ property: 'phase', label: 'Phase' },
|
{ property: 'phase', label: 'Phase' },
|
||||||
{ property: 'candidateAmount', label: 'Number of candidates' }
|
{ property: 'candidateAmount', label: 'Number of candidates' }
|
||||||
]
|
];
|
||||||
};
|
|
||||||
protected name = 'Assignment';
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param translate required by parent
|
||||||
|
* @param storage required by parent
|
||||||
|
*/
|
||||||
|
public constructor(translate: TranslateService, storage: StorageService) {
|
||||||
|
super('Assignment', translate, storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required by parent
|
||||||
|
*
|
||||||
|
* @returns the default sorting strategy
|
||||||
|
*/
|
||||||
|
public async getDefaultDefinition(): Promise<OsSortingDefinition<ViewAssignment>> {
|
||||||
|
return {
|
||||||
|
sortProperty: 'assignment',
|
||||||
|
sortAscending: true
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,14 @@ import { BaseSortListService } from 'app/core/ui-services/base-sort-list.service
|
|||||||
import { BaseFilterListService } from 'app/core/ui-services/base-filter-list.service';
|
import { BaseFilterListService } from 'app/core/ui-services/base-filter-list.service';
|
||||||
import { BaseModel } from 'app/shared/models/base/base-model';
|
import { BaseModel } from 'app/shared/models/base/base-model';
|
||||||
import { StorageService } from 'app/core/core-services/storage.service';
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
|
import { BaseRepository } from 'app/core/repositories/base-repository';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
export abstract class ListViewBaseComponent<V extends BaseViewModel, M extends BaseModel> extends BaseViewComponent
|
export abstract class ListViewBaseComponent<
|
||||||
implements OnDestroy {
|
V extends BaseViewModel,
|
||||||
|
M extends BaseModel,
|
||||||
|
R extends BaseRepository<V, M>
|
||||||
|
> extends BaseViewComponent implements OnDestroy {
|
||||||
/**
|
/**
|
||||||
* The data source for a table. Requires to be initialized with a BaseViewModel
|
* The data source for a table. Requires to be initialized with a BaseViewModel
|
||||||
*/
|
*/
|
||||||
@ -76,21 +81,24 @@ export abstract class ListViewBaseComponent<V extends BaseViewModel, M extends B
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for list view bases
|
|
||||||
* @param titleService the title serivce
|
* @param titleService the title serivce
|
||||||
* @param translate the translate service
|
* @param translate the translate service
|
||||||
* @param matSnackBar showing errors
|
* @param matSnackBar showing errors
|
||||||
* @param filterService filter
|
* @param viewModelRepo Repository for the view Model. Do NOT rename to "repo"
|
||||||
* @param sortService sorting
|
* @param route Access the current route
|
||||||
|
* @param storage Access the store
|
||||||
|
* @param modelFilterListService filter do NOT rename to "filterListService"
|
||||||
|
* @param modelSortService sorting do NOT rename to "sortService"
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
titleService: Title,
|
titleService: Title,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
matSnackBar: MatSnackBar,
|
matSnackBar: MatSnackBar,
|
||||||
|
protected viewModelRepo: R,
|
||||||
protected route?: ActivatedRoute,
|
protected route?: ActivatedRoute,
|
||||||
protected storage?: StorageService,
|
protected storage?: StorageService,
|
||||||
public filterService?: BaseFilterListService<V>,
|
protected modelFilterListService?: BaseFilterListService<V>,
|
||||||
public sortService?: BaseSortListService<V>
|
protected modelSortService?: BaseSortListService<V>
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar);
|
super(titleService, translate, matSnackBar);
|
||||||
this.selectedRows = [];
|
this.selectedRows = [];
|
||||||
@ -114,40 +122,41 @@ export abstract class ListViewBaseComponent<V extends BaseViewModel, M extends B
|
|||||||
this.initializePagination();
|
this.initializePagination();
|
||||||
this.dataSource.paginator._intl.itemsPerPageLabel = this.translate.instant('items per page');
|
this.dataSource.paginator._intl.itemsPerPageLabel = this.translate.instant('items per page');
|
||||||
}
|
}
|
||||||
if (this.filterService) {
|
|
||||||
this.onFilter();
|
// TODO: Add subscription to this.subscriptions
|
||||||
}
|
if (this.modelFilterListService && this.modelSortService) {
|
||||||
if (this.sortService) {
|
// filtering and sorting
|
||||||
this.onSort();
|
this.modelFilterListService.initFilters(this.getModelListObservable());
|
||||||
|
this.modelSortService.initSorting(this.modelFilterListService.outputObservable);
|
||||||
|
this.modelSortService.outputObservable.subscribe(data => this.setDataSource(data));
|
||||||
|
} else if (this.modelFilterListService) {
|
||||||
|
// only filter service
|
||||||
|
this.modelFilterListService.initFilters(this.getModelListObservable());
|
||||||
|
this.modelFilterListService.outputObservable.subscribe(data => this.setDataSource(data));
|
||||||
|
} else if (this.modelSortService) {
|
||||||
|
// only sorting
|
||||||
|
this.modelSortService.initSorting(this.getModelListObservable());
|
||||||
|
this.modelSortService.outputObservable.subscribe(data => this.setDataSource(data));
|
||||||
|
} else {
|
||||||
|
// none of both
|
||||||
|
this.getModelListObservable().subscribe(data => this.setDataSource(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standard filtering function. Sufficient for most list views but can be overwritten
|
* Standard filtering function. Sufficient for most list views but can be overwritten
|
||||||
*/
|
*/
|
||||||
protected onFilter(): void {
|
protected getModelListObservable(): Observable<V[]> {
|
||||||
if (this.sortService) {
|
return this.viewModelRepo.getViewModelListObservable();
|
||||||
this.subscriptions.push(
|
|
||||||
this.filterService.filter().subscribe(filteredData => (this.sortService.data = filteredData))
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.filterService.filter().subscribe(filteredData => (this.dataSource.data = filteredData));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private setDataSource(data: V[]): void {
|
||||||
* Standard sorting function. Sufficient for most list views but can be overwritten
|
// the dataArray needs to be cleared (since angular 7)
|
||||||
*/
|
// changes are not detected properly anymore
|
||||||
protected onSort(): void {
|
this.dataSource.data = [];
|
||||||
this.subscriptions.push(
|
this.dataSource.data = data;
|
||||||
this.sortService.sort().subscribe(sortedData => {
|
|
||||||
// the dataArray needs to be cleared (since angular 7)
|
this.checkSelection();
|
||||||
// changes are not detected properly anymore
|
|
||||||
this.dataSource.data = [];
|
|
||||||
this.dataSource.data = sortedData;
|
|
||||||
this.checkSelection();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSortButton(itemProperty: string): void {
|
public onSortButton(itemProperty: string): void {
|
||||||
|
@ -25,7 +25,8 @@ import { langToLocale } from 'app/shared/utils/lang-to-locale';
|
|||||||
templateUrl: './history-list.component.html',
|
templateUrl: './history-list.component.html',
|
||||||
styleUrls: ['./history-list.component.scss']
|
styleUrls: ['./history-list.component.scss']
|
||||||
})
|
})
|
||||||
export class HistoryListComponent extends ListViewBaseComponent<ViewHistory, History> implements OnInit {
|
export class HistoryListComponent extends ListViewBaseComponent<ViewHistory, History, HistoryRepositoryService>
|
||||||
|
implements OnInit {
|
||||||
/**
|
/**
|
||||||
* Subject determine when the custom timestamp subject changes
|
* Subject determine when the custom timestamp subject changes
|
||||||
*/
|
*/
|
||||||
@ -51,7 +52,7 @@ export class HistoryListComponent extends ListViewBaseComponent<ViewHistory, His
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
private operator: OperatorService
|
private operator: OperatorService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar);
|
super(titleService, translate, matSnackBar, repo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,7 +26,8 @@ import { StorageService } from 'app/core/core-services/storage.service';
|
|||||||
templateUrl: './mediafile-list.component.html',
|
templateUrl: './mediafile-list.component.html',
|
||||||
styleUrls: ['./mediafile-list.component.scss']
|
styleUrls: ['./mediafile-list.component.scss']
|
||||||
})
|
})
|
||||||
export class MediafileListComponent extends ListViewBaseComponent<ViewMediafile, Mediafile> implements OnInit {
|
export class MediafileListComponent extends ListViewBaseComponent<ViewMediafile, Mediafile, MediafileRepositoryService>
|
||||||
|
implements OnInit {
|
||||||
/**
|
/**
|
||||||
* Holds the actions for logos. Updated via an observable
|
* Holds the actions for logos. Updated via an observable
|
||||||
*/
|
*/
|
||||||
@ -108,7 +109,7 @@ export class MediafileListComponent extends ListViewBaseComponent<ViewMediafile,
|
|||||||
public sortService: MediafilesSortListService,
|
public sortService: MediafilesSortListService,
|
||||||
private operator: OperatorService
|
private operator: OperatorService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar, route, storage, filterService, sortService);
|
super(titleService, translate, matSnackBar, repo, route, storage, filterService, sortService);
|
||||||
|
|
||||||
// enables multiSelection for this listView
|
// enables multiSelection for this listView
|
||||||
this.canMultiSelect = true;
|
this.canMultiSelect = true;
|
||||||
|
@ -1,65 +1,62 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { BaseFilterListService, OsFilter } from 'app/core/ui-services/base-filter-list.service';
|
import { BaseFilterListService, OsFilter } from 'app/core/ui-services/base-filter-list.service';
|
||||||
import { ViewMediafile } from '../models/view-mediafile';
|
|
||||||
import { StorageService } from 'app/core/core-services/storage.service';
|
|
||||||
import { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service';
|
|
||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { ViewMediafile } from '../models/view-mediafile';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter service for media files
|
||||||
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class MediafileFilterListService extends BaseFilterListService<ViewMediafile> {
|
export class MediafileFilterListService extends BaseFilterListService<ViewMediafile> {
|
||||||
protected name = 'Mediafile';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A filter checking if a file is a pdf or not
|
* Constructor.
|
||||||
*/
|
* Sets the filter options according to permissions
|
||||||
public pdfOption: OsFilter = {
|
*
|
||||||
property: 'type',
|
|
||||||
label: 'PDF',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
condition: 'application/pdf',
|
|
||||||
label: this.translate.instant('Is PDF file')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
condition: null,
|
|
||||||
label: this.translate.instant('Is no PDF file')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A filter checking if a file is hidden. Only included if the operator has permission to see hidden files
|
|
||||||
*/
|
|
||||||
public hiddenOptions: OsFilter = {
|
|
||||||
property: 'is_hidden',
|
|
||||||
label: this.translate.instant('Visibility'),
|
|
||||||
options: [
|
|
||||||
{ condition: true, label: this.translate.instant('is hidden') },
|
|
||||||
{ condition: false, label: this.translate.instant('is not hidden') }
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor. Sets the filter options according to permissions
|
|
||||||
* @param store
|
* @param store
|
||||||
* @param repo
|
|
||||||
* @param operator
|
* @param operator
|
||||||
* @param translate
|
* @param translate
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(store: StorageService, private operator: OperatorService, private translate: TranslateService) {
|
||||||
store: StorageService,
|
super('Mediafiles', store);
|
||||||
repo: MediafileRepositoryService,
|
|
||||||
operator: OperatorService,
|
this.operator.getUserObservable().subscribe(() => {
|
||||||
private translate: TranslateService
|
this.setFilterDefinitions();
|
||||||
) {
|
});
|
||||||
super(store, repo);
|
}
|
||||||
const filterOptions = operator.hasPerms('mediafiles.can_see_hidden')
|
|
||||||
? [this.hiddenOptions, this.pdfOption]
|
/**
|
||||||
: [this.pdfOption];
|
* @returns the filter definition
|
||||||
this.updateFilterDefinitions(filterOptions);
|
*/
|
||||||
|
protected getFilterDefinitions(): OsFilter[] {
|
||||||
|
const pdfOption: OsFilter = {
|
||||||
|
property: 'type',
|
||||||
|
label: 'PDF',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
condition: 'application/pdf',
|
||||||
|
label: this.translate.instant('Is PDF file')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
condition: null,
|
||||||
|
label: this.translate.instant('Is no PDF file')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const hiddenOptions: OsFilter = {
|
||||||
|
property: 'is_hidden',
|
||||||
|
label: this.translate.instant('Visibility'),
|
||||||
|
options: [
|
||||||
|
{ condition: true, label: this.translate.instant('is hidden') },
|
||||||
|
{ condition: false, label: this.translate.instant('is not hidden') }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.operator.hasPerms('mediafiles.can_see_hidden') ? [hiddenOptions, pdfOption] : [pdfOption];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,48 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { BaseSortListService, OsSortingDefinition } from 'app/core/ui-services/base-sort-list.service';
|
import { BaseSortListService, OsSortingDefinition, OsSortingOption } from 'app/core/ui-services/base-sort-list.service';
|
||||||
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { ViewMediafile } from '../models/view-mediafile';
|
import { ViewMediafile } from '../models/view-mediafile';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorting service for the mediafile list
|
||||||
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class MediafilesSortListService extends BaseSortListService<ViewMediafile> {
|
export class MediafilesSortListService extends BaseSortListService<ViewMediafile> {
|
||||||
public sortOptions: OsSortingDefinition<ViewMediafile> = {
|
public sortOptions: OsSortingOption<ViewMediafile>[] = [
|
||||||
sortProperty: 'title',
|
{ property: 'title' },
|
||||||
sortAscending: true,
|
{
|
||||||
options: [
|
property: 'type',
|
||||||
{ property: 'title' },
|
label: this.translate.instant('Type')
|
||||||
{
|
},
|
||||||
property: 'type',
|
{
|
||||||
label: this.translate.instant('Type')
|
property: 'size',
|
||||||
},
|
label: this.translate.instant('Size')
|
||||||
{
|
}
|
||||||
property: 'size',
|
];
|
||||||
label: this.translate.instant('Size')
|
|
||||||
}
|
/**
|
||||||
]
|
* Constructor.
|
||||||
};
|
*
|
||||||
protected name = 'Mediafile';
|
* @param translate required by parent
|
||||||
|
* @param store required by parent
|
||||||
|
*/
|
||||||
|
public constructor(translate: TranslateService, store: StorageService) {
|
||||||
|
super('Mediafiles', translate, store);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required by parent
|
||||||
|
*
|
||||||
|
* @returns the default sorting strategy
|
||||||
|
*/
|
||||||
|
public async getDefaultDefinition(): Promise<OsSortingDefinition<ViewMediafile>> {
|
||||||
|
return {
|
||||||
|
sortProperty: 'title',
|
||||||
|
sortAscending: true
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,10 +41,10 @@
|
|||||||
<span translate>Follow recommendations for all motions</span>
|
<span translate>Follow recommendations for all motions</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<table class="os-headed-listview-table on-transition-fade" mat-table [dataSource]="dataSource" matSort>
|
<table class="os-headed-listview-table on-transition-fade" mat-table [dataSource]="dataSource">
|
||||||
<!-- title column -->
|
<!-- title column -->
|
||||||
<ng-container matColumnDef="title">
|
<ng-container matColumnDef="title">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header> <span translate>Motion</span> </mat-header-cell>
|
<mat-header-cell *matHeaderCellDef> <span translate>Motion</span> </mat-header-cell>
|
||||||
<mat-cell *matCellDef="let motion">
|
<mat-cell *matCellDef="let motion">
|
||||||
{{ motion.getTitle() }}
|
{{ motion.getTitle() }}
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
|
@ -15,6 +15,7 @@ import { PromptService } from 'app/core/ui-services/prompt.service';
|
|||||||
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
||||||
import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
|
import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
|
||||||
import { StorageService } from 'app/core/core-services/storage.service';
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
|
import { Motion } from 'app/shared/models/motions/motion';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detail component to display one motion block
|
* Detail component to display one motion block
|
||||||
@ -24,17 +25,13 @@ import { StorageService } from 'app/core/core-services/storage.service';
|
|||||||
templateUrl: './motion-block-detail.component.html',
|
templateUrl: './motion-block-detail.component.html',
|
||||||
styleUrls: ['./motion-block-detail.component.scss']
|
styleUrls: ['./motion-block-detail.component.scss']
|
||||||
})
|
})
|
||||||
export class MotionBlockDetailComponent extends ListViewBaseComponent<ViewMotion, MotionBlock> implements OnInit {
|
export class MotionBlockDetailComponent extends ListViewBaseComponent<ViewMotion, Motion, MotionRepositoryService>
|
||||||
|
implements OnInit {
|
||||||
/**
|
/**
|
||||||
* Determines the block id from the given URL
|
* Determines the block id from the given URL
|
||||||
*/
|
*/
|
||||||
public block: ViewMotionBlock;
|
public block: ViewMotionBlock;
|
||||||
|
|
||||||
/**
|
|
||||||
* All motions in this block
|
|
||||||
*/
|
|
||||||
public motions: ViewMotion[];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the edit mode
|
* Determine the edit mode
|
||||||
*/
|
*/
|
||||||
@ -67,11 +64,11 @@ export class MotionBlockDetailComponent extends ListViewBaseComponent<ViewMotion
|
|||||||
storage: StorageService,
|
storage: StorageService,
|
||||||
private operator: OperatorService,
|
private operator: OperatorService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private repo: MotionBlockRepositoryService,
|
protected repo: MotionBlockRepositoryService,
|
||||||
private motionRepo: MotionRepositoryService,
|
protected motionRepo: MotionRepositoryService,
|
||||||
private promptService: PromptService
|
private promptService: PromptService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar, route, storage);
|
super(titleService, translate, matSnackBar, motionRepo, route, storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -81,28 +78,29 @@ export class MotionBlockDetailComponent extends ListViewBaseComponent<ViewMotion
|
|||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
super.setTitle('Motion block');
|
super.setTitle('Motion block');
|
||||||
this.initTable();
|
this.initTable();
|
||||||
|
const blockId = parseInt(this.route.snapshot.params.id, 10);
|
||||||
|
|
||||||
this.blockEditForm = new FormGroup({
|
this.blockEditForm = new FormGroup({
|
||||||
title: new FormControl('', Validators.required)
|
title: new FormControl('', Validators.required)
|
||||||
});
|
});
|
||||||
|
|
||||||
const blockId = +this.route.snapshot.params.id;
|
// pseudo filter
|
||||||
this.block = this.repo.getViewModel(blockId);
|
this.subscriptions.push(
|
||||||
|
this.repo.getViewModelObservable(blockId).subscribe(newBlock => {
|
||||||
this.repo.getViewModelObservable(blockId).subscribe(newBlock => {
|
if (newBlock) {
|
||||||
// necessary since the subscription can return undefined
|
this.block = newBlock;
|
||||||
if (newBlock) {
|
this.subscriptions.push(
|
||||||
this.block = newBlock;
|
this.repo.getViewMotionsByBlock(this.block.motionBlock).subscribe(viewMotions => {
|
||||||
|
if (viewMotions && viewMotions.length) {
|
||||||
// set the blocks title in the form
|
this.dataSource.data = viewMotions;
|
||||||
this.blockEditForm.get('title').setValue(this.block.title);
|
} else {
|
||||||
|
this.dataSource.data = [];
|
||||||
this.repo.getViewMotionsByBlock(this.block.motionBlock).subscribe(newMotions => {
|
}
|
||||||
this.motions = newMotions;
|
})
|
||||||
this.dataSource.data = this.motions;
|
);
|
||||||
});
|
}
|
||||||
}
|
})
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -193,8 +191,8 @@ export class MotionBlockDetailComponent extends ListViewBaseComponent<ViewMotion
|
|||||||
* Following a recommendation implies, that a valid recommendation exists.
|
* Following a recommendation implies, that a valid recommendation exists.
|
||||||
*/
|
*/
|
||||||
public isFollowingProhibited(): boolean {
|
public isFollowingProhibited(): boolean {
|
||||||
if (this.motions) {
|
if (this.dataSource.data) {
|
||||||
return this.motions.every(motion => motion.isInFinalState() || !motion.recommendation_id);
|
return this.dataSource.data.every(motion => motion.isInFinalState() || !motion.recommendation_id);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -51,10 +51,10 @@
|
|||||||
|
|
||||||
<!-- Table -->
|
<!-- Table -->
|
||||||
<mat-card class="os-card">
|
<mat-card class="os-card">
|
||||||
<table class="os-headed-listview-table on-transition-fade" mat-table [dataSource]="dataSource" matSort>
|
<table class="os-headed-listview-table on-transition-fade" mat-table [dataSource]="dataSource">
|
||||||
<!-- Projector column -->
|
<!-- Projector column -->
|
||||||
<ng-container matColumnDef="projector">
|
<ng-container matColumnDef="projector">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Projector</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef></mat-header-cell>
|
||||||
<mat-cell *matCellDef="let block">
|
<mat-cell *matCellDef="let block">
|
||||||
<os-projector-button [object]="block"></os-projector-button>
|
<os-projector-button [object]="block"></os-projector-button>
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
@ -62,13 +62,17 @@
|
|||||||
|
|
||||||
<!-- title column -->
|
<!-- title column -->
|
||||||
<ng-container matColumnDef="title">
|
<ng-container matColumnDef="title">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header> <span translate>Title</span> </mat-header-cell>
|
<mat-header-cell *matHeaderCellDef>
|
||||||
|
<span translate>Title</span>
|
||||||
|
</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let block"> {{ block.title }} </mat-cell>
|
<mat-cell *matCellDef="let block"> {{ block.title }} </mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- amount column -->
|
<!-- amount column -->
|
||||||
<ng-container matColumnDef="amount">
|
<ng-container matColumnDef="amount">
|
||||||
<mat-header-cell *matHeaderCellDef> <span translate>Motions</span> </mat-header-cell>
|
<mat-header-cell *matHeaderCellDef>
|
||||||
|
<span translate>Motions</span>
|
||||||
|
</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let block">
|
<mat-cell *matCellDef="let block">
|
||||||
<span class="os-amount-chip">{{ getMotionAmount(block.motionBlock) }}</span>
|
<span class="os-amount-chip">{{ getMotionAmount(block.motionBlock) }}</span>
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
@ -76,7 +80,9 @@
|
|||||||
|
|
||||||
<!-- menu -->
|
<!-- menu -->
|
||||||
<ng-container matColumnDef="menu">
|
<ng-container matColumnDef="menu">
|
||||||
<mat-header-cell *matHeaderCellDef>Menu</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef>
|
||||||
|
<span translate>Menu</span>
|
||||||
|
</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let block">
|
<mat-cell *matCellDef="let block">
|
||||||
<button
|
<button
|
||||||
*ngIf="canEdit"
|
*ngIf="canEdit"
|
||||||
|
@ -12,6 +12,7 @@ import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
|
|||||||
import { ListViewBaseComponent } from 'app/site/base/list-view-base';
|
import { ListViewBaseComponent } from 'app/site/base/list-view-base';
|
||||||
import { MotionBlock } from 'app/shared/models/motions/motion-block';
|
import { MotionBlock } from 'app/shared/models/motions/motion-block';
|
||||||
import { MotionBlockRepositoryService } from 'app/core/repositories/motions/motion-block-repository.service';
|
import { MotionBlockRepositoryService } from 'app/core/repositories/motions/motion-block-repository.service';
|
||||||
|
import { MotionBlockSortService } from 'app/site/motions/services/motion-block-sort.service';
|
||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { StorageService } from 'app/core/core-services/storage.service';
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
@ -26,7 +27,9 @@ import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
|
|||||||
templateUrl: './motion-block-list.component.html',
|
templateUrl: './motion-block-list.component.html',
|
||||||
styleUrls: ['./motion-block-list.component.scss']
|
styleUrls: ['./motion-block-list.component.scss']
|
||||||
})
|
})
|
||||||
export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBlock, MotionBlock> implements OnInit {
|
export class MotionBlockListComponent
|
||||||
|
extends ListViewBaseComponent<ViewMotionBlock, MotionBlock, MotionBlockRepositoryService>
|
||||||
|
implements OnInit {
|
||||||
/**
|
/**
|
||||||
* Holds the create form
|
* Holds the create form
|
||||||
*/
|
*/
|
||||||
@ -88,9 +91,10 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
|
|||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private promptService: PromptService,
|
private promptService: PromptService,
|
||||||
private itemRepo: ItemRepositoryService,
|
private itemRepo: ItemRepositoryService,
|
||||||
private operator: OperatorService
|
private operator: OperatorService,
|
||||||
|
sortService: MotionBlockSortService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar, route, storage);
|
super(titleService, translate, matSnackBar, repo, route, storage, null, sortService);
|
||||||
|
|
||||||
this.createBlockForm = this.formBuilder.group({
|
this.createBlockForm = this.formBuilder.group({
|
||||||
title: ['', Validators.required],
|
title: ['', Validators.required],
|
||||||
@ -105,14 +109,7 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
|
|||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
super.setTitle('Motion blocks');
|
super.setTitle('Motion blocks');
|
||||||
this.initTable();
|
this.initTable();
|
||||||
|
|
||||||
this.items = this.itemRepo.getViewModelListBehaviorSubject();
|
this.items = this.itemRepo.getViewModelListBehaviorSubject();
|
||||||
|
|
||||||
this.repo.getViewModelListObservable().subscribe(newMotionblocks => {
|
|
||||||
newMotionblocks.sort((a, b) => (a > b ? 1 : -1));
|
|
||||||
this.dataSource.data = newMotionblocks;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.agendaRepo.getDefaultAgendaVisibility().subscribe(visibility => (this.defaultVisibility = visibility));
|
this.agendaRepo.getDefaultAgendaVisibility().subscribe(visibility => (this.defaultVisibility = visibility));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +66,8 @@ interface InfoDialog {
|
|||||||
templateUrl: './motion-list.component.html',
|
templateUrl: './motion-list.component.html',
|
||||||
styleUrls: ['./motion-list.component.scss']
|
styleUrls: ['./motion-list.component.scss']
|
||||||
})
|
})
|
||||||
export class MotionListComponent extends ListViewBaseComponent<ViewMotion, Motion> implements OnInit {
|
export class MotionListComponent extends ListViewBaseComponent<ViewMotion, Motion, MotionRepositoryService>
|
||||||
|
implements OnInit {
|
||||||
/**
|
/**
|
||||||
* Reference to the dialog for quick editing meta information.
|
* Reference to the dialog for quick editing meta information.
|
||||||
*/
|
*/
|
||||||
@ -130,15 +131,15 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion, Motio
|
|||||||
matSnackBar: MatSnackBar,
|
matSnackBar: MatSnackBar,
|
||||||
route: ActivatedRoute,
|
route: ActivatedRoute,
|
||||||
storage: StorageService,
|
storage: StorageService,
|
||||||
filterService: MotionFilterListService,
|
public filterService: MotionFilterListService,
|
||||||
sortService: MotionSortListService,
|
public sortService: MotionSortListService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
private tagRepo: TagRepositoryService,
|
private tagRepo: TagRepositoryService,
|
||||||
private motionBlockRepo: MotionBlockRepositoryService,
|
private motionBlockRepo: MotionBlockRepositoryService,
|
||||||
private categoryRepo: CategoryRepositoryService,
|
private categoryRepo: CategoryRepositoryService,
|
||||||
private workflowRepo: WorkflowRepositoryService,
|
private workflowRepo: WorkflowRepositoryService,
|
||||||
private motionRepo: MotionRepositoryService,
|
protected motionRepo: MotionRepositoryService,
|
||||||
private motionCsvExport: MotionCsvExportService,
|
private motionCsvExport: MotionCsvExportService,
|
||||||
private operator: OperatorService,
|
private operator: OperatorService,
|
||||||
private pdfExport: MotionPdfExportService,
|
private pdfExport: MotionPdfExportService,
|
||||||
@ -148,7 +149,7 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion, Motio
|
|||||||
public perms: LocalPermissionsService,
|
public perms: LocalPermissionsService,
|
||||||
private motionXlsxExport: MotionXlsxExportService
|
private motionXlsxExport: MotionXlsxExportService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar, route, storage, filterService, sortService);
|
super(titleService, translate, matSnackBar, motionRepo, route, storage, filterService, sortService);
|
||||||
|
|
||||||
// enable multiSelect for this listView
|
// enable multiSelect for this listView
|
||||||
this.canMultiSelect = true;
|
this.canMultiSelect = true;
|
||||||
@ -357,23 +358,6 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion, Motio
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
filter = filter ? filter.toLowerCase() : '';
|
filter = filter ? filter.toLowerCase() : '';
|
||||||
if (
|
|
||||||
data.recommendation &&
|
|
||||||
this.translate
|
|
||||||
.instant(data.recommendation.recommendation_label)
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(filter)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
this.translate
|
|
||||||
.instant(data.state.name)
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(filter)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (data.submitters.length && data.submitters.find(user => user.full_name.toLowerCase().includes(filter))) {
|
if (data.submitters.length && data.submitters.find(user => user.full_name.toLowerCase().includes(filter))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -387,6 +371,24 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion, Motio
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.getStateLabel(data) &&
|
||||||
|
this.getStateLabel(data)
|
||||||
|
.toLocaleLowerCase()
|
||||||
|
.includes(filter)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.getRecommendationLabel(data) &&
|
||||||
|
this.getRecommendationLabel(data)
|
||||||
|
.toLocaleLowerCase()
|
||||||
|
.includes(filter)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const dataid = '' + data.id;
|
const dataid = '' + data.id;
|
||||||
if (dataid.includes(filter)) {
|
if (dataid.includes(filter)) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -20,7 +20,8 @@ import { StorageService } from 'app/core/core-services/storage.service';
|
|||||||
templateUrl: './workflow-list.component.html',
|
templateUrl: './workflow-list.component.html',
|
||||||
styleUrls: ['./workflow-list.component.scss']
|
styleUrls: ['./workflow-list.component.scss']
|
||||||
})
|
})
|
||||||
export class WorkflowListComponent extends ListViewBaseComponent<ViewWorkflow, Workflow> implements OnInit {
|
export class WorkflowListComponent extends ListViewBaseComponent<ViewWorkflow, Workflow, WorkflowRepositoryService>
|
||||||
|
implements OnInit {
|
||||||
/**
|
/**
|
||||||
* Holds the new workflow title
|
* Holds the new workflow title
|
||||||
*/
|
*/
|
||||||
@ -51,10 +52,10 @@ export class WorkflowListComponent extends ListViewBaseComponent<ViewWorkflow, W
|
|||||||
storage: StorageService,
|
storage: StorageService,
|
||||||
private dialog: MatDialog,
|
private dialog: MatDialog,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private workflowRepo: WorkflowRepositoryService,
|
protected workflowRepo: WorkflowRepositoryService,
|
||||||
private promptService: PromptService
|
private promptService: PromptService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar, route, storage);
|
super(titleService, translate, matSnackBar, workflowRepo, route, storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MotionBlockSortService } from './motion-block-sort.service';
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
|
||||||
|
describe('MotionBlockSortService', () => {
|
||||||
|
beforeEach(() =>
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
const service: MotionBlockSortService = TestBed.get(MotionBlockSortService);
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,24 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { BaseSortListService, OsSortingDefinition, OsSortingOption } from 'app/core/ui-services/base-sort-list.service';
|
||||||
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { ViewMotionBlock } from '../models/view-motion-block';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class MotionBlockSortService extends BaseSortListService<ViewMotionBlock> {
|
||||||
|
public sortOptions: OsSortingOption<ViewMotionBlock>[] = [{ property: 'title' }];
|
||||||
|
|
||||||
|
public constructor(translate: TranslateService, store: StorageService) {
|
||||||
|
super('Motion block', translate, store);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async getDefaultDefinition(): Promise<OsSortingDefinition<ViewMotionBlock>> {
|
||||||
|
return {
|
||||||
|
sortProperty: 'title',
|
||||||
|
sortAscending: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -2,69 +2,52 @@ import { Injectable } from '@angular/core';
|
|||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { BaseFilterListService, OsFilter, OsFilterOptions } from 'app/core/ui-services/base-filter-list.service';
|
import {
|
||||||
|
BaseFilterListService,
|
||||||
|
OsFilter,
|
||||||
|
OsFilterOptions,
|
||||||
|
OsFilterOption
|
||||||
|
} from 'app/core/ui-services/base-filter-list.service';
|
||||||
import { ViewMotion } from '../models/view-motion';
|
import { ViewMotion } from '../models/view-motion';
|
||||||
import { CategoryRepositoryService } from 'app/core/repositories/motions/category-repository.service';
|
import { CategoryRepositoryService } from 'app/core/repositories/motions/category-repository.service';
|
||||||
import { WorkflowRepositoryService } from 'app/core/repositories/motions/workflow-repository.service';
|
import { WorkflowRepositoryService } from 'app/core/repositories/motions/workflow-repository.service';
|
||||||
import { StorageService } from 'app/core/core-services/storage.service';
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
|
||||||
import { MotionBlockRepositoryService } from 'app/core/repositories/motions/motion-block-repository.service';
|
import { MotionBlockRepositoryService } from 'app/core/repositories/motions/motion-block-repository.service';
|
||||||
import { MotionCommentSectionRepositoryService } from 'app/core/repositories/motions/motion-comment-section-repository.service';
|
import { MotionCommentSectionRepositoryService } from 'app/core/repositories/motions/motion-comment-section-repository.service';
|
||||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
|
||||||
import { ViewWorkflow } from '../models/view-workflow';
|
|
||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service';
|
import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service';
|
||||||
|
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter description to easier parse dynamically occurring workflows
|
||||||
|
*/
|
||||||
|
interface WorkflowFilterDesc {
|
||||||
|
name: string;
|
||||||
|
filter: OsFilterOption[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter the motion list
|
||||||
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class MotionFilterListService extends BaseFilterListService<ViewMotion> {
|
export class MotionFilterListService extends BaseFilterListService<ViewMotion> {
|
||||||
protected name = 'Motion';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* getter for the filterOptions. Note that in this case, the options are
|
|
||||||
* generated dynamically, as the options change with the datastore
|
|
||||||
*/
|
|
||||||
public get filterOptions(): OsFilter[] {
|
|
||||||
let filterOptions = [
|
|
||||||
this.flowFilterOptions,
|
|
||||||
this.categoryFilterOptions,
|
|
||||||
this.motionBlockFilterOptions,
|
|
||||||
this.recommendationFilterOptions,
|
|
||||||
this.motionCommentFilterOptions,
|
|
||||||
this.tagFilterOptions
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!this.operator.isAnonymous) {
|
|
||||||
filterOptions = filterOptions.concat(this.personalNoteFilterOptions);
|
|
||||||
}
|
|
||||||
return filterOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter definitions for the workflow filter. Options will be generated by
|
|
||||||
* getFilterOptions (as the workflows available may change)
|
|
||||||
*/
|
|
||||||
public flowFilterOptions: OsFilter = {
|
|
||||||
property: 'state',
|
|
||||||
label: 'State',
|
|
||||||
options: []
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listen to the configuration for change in defined/used workflows
|
* Listen to the configuration for change in defined/used workflows
|
||||||
*/
|
*/
|
||||||
private enabledWorkflows = { statuteEnabled: false, statute: null, motion: null };
|
private enabledWorkflows = { statuteEnabled: false, statute: null, motion: null };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* storage for currently used workflows
|
* Filter definitions for the workflow filter. Options will be generated by
|
||||||
|
* getFilterOptions (as the workflows available may change)
|
||||||
*/
|
*/
|
||||||
private currentWorkflows: ViewWorkflow[];
|
public stateFilterOptions: OsFilter = {
|
||||||
|
property: 'state',
|
||||||
|
label: 'State',
|
||||||
|
options: []
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter definitions for the category filter. Options will be generated by
|
|
||||||
* getFilterOptions (as the categories available may change)
|
|
||||||
*/
|
|
||||||
public categoryFilterOptions: OsFilter = {
|
public categoryFilterOptions: OsFilter = {
|
||||||
property: 'category',
|
property: 'category',
|
||||||
options: []
|
options: []
|
||||||
@ -98,7 +81,6 @@ export class MotionFilterListService extends BaseFilterListService<ViewMotion> {
|
|||||||
{
|
{
|
||||||
property: 'star',
|
property: 'star',
|
||||||
label: this.translate.instant('Favorites'),
|
label: this.translate.instant('Favorites'),
|
||||||
isActive: false,
|
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
condition: true,
|
condition: true,
|
||||||
@ -113,7 +95,6 @@ export class MotionFilterListService extends BaseFilterListService<ViewMotion> {
|
|||||||
{
|
{
|
||||||
property: 'hasNotes',
|
property: 'hasNotes',
|
||||||
label: this.translate.instant('Personal notes'),
|
label: this.translate.instant('Personal notes'),
|
||||||
isActive: false,
|
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
condition: true,
|
condition: true,
|
||||||
@ -132,216 +113,175 @@ export class MotionFilterListService extends BaseFilterListService<ViewMotion> {
|
|||||||
* the available filters
|
* the available filters
|
||||||
*
|
*
|
||||||
* @param store The browser's storage; required for fetching filters from any previous sessions
|
* @param store The browser's storage; required for fetching filters from any previous sessions
|
||||||
* @param workflowRepo Subscribing to filters by states/Recommendation
|
* @param categoryRepo to filter by Categories
|
||||||
* @param categoryRepo Subscribing to filters by Categories
|
* @param motionBlockRepo to filter by MotionBlock
|
||||||
* @param motionBlockRepo Subscribing to filters by MotionBlock
|
* @param commentRepo to filter by motion comments
|
||||||
* @param commentRepo subycribing filter by presense of comment
|
* @param tagRepo to filter by tags
|
||||||
|
* @param workflowRepo Subscribing to filters by states and recommendation
|
||||||
* @param translate Translation service
|
* @param translate Translation service
|
||||||
* @param config the current configuration (to determine which workflow filters to offer )
|
* @param operator
|
||||||
* @param motionRepo the motion's own repository, required by the parent
|
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
store: StorageService,
|
store: StorageService,
|
||||||
|
categoryRepo: CategoryRepositoryService,
|
||||||
|
motionBlockRepo: MotionBlockRepositoryService,
|
||||||
|
commentRepo: MotionCommentSectionRepositoryService,
|
||||||
|
tagRepo: TagRepositoryService,
|
||||||
private workflowRepo: WorkflowRepositoryService,
|
private workflowRepo: WorkflowRepositoryService,
|
||||||
private categoryRepo: CategoryRepositoryService,
|
|
||||||
private motionBlockRepo: MotionBlockRepositoryService,
|
|
||||||
private commentRepo: MotionCommentSectionRepositoryService,
|
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private config: ConfigService,
|
|
||||||
motionRepo: MotionRepositoryService,
|
|
||||||
private operator: OperatorService,
|
private operator: OperatorService,
|
||||||
private tagRepo: TagRepositoryService
|
private config: ConfigService
|
||||||
) {
|
) {
|
||||||
super(store, motionRepo);
|
super('Motion', store);
|
||||||
|
this.getWorkflowConfig();
|
||||||
|
|
||||||
|
this.updateFilterForRepo(categoryRepo, this.categoryFilterOptions, this.translate.instant('No category set'));
|
||||||
|
|
||||||
|
this.updateFilterForRepo(
|
||||||
|
motionBlockRepo,
|
||||||
|
this.motionBlockFilterOptions,
|
||||||
|
this.translate.instant('No motion block set')
|
||||||
|
);
|
||||||
|
|
||||||
|
this.updateFilterForRepo(commentRepo, this.motionCommentFilterOptions, this.translate.instant('No comment'));
|
||||||
|
|
||||||
|
this.updateFilterForRepo(tagRepo, this.tagFilterOptions, this.translate.instant('No tags'));
|
||||||
|
|
||||||
this.subscribeWorkflows();
|
this.subscribeWorkflows();
|
||||||
this.subscribeCategories();
|
|
||||||
this.subscribeMotionBlocks();
|
|
||||||
this.subscribeComments();
|
|
||||||
this.subscribeTags();
|
|
||||||
|
|
||||||
this.operator.getUserObservable().subscribe(() => {
|
this.operator.getUserObservable().subscribe(() => {
|
||||||
this.updateFilterDefinitions(this.filterOptions);
|
this.setFilterDefinitions();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getWorkflowConfig(): void {
|
||||||
|
this.config.get<string>('motions_statute_amendments_workflow').subscribe(id => {
|
||||||
|
this.enabledWorkflows.statute = +id;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.config.get<string>('motions_workflow').subscribe(id => {
|
||||||
|
this.enabledWorkflows.motion = +id;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.config.get<boolean>('motions_statutes_enabled').subscribe(bool => {
|
||||||
|
this.enabledWorkflows.statuteEnabled = bool;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscibes to changing MotionBlocks, and updates the filter accordingly
|
* @returns the filter definition
|
||||||
*/
|
*/
|
||||||
private subscribeMotionBlocks(): void {
|
protected getFilterDefinitions(): OsFilter[] {
|
||||||
this.motionBlockRepo.getViewModelListObservable().subscribe(motionBlocks => {
|
let filterDefinitions = [
|
||||||
const motionBlockOptions: OsFilterOptions = motionBlocks.map(mb => ({
|
this.stateFilterOptions,
|
||||||
condition: mb.id,
|
this.categoryFilterOptions,
|
||||||
label: mb.title,
|
this.motionBlockFilterOptions,
|
||||||
isActive: false
|
this.recommendationFilterOptions,
|
||||||
}));
|
this.motionCommentFilterOptions,
|
||||||
if (motionBlocks.length) {
|
this.tagFilterOptions
|
||||||
motionBlockOptions.push('-');
|
];
|
||||||
motionBlockOptions.push({
|
|
||||||
condition: null,
|
if (!this.operator.isAnonymous) {
|
||||||
label: this.translate.instant('No motion block set'),
|
filterDefinitions = filterDefinitions.concat(this.personalNoteFilterOptions);
|
||||||
isActive: false
|
}
|
||||||
});
|
return filterDefinitions;
|
||||||
}
|
|
||||||
this.motionBlockFilterOptions.options = motionBlockOptions;
|
|
||||||
this.updateFilterDefinitions(this.filterOptions);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscibes to changing Categories, and updates the filter accordingly
|
* Subscribes to changing Workflows, and updates the state and recommendation filters accordingly.
|
||||||
*/
|
|
||||||
private subscribeCategories(): void {
|
|
||||||
this.categoryRepo.getViewModelListObservable().subscribe(categories => {
|
|
||||||
const categoryOptions: OsFilterOptions = categories.map(cat => ({
|
|
||||||
condition: cat.id,
|
|
||||||
label: cat.prefixedName,
|
|
||||||
isActive: false
|
|
||||||
}));
|
|
||||||
if (categories.length) {
|
|
||||||
categoryOptions.push('-');
|
|
||||||
categoryOptions.push({
|
|
||||||
label: this.translate.instant('No category set'),
|
|
||||||
condition: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.categoryFilterOptions.options = categoryOptions;
|
|
||||||
this.updateFilterDefinitions(this.filterOptions);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscibes to changing Workflows, and updates the state and recommendation filters accordingly
|
|
||||||
* Only subscribes to workflows that are enabled in the config as motion or statute paragraph workflow
|
|
||||||
*/
|
*/
|
||||||
private subscribeWorkflows(): void {
|
private subscribeWorkflows(): void {
|
||||||
this.workflowRepo.getViewModelListObservable().subscribe(workflows => {
|
this.workflowRepo.getViewModelListObservable().subscribe(workflows => {
|
||||||
this.currentWorkflows = workflows;
|
if (workflows && workflows.length) {
|
||||||
this.updateWorkflows();
|
const workflowFilters: WorkflowFilterDesc[] = [];
|
||||||
});
|
const recoFilters: WorkflowFilterDesc[] = [];
|
||||||
this.config.get<string>('motions_statute_amendments_workflow').subscribe(id => {
|
|
||||||
this.enabledWorkflows.statute = +id;
|
|
||||||
this.updateWorkflows();
|
|
||||||
});
|
|
||||||
this.config.get<string>('motions_workflow').subscribe(id => {
|
|
||||||
this.enabledWorkflows.motion = +id;
|
|
||||||
this.updateWorkflows();
|
|
||||||
});
|
|
||||||
this.config.get<boolean>('motions_statutes_enabled').subscribe(bool => {
|
|
||||||
this.enabledWorkflows.statuteEnabled = bool;
|
|
||||||
this.updateWorkflows();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const finalStates: number[] = [];
|
||||||
* Helper to show only filter for workflows that are included in to currently
|
const nonFinalStates: number[] = [];
|
||||||
* set config options
|
|
||||||
*/
|
// get all relevant information
|
||||||
private updateWorkflows(): void {
|
for (const workflow of workflows) {
|
||||||
const workflowOptions: OsFilterOptions = [];
|
if (this.isWorkflowEnabled(workflow.id)) {
|
||||||
const finalStates: number[] = [];
|
workflowFilters.push({
|
||||||
const nonFinalStates: number[] = [];
|
name: workflow.name,
|
||||||
const recommendationOptions: OsFilterOptions = [];
|
filter: []
|
||||||
if (!this.currentWorkflows) {
|
});
|
||||||
return;
|
|
||||||
}
|
recoFilters.push({
|
||||||
this.currentWorkflows.forEach(workflow => {
|
name: workflow.name,
|
||||||
if (
|
filter: []
|
||||||
workflow.id === this.enabledWorkflows.motion ||
|
});
|
||||||
(this.enabledWorkflows.statuteEnabled && workflow.id === this.enabledWorkflows.statute)
|
|
||||||
) {
|
for (const state of workflow.states) {
|
||||||
workflowOptions.push(workflow.name);
|
if (
|
||||||
recommendationOptions.push(workflow.name);
|
this.operator.hasPerms('motions.can_manage', 'motions.can_manage_metadata') &&
|
||||||
workflow.states.forEach(state => {
|
state.restriction
|
||||||
// filter out restricted states for unpriviledged users
|
) {
|
||||||
if (
|
// sort final and non final states
|
||||||
this.operator.hasPerms('motions.can_manage', 'motions.can_manage_metadata') ||
|
state.isFinalState ? finalStates.push(state.id) : nonFinalStates.push(state.id);
|
||||||
state.restriction.length === 0
|
|
||||||
) {
|
workflowFilters[workflowFilters.length - 1].filter.push({
|
||||||
if (state.isFinalState) {
|
condition: state.id,
|
||||||
finalStates.push(state.id);
|
label: state.name
|
||||||
} else {
|
});
|
||||||
nonFinalStates.push(state.id);
|
|
||||||
|
if (state.recommendation_label) {
|
||||||
|
recoFilters[workflowFilters.length - 1].filter.push({
|
||||||
|
condition: state.id,
|
||||||
|
label: state.recommendation_label
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
workflowOptions.push({
|
|
||||||
condition: state.id,
|
|
||||||
label: state.name,
|
|
||||||
isActive: false
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (state.recommendation_label) {
|
}
|
||||||
recommendationOptions.push({
|
|
||||||
condition: state.id,
|
|
||||||
label: state.recommendation_label,
|
|
||||||
isActive: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (workflowOptions.length) {
|
|
||||||
workflowOptions.push('-');
|
|
||||||
workflowOptions.push({
|
|
||||||
label: 'Done',
|
|
||||||
condition: finalStates
|
|
||||||
});
|
|
||||||
workflowOptions.push({
|
|
||||||
label: this.translate.instant('Undone'),
|
|
||||||
condition: nonFinalStates
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (recommendationOptions.length) {
|
|
||||||
recommendationOptions.push('-');
|
|
||||||
recommendationOptions.push({
|
|
||||||
label: this.translate.instant('No recommendation'),
|
|
||||||
condition: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.flowFilterOptions.options = workflowOptions;
|
|
||||||
this.recommendationFilterOptions.options = recommendationOptions;
|
|
||||||
this.updateFilterDefinitions(this.filterOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// convert to filter options
|
||||||
* Subscibes to changing Comments, and updates the filter accordingly
|
if (workflowFilters && workflowFilters.length) {
|
||||||
*/
|
let workflowOptions: OsFilterOptions = [];
|
||||||
private subscribeComments(): void {
|
for (const filterDef of workflowFilters) {
|
||||||
this.commentRepo.getViewModelListObservable().subscribe(comments => {
|
workflowOptions.push(filterDef.name);
|
||||||
const commentOptions: OsFilterOptions = comments.map(comment => ({
|
workflowOptions = workflowOptions.concat(filterDef.filter);
|
||||||
condition: comment.id,
|
}
|
||||||
label: comment.name,
|
|
||||||
isActive: false
|
// add "done" and "undone"
|
||||||
}));
|
workflowOptions.push('-');
|
||||||
if (comments.length) {
|
workflowOptions.push({
|
||||||
commentOptions.push('-');
|
label: 'Done',
|
||||||
commentOptions.push({
|
condition: finalStates
|
||||||
label: this.translate.instant('No comment'),
|
});
|
||||||
condition: null
|
|
||||||
});
|
workflowOptions.push({
|
||||||
|
label: this.translate.instant('Undone'),
|
||||||
|
condition: nonFinalStates
|
||||||
|
});
|
||||||
|
|
||||||
|
this.stateFilterOptions.options = workflowOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recoFilters && recoFilters.length) {
|
||||||
|
let recoOptions: OsFilterOptions = [];
|
||||||
|
|
||||||
|
for (const filterDef of recoFilters) {
|
||||||
|
recoOptions.push(filterDef.name);
|
||||||
|
recoOptions = recoOptions.concat(filterDef.filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
recoOptions.push('-');
|
||||||
|
recoOptions.push({
|
||||||
|
label: this.translate.instant('No recommendation'),
|
||||||
|
condition: null
|
||||||
|
});
|
||||||
|
this.recommendationFilterOptions.options = recoOptions;
|
||||||
|
}
|
||||||
|
this.setFilterDefinitions();
|
||||||
}
|
}
|
||||||
this.motionCommentFilterOptions.options = commentOptions;
|
|
||||||
this.updateFilterDefinitions(this.filterOptions);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private isWorkflowEnabled(workflowId: number): boolean {
|
||||||
* Subscibes to changing Tags, and updates the filter accordingly
|
return (
|
||||||
*/
|
workflowId === this.enabledWorkflows.motion ||
|
||||||
private subscribeTags(): void {
|
(this.enabledWorkflows.statuteEnabled && workflowId === this.enabledWorkflows.statute)
|
||||||
this.tagRepo.getViewModelListObservable().subscribe(tags => {
|
);
|
||||||
const tagOptions: OsFilterOptions = tags.map(tag => ({
|
|
||||||
condition: tag.id,
|
|
||||||
label: tag.name,
|
|
||||||
isActive: false
|
|
||||||
}));
|
|
||||||
if (tags.length) {
|
|
||||||
tagOptions.push('-');
|
|
||||||
tagOptions.push({
|
|
||||||
label: this.translate.instant('No tags'),
|
|
||||||
condition: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.tagFilterOptions.options = tagOptions;
|
|
||||||
this.updateFilterDefinitions(this.filterOptions);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,74 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { BaseSortListService, OsSortingDefinition } from 'app/core/ui-services/base-sort-list.service';
|
|
||||||
import { ViewMotion } from '../models/view-motion';
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { StorageService } from 'app/core/core-services/storage.service';
|
|
||||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
|
||||||
import { _ } from 'app/core/translate/translation-marker';
|
|
||||||
|
|
||||||
|
import { _ } from 'app/core/translate/translation-marker';
|
||||||
|
import { BaseSortListService, OsSortingDefinition, OsSortingOption } from 'app/core/ui-services/base-sort-list.service';
|
||||||
|
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||||
|
import { Deferred } from 'app/core/deferred';
|
||||||
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
|
import { ViewMotion } from '../models/view-motion';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorting service for the motion list
|
||||||
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class MotionSortListService extends BaseSortListService<ViewMotion> {
|
export class MotionSortListService extends BaseSortListService<ViewMotion> {
|
||||||
public sortOptions: OsSortingDefinition<ViewMotion> = {
|
/**
|
||||||
sortProperty: 'weight',
|
* Hold the default motion sorting
|
||||||
sortAscending: true,
|
*/
|
||||||
options: [
|
private defaultMotionSorting: string;
|
||||||
{ property: 'weight', label: 'Call list' },
|
|
||||||
{ property: 'identifier' },
|
|
||||||
{ property: 'title' },
|
|
||||||
{ property: 'submitters' },
|
|
||||||
{ property: 'category' },
|
|
||||||
{ property: 'motion_block_id', label: 'Motion block' },
|
|
||||||
{ property: 'state' },
|
|
||||||
{ property: 'creationDate', label: _('Creation date') },
|
|
||||||
{ property: 'lastChangeDate', label: _('Last modified') }
|
|
||||||
]
|
|
||||||
};
|
|
||||||
protected name = 'Motion';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor. Sets the default sorting if none is set locally
|
* To wait until the default motion was loaded once
|
||||||
*
|
|
||||||
* @param translate
|
|
||||||
* @param store
|
|
||||||
* @param config
|
|
||||||
*/
|
*/
|
||||||
public constructor(translate: TranslateService, store: StorageService, config: ConfigService) {
|
private readonly defaultSortingLoaded: Deferred<void> = new Deferred();
|
||||||
super(translate, store);
|
|
||||||
this.defaultSorting = config.instant<keyof ViewMotion>('motions_motions_sorting');
|
/**
|
||||||
|
* Define the sort options
|
||||||
|
*/
|
||||||
|
public sortOptions: OsSortingOption<ViewMotion>[] = [
|
||||||
|
{ property: 'weight', label: 'Call list' },
|
||||||
|
{ property: 'identifier' },
|
||||||
|
{ property: 'title' },
|
||||||
|
{ property: 'submitters' },
|
||||||
|
{ property: 'category' },
|
||||||
|
{ property: 'motion_block_id', label: 'Motion block' },
|
||||||
|
{ property: 'state' },
|
||||||
|
{ property: 'creationDate', label: _('Creation date') },
|
||||||
|
{ property: 'lastChangeDate', label: _('Last modified') }
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param translate required by parent
|
||||||
|
* @param store required by parent
|
||||||
|
* @param config set the default sorting according to OpenSlides configuration
|
||||||
|
*/
|
||||||
|
public constructor(translate: TranslateService, store: StorageService, private config: ConfigService) {
|
||||||
|
super('Motion', translate, store);
|
||||||
|
|
||||||
|
this.config.get<string>('motions_motions_sorting').subscribe(defSortProp => {
|
||||||
|
if (defSortProp) {
|
||||||
|
this.defaultMotionSorting = defSortProp;
|
||||||
|
this.defaultSortingLoaded.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required by parent
|
||||||
|
*
|
||||||
|
* @returns the default sorting strategy
|
||||||
|
*/
|
||||||
|
protected async getDefaultDefinition(): Promise<OsSortingDefinition<ViewMotion>> {
|
||||||
|
await this.defaultSortingLoaded;
|
||||||
|
return {
|
||||||
|
sortProperty: this.defaultMotionSorting as keyof ViewMotion,
|
||||||
|
sortAscending: true
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ import { ViewTag } from '../../models/view-tag';
|
|||||||
templateUrl: './tag-list.component.html',
|
templateUrl: './tag-list.component.html',
|
||||||
styleUrls: ['./tag-list.component.css']
|
styleUrls: ['./tag-list.component.css']
|
||||||
})
|
})
|
||||||
export class TagListComponent extends ListViewBaseComponent<ViewTag, Tag> implements OnInit {
|
export class TagListComponent extends ListViewBaseComponent<ViewTag, Tag, TagRepositoryService> implements OnInit {
|
||||||
public editTag = false;
|
public editTag = false;
|
||||||
public newTag = false;
|
public newTag = false;
|
||||||
public selectedTag: ViewTag;
|
public selectedTag: ViewTag;
|
||||||
@ -50,7 +50,7 @@ export class TagListComponent extends ListViewBaseComponent<ViewTag, Tag> implem
|
|||||||
private repo: TagRepositoryService,
|
private repo: TagRepositoryService,
|
||||||
private promptService: PromptService
|
private promptService: PromptService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar, route, storage);
|
super(titleService, translate, matSnackBar, repo, route, storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,14 +55,13 @@ interface InfoDialog {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Component for the user list view.
|
* Component for the user list view.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'os-user-list',
|
selector: 'os-user-list',
|
||||||
templateUrl: './user-list.component.html',
|
templateUrl: './user-list.component.html',
|
||||||
styleUrls: ['./user-list.component.scss']
|
styleUrls: ['./user-list.component.scss']
|
||||||
})
|
})
|
||||||
export class UserListComponent extends ListViewBaseComponent<ViewUser, User> implements OnInit {
|
export class UserListComponent extends ListViewBaseComponent<ViewUser, User, UserRepositoryService> implements OnInit {
|
||||||
/**
|
/**
|
||||||
* The reference to the template.
|
* The reference to the template.
|
||||||
*/
|
*/
|
||||||
@ -154,7 +153,7 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser, User> imp
|
|||||||
private userPdf: UserPdfExportService,
|
private userPdf: UserPdfExportService,
|
||||||
private dialog: MatDialog
|
private dialog: MatDialog
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar, route, storage, filterService, sortService);
|
super(titleService, translate, matSnackBar, repo, route, storage, filterService, sortService);
|
||||||
|
|
||||||
// enable multiSelect for this listView
|
// enable multiSelect for this listView
|
||||||
this.canMultiSelect = true;
|
this.canMultiSelect = true;
|
||||||
|
@ -1,107 +1,75 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { BaseFilterListService, OsFilter } from 'app/core/ui-services/base-filter-list.service';
|
import { BaseFilterListService, OsFilter } from 'app/core/ui-services/base-filter-list.service';
|
||||||
import { StorageService } from 'app/core/core-services/storage.service';
|
|
||||||
import { ViewUser } from '../models/view-user';
|
|
||||||
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
|
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
|
||||||
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { ViewUser } from '../models/view-user';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter the user list
|
||||||
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class UserFilterListService extends BaseFilterListService<ViewUser> {
|
export class UserFilterListService extends BaseFilterListService<ViewUser> {
|
||||||
protected name = 'User';
|
private userGroupFilterOptions: OsFilter = {
|
||||||
|
|
||||||
private userGroupFilterOptions = {
|
|
||||||
isActive: false,
|
|
||||||
property: 'groups_id',
|
property: 'groups_id',
|
||||||
label: 'Groups',
|
label: 'Groups',
|
||||||
options: []
|
options: []
|
||||||
};
|
};
|
||||||
|
|
||||||
public staticFilterOptions = [
|
|
||||||
{
|
|
||||||
property: 'is_present',
|
|
||||||
label: 'Presence',
|
|
||||||
isActive: false,
|
|
||||||
options: [
|
|
||||||
{ condition: true, label: this.translate.instant('Is present') },
|
|
||||||
{ condition: false, label: this.translate.instant('Is not present') }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
property: 'is_active',
|
|
||||||
label: this.translate.instant('Active'),
|
|
||||||
isActive: false,
|
|
||||||
options: [
|
|
||||||
{ condition: true, label: 'Is active' },
|
|
||||||
{ condition: false, label: this.translate.instant('Is not active') }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
property: 'is_committee',
|
|
||||||
label: this.translate.instant('Committee'),
|
|
||||||
isActive: false,
|
|
||||||
options: [
|
|
||||||
{ condition: true, label: 'Is a committee' },
|
|
||||||
{ condition: false, label: this.translate.instant('Is not a committee') }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
property: 'is_last_email_send',
|
|
||||||
label: this.translate.instant('Last email send'),
|
|
||||||
isActive: false,
|
|
||||||
options: [
|
|
||||||
{ condition: true, label: this.translate.instant('Got an email') },
|
|
||||||
{ condition: false, label: this.translate.instant("Didn't get an email") }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getter for the filterOptions. Note that in this case, the options are
|
* Constructor.
|
||||||
* generated dynamically, as the options change with the datastore
|
* Subscribes to incoming group definitions.
|
||||||
*/
|
|
||||||
public get filterOptions(): OsFilter[] {
|
|
||||||
return [this.userGroupFilterOptions].concat(this.staticFilterOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contructor. Subscribes to incoming group definitions.
|
|
||||||
*
|
*
|
||||||
* @param store
|
* @param store
|
||||||
* @param groupRepo
|
* @param groupRepo to filter by groups
|
||||||
* @param repo
|
|
||||||
* @param translate marking some translations that are unique here
|
* @param translate marking some translations that are unique here
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(store: StorageService, groupRepo: GroupRepositoryService, private translate: TranslateService) {
|
||||||
store: StorageService,
|
super('User', store);
|
||||||
private groupRepo: GroupRepositoryService,
|
this.updateFilterForRepo(groupRepo, this.userGroupFilterOptions, this.translate.instant('Default'), [1]);
|
||||||
repo: UserRepositoryService,
|
|
||||||
private translate: TranslateService
|
|
||||||
) {
|
|
||||||
super(store, repo);
|
|
||||||
this.subscribeGroups();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the filter according to existing groups.
|
* @returns the filter definition
|
||||||
* TODO: Users with only the 'standard' group set appear in the model as items without groups_id. 'Standard' filter is broken
|
|
||||||
*/
|
*/
|
||||||
public subscribeGroups(): void {
|
protected getFilterDefinitions(): OsFilter[] {
|
||||||
this.groupRepo.getViewModelListObservable().subscribe(groups => {
|
const staticFilterOptions: OsFilter[] = [
|
||||||
const groupOptions = [];
|
{
|
||||||
groups.forEach(group => {
|
property: 'is_present',
|
||||||
groupOptions.push({
|
label: 'Presence',
|
||||||
condition: group.id,
|
options: [
|
||||||
label: group.name,
|
{ condition: true, label: this.translate.instant('Is present') },
|
||||||
isActive: false
|
{ condition: false, label: this.translate.instant('Is not present') }
|
||||||
});
|
]
|
||||||
});
|
},
|
||||||
this.userGroupFilterOptions.options = groupOptions;
|
{
|
||||||
this.updateFilterDefinitions(this.filterOptions);
|
property: 'is_active',
|
||||||
});
|
label: this.translate.instant('Active'),
|
||||||
|
options: [
|
||||||
|
{ condition: true, label: 'Is active' },
|
||||||
|
{ condition: false, label: this.translate.instant('Is not active') }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
property: 'is_committee',
|
||||||
|
label: this.translate.instant('Committee'),
|
||||||
|
options: [
|
||||||
|
{ condition: true, label: 'Is a committee' },
|
||||||
|
{ condition: false, label: this.translate.instant('Is not a committee') }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
property: 'is_last_email_send',
|
||||||
|
label: this.translate.instant('Last email send'),
|
||||||
|
options: [
|
||||||
|
{ condition: true, label: this.translate.instant('Got an email') },
|
||||||
|
{ condition: false, label: this.translate.instant("Didn't get an email") }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return staticFilterOptions.concat(this.userGroupFilterOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,40 +1,52 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { BaseSortListService, OsSortingDefinition } from 'app/core/ui-services/base-sort-list.service';
|
import { BaseSortListService, OsSortingDefinition, OsSortingOption } from 'app/core/ui-services/base-sort-list.service';
|
||||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
|
||||||
import { StorageService } from 'app/core/core-services/storage.service';
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
import { ViewUser } from '../models/view-user';
|
import { ViewUser } from '../models/view-user';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorting service for the user list
|
||||||
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class UserSortListService extends BaseSortListService<ViewUser> {
|
export class UserSortListService extends BaseSortListService<ViewUser> {
|
||||||
public sortOptions: OsSortingDefinition<ViewUser> = {
|
/**
|
||||||
sortProperty: 'first_name',
|
* Define the sort options
|
||||||
sortAscending: true,
|
*/
|
||||||
options: [
|
public sortOptions: OsSortingOption<ViewUser>[] = [
|
||||||
{ property: 'first_name', label: 'Given name' },
|
{ property: 'first_name', label: 'Given name' },
|
||||||
{ property: 'last_name', label: 'Surname' },
|
{ property: 'last_name', label: 'Surname' },
|
||||||
{ property: 'is_present', label: 'Presence' },
|
{ property: 'is_present', label: 'Presence' },
|
||||||
{ property: 'is_active', label: 'Is active' },
|
{ property: 'is_active', label: 'Is active' },
|
||||||
{ property: 'is_committee', label: 'Is committee' },
|
{ property: 'is_committee', label: 'Is committee' },
|
||||||
{ property: 'number', label: 'Participant number' },
|
{ property: 'number', label: 'Participant number' },
|
||||||
{ property: 'structure_level', label: 'Structure level' },
|
{ property: 'structure_level', label: 'Structure level' },
|
||||||
{ property: 'comment' }
|
{ property: 'comment' }
|
||||||
]
|
// TODO email send?
|
||||||
};
|
];
|
||||||
protected name = 'User';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor. Sets the default sorting if none is set locally
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param translate
|
* @param translate required by parent
|
||||||
* @param store
|
* @param store requires by parent
|
||||||
* @param config
|
|
||||||
*/
|
*/
|
||||||
public constructor(translate: TranslateService, store: StorageService, config: ConfigService) {
|
public constructor(translate: TranslateService, store: StorageService) {
|
||||||
super(translate, store);
|
super('User', translate, store);
|
||||||
this.defaultSorting = config.instant<keyof ViewUser>('users_sort_by');
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required by parent
|
||||||
|
*
|
||||||
|
* @returns the default sorting strategy
|
||||||
|
*/
|
||||||
|
public async getDefaultDefinition(): Promise<OsSortingDefinition<ViewUser>> {
|
||||||
|
return {
|
||||||
|
sortProperty: 'first_name',
|
||||||
|
sortAscending: true
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,7 +197,7 @@ def get_config_variables():
|
|||||||
input_type="choice",
|
input_type="choice",
|
||||||
label="Sort motions by",
|
label="Sort motions by",
|
||||||
choices=(
|
choices=(
|
||||||
{"value": "callListWeight", "display_name": "Call list"},
|
{"value": "weight", "display_name": "Call list"},
|
||||||
{"value": "identifier", "display_name": "Identifier"},
|
{"value": "identifier", "display_name": "Identifier"},
|
||||||
),
|
),
|
||||||
weight=335,
|
weight=335,
|
||||||
|
Loading…
Reference in New Issue
Block a user