Replace non-copyable table state classes with objects

pull/7082/head
Oliver Günther 6 years ago
parent 3fdddd379d
commit ba8321889a
No known key found for this signature in database
GPG Key ID: A3A8BDAD7C0C552C
  1. 4
      frontend/src/app/ckeditor/ckeditor-augmented-textarea.component.ts
  2. 10
      frontend/src/app/components/filters/filter-container/filter-container.directive.ts
  3. 4
      frontend/src/app/components/filters/query-filters/query-filters.component.html
  4. 28
      frontend/src/app/components/filters/query-filters/query-filters.component.ts
  5. 27
      frontend/src/app/components/filters/quick-filter-by-text-input/quick-filter-by-text-input.component.ts
  6. 37
      frontend/src/app/components/states.service.ts
  7. 4
      frontend/src/app/components/wp-buttons/wp-filter-button/wp-filter-button.component.ts
  8. 7
      frontend/src/app/components/wp-fast-table/handlers/cell/relations-cell-handler.ts
  9. 21
      frontend/src/app/components/wp-fast-table/handlers/state/hierarchy-transformer.ts
  10. 2
      frontend/src/app/components/wp-fast-table/handlers/state/timeline-transformer.ts
  11. 49
      frontend/src/app/components/wp-fast-table/state/wp-table-base.service.ts
  12. 10
      frontend/src/app/components/wp-fast-table/state/wp-table-columns.service.ts
  13. 266
      frontend/src/app/components/wp-fast-table/state/wp-table-filters.service.ts
  14. 48
      frontend/src/app/components/wp-fast-table/state/wp-table-group-by.service.ts
  15. 28
      frontend/src/app/components/wp-fast-table/state/wp-table-hierarchy.service.ts
  16. 13
      frontend/src/app/components/wp-fast-table/state/wp-table-highlighting.service.ts
  17. 52
      frontend/src/app/components/wp-fast-table/state/wp-table-relation-columns.service.ts
  18. 52
      frontend/src/app/components/wp-fast-table/state/wp-table-sort-by.service.ts
  19. 39
      frontend/src/app/components/wp-fast-table/state/wp-table-sum.service.ts
  20. 64
      frontend/src/app/components/wp-fast-table/state/wp-table-timeline.service.ts
  21. 2
      frontend/src/app/components/wp-fast-table/wp-fast-table.ts
  22. 31
      frontend/src/app/components/wp-fast-table/wp-table-base.ts
  23. 116
      frontend/src/app/components/wp-fast-table/wp-table-filters.ts
  24. 47
      frontend/src/app/components/wp-fast-table/wp-table-group-by.ts
  25. 25
      frontend/src/app/components/wp-fast-table/wp-table-hierarchies.ts
  26. 34
      frontend/src/app/components/wp-fast-table/wp-table-highlight.ts
  27. 4
      frontend/src/app/components/wp-fast-table/wp-table-pagination.ts
  28. 23
      frontend/src/app/components/wp-fast-table/wp-table-relation-columns.ts
  29. 59
      frontend/src/app/components/wp-fast-table/wp-table-sort-by.ts
  30. 46
      frontend/src/app/components/wp-fast-table/wp-table-sum.ts
  31. 38
      frontend/src/app/components/wp-fast-table/wp-table-timeline.ts
  32. 8
      frontend/src/app/components/wp-fast-table/wp-table.interfaces.ts
  33. 2
      frontend/src/app/components/wp-list/wp-states-initialization.service.ts
  34. 13
      frontend/src/app/components/wp-table/configuration-modal/tabs/display-settings-tab.component.ts
  35. 8
      frontend/src/app/components/wp-table/configuration-modal/tabs/filters-tab.component.ts
  36. 4
      frontend/src/app/components/wp-table/configuration-modal/tabs/highlighting-tab.component.ts
  37. 4
      frontend/src/app/components/wp-table/configuration-modal/tabs/sort-by-tab.component.ts
  38. 18
      frontend/src/app/components/wp-table/configuration-modal/wp-table-configuration-relation-selector.ts
  39. 6
      frontend/src/app/components/wp-table/embedded/wp-embedded-table.component.ts
  40. 8
      frontend/src/app/components/wp-table/sort-header/sort-header.directive.ts
  41. 34
      frontend/src/app/components/wp-table/timeline/container/wp-timeline-container.directive.ts
  42. 2
      frontend/src/app/components/wp-table/timeline/global-elements/wp-timeline-relations.directive.ts
  43. 2
      frontend/src/app/components/wp-table/wp-table-sums-row/wp-table-sums-row.directive.ts
  44. 9
      frontend/src/app/components/wp-table/wp-table.directive.html
  45. 16
      frontend/src/app/components/wp-table/wp-table.directive.ts
  46. 2
      frontend/src/app/modules/attachments/attachment-list/attachment-list-item.component.ts
  47. 2
      frontend/src/app/modules/attachments/attachments.component.ts
  48. 17
      frontend/src/app/modules/calendar/wp-calendar/wp-calendar.component.ts
  49. 16
      frontend/src/app/modules/global_search/global-search-work-packages.component.ts
  50. 23
      frontend/src/app/modules/work_packages/query-space/isolated-query-space.ts
  51. 4
      frontend/src/app/modules/work_packages/routing/wp-view-base/work-packages-view.base.ts

@ -134,14 +134,14 @@ export class CkeditorAugmentedTextareaComponent implements OnInit, OnDestroy {
private setupAttachmentAddedCallback(editor:ICKEditorInstance) {
editor.model.on('op:attachment-added', () => {
this.states.forResource(this.resource!).putValue(this.resource!);
this.states.forResource(this.resource!)!.putValue(this.resource!);
});
}
private setupAttachmentRemovalSignal(editor:ICKEditorInstance) {
this.attachments = _.clone(this.resource!.attachments.elements);
this.states.forResource(this.resource!).changes$()
this.states.forResource(this.resource!)!.changes$()
.pipe(
takeUntil(componentDestroyed(this)),
filter(resource => !!resource)

@ -28,10 +28,10 @@
import {Component, Input, OnDestroy, Output} from '@angular/core';
import {WorkPackageTableFiltersService} from 'core-components/wp-fast-table/state/wp-table-filters.service';
import {WorkPackageTableFilters} from 'core-components/wp-fast-table/wp-table-filters';
import {componentDestroyed} from 'ng2-rx-componentdestroyed';
import {WorkPackageFiltersService} from 'core-components/filters/wp-filters/wp-filters.service';
import {DebouncedEventEmitter} from "core-components/angular/debounced-event-emitter";
import {QueryFilterInstanceResource} from "core-app/modules/hal/resources/query-filter-instance-resource";
@Component({
templateUrl: './filter-container.directive.html',
@ -40,17 +40,17 @@ import {DebouncedEventEmitter} from "core-components/angular/debounced-event-emi
export class WorkPackageFilterContainerComponent implements OnDestroy {
@Input('showFilterButton') showFilterButton:boolean = false;
@Input('filterButtonText') filterButtonText:string = I18n.t('js.button_filter');
@Output() public filtersChanged = new DebouncedEventEmitter<WorkPackageTableFilters>(componentDestroyed(this));
@Output() public filtersChanged = new DebouncedEventEmitter<QueryFilterInstanceResource[]>(componentDestroyed(this));
public visible = false;
public filters = this.wpTableFilters.currentState;
public filters = this.wpTableFilters.current;
constructor(readonly wpTableFilters:WorkPackageTableFiltersService,
readonly wpFiltersService:WorkPackageFiltersService) {
this.wpTableFilters
.observeUntil(componentDestroyed(this))
.subscribe(() => {
this.filters = this.wpTableFilters.currentState;
this.filters = this.wpTableFilters.current;
});
this.wpFiltersService
@ -64,7 +64,7 @@ export class WorkPackageFilterContainerComponent implements OnDestroy {
// Nothing to do, added for interface compatibility
}
public replaceIfComplete(filters:WorkPackageTableFilters) {
public replaceIfComplete(filters:QueryFilterInstanceResource[]) {
this.wpTableFilters.replaceIfComplete(filters);
this.filtersChanged.emit(this.filters);
}

@ -1,4 +1,4 @@
<fieldset id="filters" *ngIf="filters && filters.current" class="advanced-filters--container">
<fieldset id="filters" *ngIf="filters" class="advanced-filters--container">
<legend [textContent]="text.selected_filter_list"></legend>
<a *ngIf="showCloseFilter"
@ -24,7 +24,7 @@
<li class="advanced-filters--spacer"></li>
<ng-container *ngFor="let filter of filters.current; trackBy: trackByName ; let index = index">
<ng-container *ngFor="let filter of filters; trackBy: trackByName ; let index = index">
<li id="filter_{{filter.id}}"
query-filter
[filter]="filter"

@ -32,7 +32,6 @@ import {Component, Inject, Input, OnChanges, OnDestroy, OnInit, Output} from '@a
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource';
import {I18nService} from 'core-app/modules/common/i18n/i18n.service';
import {componentDestroyed} from 'ng2-rx-componentdestroyed';
import {WorkPackageTableFilters} from 'core-components/wp-fast-table/wp-table-filters';
import {QueryFilterResource} from 'core-app/modules/hal/resources/query-filter-resource';
import {DebouncedEventEmitter} from 'core-components/angular/debounced-event-emitter';
import {AngularTrackingHelpers} from "core-components/angular/tracking-functions";
@ -46,9 +45,9 @@ const ADD_FILTER_SELECT_INDEX = -1;
})
export class QueryFiltersComponent implements OnInit, OnChanges, OnDestroy {
@Input() public filters:WorkPackageTableFilters;
@Input() public filters:QueryFilterInstanceResource[];
@Input() public showCloseFilter:boolean = false;
@Output() public filtersChanged = new DebouncedEventEmitter<WorkPackageTableFilters>(componentDestroyed(this));
@Output() public filtersChanged = new DebouncedEventEmitter<QueryFilterInstanceResource[]>(componentDestroyed(this));
public filterToBeAdded:QueryFilterResource|undefined;
@ -89,7 +88,8 @@ export class QueryFiltersComponent implements OnInit, OnChanges, OnDestroy {
public onFilterAdded(filterToBeAdded:QueryFilterResource) {
if (filterToBeAdded) {
let newFilter = this.filters.add(filterToBeAdded);
let newFilter = this.wpTableFilters.instantiate(filterToBeAdded);
this.filters.push(newFilter);
this.filterToBeAdded = undefined;
const index = this.currentFilterLength();
@ -105,13 +105,13 @@ export class QueryFiltersComponent implements OnInit, OnChanges, OnDestroy {
}
public isHiddenFilter(filter:QueryFilterResource) {
return _.includes(this.filters.hidden, filter.id);
return _.includes(this.wpTableFilters.hidden, filter.id);
}
public deactivateFilter(removedFilter:QueryFilterInstanceResource) {
let index = this.filters.current.indexOf(removedFilter);
let index = this.filters.indexOf(removedFilter);
_.remove(this.filters, f => f.id === removedFilter.id);
this.filters.remove(removedFilter);
if (removedFilter.isCompletelyDefined()) {
this.filtersChanged.emit(this.filters);
}
@ -121,13 +121,13 @@ export class QueryFiltersComponent implements OnInit, OnChanges, OnDestroy {
}
public get isSecondSpacerVisible():boolean {
return _.reject(this.filters.current, (filter) => {
return (filter.id === 'search');
}).length > 0;
return this.filters
.filter((f) => f.id === 'search')
.length > 0;
}
private updateRemainingFilters() {
this.remainingFilters = _.sortBy(this.filters.remainingVisibleFilters, 'name');
this.remainingFilters = _.sortBy(this.wpTableFilters.remainingVisibleFilters(this.filters), 'name');
}
private updateFilterFocus(index:number) {
@ -138,16 +138,16 @@ export class QueryFiltersComponent implements OnInit, OnChanges, OnDestroy {
} else {
const filterIndex = (index < activeFilterCount) ? index : activeFilterCount - 1;
const filter = this.currentFilterAt(filterIndex);
this.focusElementIndex = this.filters.current.indexOf(filter);
this.focusElementIndex = this.filters.indexOf(filter);
}
}
public currentFilterLength() {
return this.filters.current.length;
return this.filters.length;
}
public currentFilterAt(index:number) {
return this.filters.current[index];
return this.filters[index];
}
}

@ -35,8 +35,8 @@ import {WorkPackageCacheService} from "app/components/work-packages/work-package
import {Subject} from "rxjs";
import {debounceTime, distinctUntilChanged} from "rxjs/operators";
import {DebouncedEventEmitter} from "core-components/angular/debounced-event-emitter";
import {WorkPackageTableFilters} from "core-components/wp-fast-table/wp-table-filters";
import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space";
import {QueryFilterInstanceResource} from "core-app/modules/hal/resources/query-filter-instance-resource";
@Component({
selector: 'wp-filter-by-text-input',
@ -44,7 +44,7 @@ import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/iso
})
export class WorkPackageFilterByTextInputComponent implements OnInit, OnDestroy {
@Output() public filterChanged = new DebouncedEventEmitter<WorkPackageTableFilters>(componentDestroyed(this));
@Output() public filterChanged = new DebouncedEventEmitter<QueryFilterInstanceResource[]>(componentDestroyed(this));
public text = {
createWithDropdown: this.I18n.t('js.work_packages.create.button'),
@ -70,18 +70,22 @@ export class WorkPackageFilterByTextInputComponent implements OnInit, OnDestroy
)
.subscribe(term => {
this.searchTerm = term;
let currentSearchFilter = this.wpTableFilters.find('search');
let filters = this.wpTableFilters.current;
let searchFilter = this.wpTableFilters.find('search');
if (this.searchTerm.length > 0) {
if (!currentSearchFilter) {
currentSearchFilter = this.wpTableFilters.currentState.add(this.availableSearchFilter);
if (!searchFilter) {
searchFilter = this.wpTableFilters.instantiate(this.availableSearchFilter);
}
currentSearchFilter.operator = currentSearchFilter.findOperator('**')!;
currentSearchFilter.values = [this.searchTerm];
} else if (currentSearchFilter) {
this.wpTableFilters.currentState.remove(currentSearchFilter);
searchFilter.operator = searchFilter.findOperator('**')!;
searchFilter.values = [this.searchTerm];
filters.push(searchFilter);
} else if (searchFilter) {
let id = searchFilter.id;
_.remove(filters, f => f.id === id);
}
this.filterChanged.emit(this.wpTableFilters.currentState);
this.filterChanged.emit(filters);
});
}
@ -100,8 +104,7 @@ export class WorkPackageFilterByTextInputComponent implements OnInit, OnDestroy
this.searchTerm = '';
}
self.availableSearchFilter = _.find(self.wpTableFilters.currentState.availableFilters,
{ id: 'search' }) as QueryFilterResource;
self.availableSearchFilter = this.wpTableFilters.findAvailableFilter('search')!;
});
}

@ -1,22 +1,22 @@
import {ProjectResource} from 'core-app/modules/hal/resources/project-resource';
import {QueryGroupByResource} from 'core-app/modules/hal/resources/query-group-by-resource';
import {QuerySortByResource} from 'core-app/modules/hal/resources/query-sort-by-resource';
import {SchemaResource} from 'core-app/modules/hal/resources/schema-resource';
import {TypeResource} from 'core-app/modules/hal/resources/type-resource';
import {UserResource} from 'core-app/modules/hal/resources/user-resource';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WPFocusState} from 'core-components/wp-fast-table/state/wp-table-focus.service';
import {input, InputState, multiInput, MultiInputState, StatesGroup} from 'reactivestates';
import {QueryColumn} from './wp-query/query-column';
import {WikiPageResource} from 'core-app/modules/hal/resources/wiki-page-resource';
import {PostResource} from 'core-app/modules/hal/resources/post-resource';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {StatusResource} from "core-app/modules/hal/resources/status-resource";
import {QueryFilterInstanceSchemaResource} from "core-app/modules/hal/resources/query-filter-instance-schema-resource";
import {Subject} from "rxjs";
import {QuerySortByResource} from "core-app/modules/hal/resources/query-sort-by-resource";
import {QueryGroupByResource} from "core-app/modules/hal/resources/query-group-by-resource";
import {Input} from "@angular/core";
import {QueryFilterResource} from "core-app/modules/hal/resources/query-filter-resource";
export class States extends StatesGroup {
[key:string]:any;
name = 'MainStore';
/* /api/v3/projects */
@ -25,9 +25,6 @@ export class States extends StatesGroup {
/* /api/v3/work_packages */
workPackages = multiInput<WorkPackageResource>();
/* /api/v3/wiki_pages */
wikiPages = multiInput<WikiPageResource>();
/* /api/v3/wiki_pages */
posts = multiInput<PostResource>();
@ -37,7 +34,7 @@ export class States extends StatesGroup {
/* /api/v3/types */
types = multiInput<TypeResource>();
/* /api/v3/types */
/* /api/v3/statuses */
statuses = multiInput<StatusResource>();
/* /api/v3/users */
@ -49,20 +46,28 @@ export class States extends StatesGroup {
// Global events to isolated changes
changes = new GlobalStateChanges();
forResource(resource:HalResource):InputState<HalResource> {
let stateName = _.camelCase(resource._type) + 's';
// Additional state map that can be dynamically registered.
additional:{[id:string]:MultiInputState<HalResource>} = {};
forResource(resource:HalResource):InputState<HalResource>|undefined {
const stateName = _.camelCase(resource._type) + 's';
let state = this.additional[stateName];
if (!state) {
state = this.additional[stateName] = multiInput<HalResource>();
}
return this[stateName].get(resource.id);
return state && state.get(resource.id);
}
public add(name:string, state:MultiInputState<HalResource>) {
this[name] = state;
this.additional[name] = state;
}
}
export class GlobalStateChanges {
// Global subject on changes to the given query ID
queries = new Subject<number>();
queries = new Subject();
}
export class QueryAvailableDataStates {
@ -75,6 +80,6 @@ export class QueryAvailableDataStates {
// Available GroupBy columns
groupBy = input<QueryGroupByResource[]>();
// Filters remain special, since they require their schema to be loaded
// Thus the table state is not initialized until all values are available.
// Available filter schemas (derived from their schema)
filters = input<QueryFilterInstanceSchemaResource[]>();
}

@ -89,8 +89,8 @@ export class WorkPackageFilterButtonComponent extends AbstractWorkPackageButtonC
private setupObserver() {
this.wpTableFilters
.observeUntil(componentDestroyed(this))
.subscribe(state => {
this.count = state.currentlyVisibleFilters.length;
.subscribe(() => {
this.count = this.wpTableFilters.currentlyVisibleFilters.length;
this.initialized = true;
});
}

@ -41,14 +41,11 @@ export class RelationsCellHandler extends ClickOrEnterHandler implements TableEv
const rowElement = jQuery(evt.target).closest(`.${tableRowClassName}`);
const workPackageId = rowElement.data('workPackageId');
// Get any existing edit state for this work package
let state = this.wpTableRelationColumns.current;
// If currently expanded
if (state.getExpandFor(workPackageId) === columnId) {
if (this.wpTableRelationColumns.getExpandFor(workPackageId) === columnId) {
this.wpTableRelationColumns.collapse(workPackageId);
} else {
this.wpTableRelationColumns.expandFor(workPackageId, columnId);
this.wpTableRelationColumns.setExpandFor(workPackageId, columnId);
}
return false;

@ -1,13 +1,16 @@
import {Injector} from '@angular/core';
import {scrollTableRowIntoView} from 'core-components/wp-fast-table/helpers/wp-table-row-helpers';
import {distinctUntilChanged, map, takeUntil} from 'rxjs/operators';
import {indicatorCollapsedClass} from '../../builders/modes/hierarchy/single-hierarchy-row-builder';
import {tableRowClassName} from '../../builders/rows/single-row-builder';
import {collapsedGroupClass, hierarchyGroupClass, hierarchyRootClass} from '../../helpers/wp-table-hierarchy-helpers';
import {WorkPackageTable} from '../../wp-fast-table';
import {WorkPackageTableHierarchies} from '../../wp-table-hierarchies';
import {WorkPackageTableHierarchiesService} from './../../state/wp-table-hierarchy.service';
import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space";
import {WorkPackageTableHierarchiesService} from "core-components/wp-fast-table/state/wp-table-hierarchy.service";
import {WorkPackageTable} from "core-components/wp-fast-table/wp-fast-table";
import {WorkPackageTableHierarchies} from "core-components/wp-fast-table/wp-table-hierarchies";
import {
collapsedGroupClass, hierarchyGroupClass,
hierarchyRootClass
} from "core-components/wp-fast-table/helpers/wp-table-hierarchy-helpers";
import {indicatorCollapsedClass} from "core-components/wp-fast-table/builders/modes/hierarchy/single-hierarchy-row-builder";
import {tableRowClassName} from "core-components/wp-fast-table/builders/rows/single-row-builder";
export class HierarchyTransformer {
@ -20,7 +23,7 @@ export class HierarchyTransformer {
.values$('Refreshing hierarchies on user request')
.pipe(
takeUntil(this.querySpace.stopAllSubscriptions),
map((state) => state.isEnabled),
map((state) => state.isVisible),
distinctUntilChanged()
)
.subscribe(() => {
@ -36,11 +39,11 @@ export class HierarchyTransformer {
.observeUntil(this.querySpace.stopAllSubscriptions)
.subscribe((state:WorkPackageTableHierarchies) => {
if (state.isEnabled === lastValue) {
if (state.isVisible === lastValue) {
this.renderHierarchyState(state);
}
lastValue = state.isEnabled;
lastValue = state.isVisible;
});
}

@ -16,7 +16,7 @@ export class TimelineTransformer {
takeUntil(this.querySpace.stopAllSubscriptions)
)
.subscribe((state:WorkPackageTableTimelineState) => {
this.renderVisibility(state.isVisible);
this.renderVisibility(state.visible);
});
}

@ -36,15 +36,9 @@ import {WorkPackageCollectionResource} from 'core-app/modules/hal/resources/wp-c
export abstract class WorkPackageTableBaseService<T> {
constructor(readonly querySpace:IsolatedQuerySpace) {
constructor(protected readonly querySpace:IsolatedQuerySpace) {
}
/**
* Return the state this service cares for from the table state.
* @returns {InputState<T>}
*/
public abstract get state():InputState<T>;
/**
* Get the state value from the current query.
*
@ -84,21 +78,52 @@ export abstract class WorkPackageTableBaseService<T> {
)
.toPromise();
}
/**
* Return the state this service cares for from the table state.
* @returns {InputState<T>}
*/
protected abstract get state():InputState<T>;
/**
* Return a public read-only state
*/
public get readonlyState():State<T> {
return this.state;
}
/**
* Helper to set the value of the current state
* @param val
*/
protected set current(val:T|undefined) {
if (val) {
this.state.putValue(val);
} else {
this.state.clear();
}
}
/**
* Get the value of the current state, if any.
*/
protected get current():T|undefined {
return this.state.value;
}
}
export interface WorkPackageQueryStateService {
export abstract class WorkPackageQueryStateService<T> extends WorkPackageTableBaseService<T> {
/**
* Check whether the state value does not match the query resource's value.
* @param query The current query resource
*/
hasChanged(query:QueryResource):boolean;
abstract hasChanged(query:QueryResource):boolean;
/**
* Apply the current state value to query
*
* @return Whether the query should be visibly updated.
*/
applyToQuery(query:QueryResource):boolean;
state:State<any>;
abstract applyToQuery(query:QueryResource):boolean;
}

@ -26,7 +26,7 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {WorkPackageQueryStateService, WorkPackageTableBaseService} from './wp-table-base.service';
import {WorkPackageQueryStateService} from './wp-table-base.service';
import {QueryResource} from 'core-app/modules/hal/resources/query-resource';
import {QueryColumn, queryColumnTypes} from '../../wp-query/query-column';
import {InputState} from 'reactivestates';
@ -36,7 +36,7 @@ import {Injectable} from '@angular/core';
import {cloneHalResourceCollection} from 'core-app/modules/hal/helpers/hal-resource-builder';
@Injectable()
export class WorkPackageTableColumnsService extends WorkPackageTableBaseService<QueryColumn[]> implements WorkPackageQueryStateService {
export class WorkPackageTableColumnsService extends WorkPackageQueryStateService<QueryColumn[]> {
public constructor(readonly states:States, readonly querySpace:IsolatedQuerySpace) {
super(querySpace);
@ -110,7 +110,7 @@ export class WorkPackageTableColumnsService extends WorkPackageTableBaseService<
/**
* Return the previous column of the given column name
* @param name
* @param column
*/
public previous(column:QueryColumn):QueryColumn|null {
let index = this.index(column.id);
@ -124,7 +124,7 @@ export class WorkPackageTableColumnsService extends WorkPackageTableBaseService<
/**
* Return the next column of the given column name
* @param name
* @param column
*/
public next(column:QueryColumn):QueryColumn|null {
let index = this.index(column.id);
@ -159,7 +159,7 @@ export class WorkPackageTableColumnsService extends WorkPackageTableBaseService<
return;
}
this.state.putValue(columns);
this.current = columns;
}
public setColumnsById(columnIds:string[]) {

@ -26,89 +26,287 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {WorkPackageQueryStateService, WorkPackageTableBaseService} from './wp-table-base.service';
import {WorkPackageQueryStateService} from './wp-table-base.service';
import {Injectable} from '@angular/core';
import {QueryResource} from 'core-app/modules/hal/resources/query-resource';
import {QuerySchemaResource} from 'core-app/modules/hal/resources/query-schema-resource';
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource';
import {CollectionResource} from 'core-app/modules/hal/resources/collection-resource';
import {WorkPackageTableFilters} from '../wp-table-filters';
import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space";
import {InputState} from 'reactivestates';
import {combine, InputState} from 'reactivestates';
import {cloneHalResourceCollection} from 'core-app/modules/hal/helpers/hal-resource-builder';
import {QueryFilterResource} from "core-app/modules/hal/resources/query-filter-resource";
import {QueryFilterInstanceSchemaResource} from "core-app/modules/hal/resources/query-filter-instance-schema-resource";
import {States} from "core-components/states.service";
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {mapTo, take} from "rxjs/operators";
@Injectable()
export class WorkPackageTableFiltersService extends WorkPackageTableBaseService<WorkPackageTableFilters> implements WorkPackageQueryStateService {
export class WorkPackageTableFiltersService extends WorkPackageQueryStateService<QueryFilterInstanceResource[]> {
public hidden:Readonly<string[]> = [
'id',
'parent',
'datesInterval',
'precedes',
'follows',
'relates',
'duplicates',
'duplicated',
'blocks',
'blocked',
'partof',
'includes',
'requires',
'required',
'search',
'subjectOrId'
];
constructor(readonly querySpace:IsolatedQuerySpace) {
constructor(protected readonly states:States,
readonly querySpace:IsolatedQuerySpace) {
super(querySpace);
}
public get state():InputState<WorkPackageTableFilters> {
protected get state():InputState<QueryFilterInstanceResource[]> {
return this.querySpace.filters;
}
public valueFromQuery(query:QueryResource):WorkPackageTableFilters|undefined {
return undefined;
}
/**
* Load all schemas for the current filters and fill respective states
* @param query
* @param schema
*/
public initializeFilters(query:QueryResource, schema:QuerySchemaResource) {
let filters = _.map(query.filters, filter => filter.$copy<QueryFilterInstanceResource>());
let filters = cloneHalResourceCollection<QueryFilterInstanceResource>(query.filters);
this.loadCurrentFiltersSchemas(filters).then(() => {
let newState = new WorkPackageTableFilters(filters, schema.filtersSchemas.elements);
this.availableState.putValue(schema.filtersSchemas.elements);
this.update(filters);
});
}
/**
* Return whether rth
*/
public get isEmpty() {
const value = this.state.value;
return !value || value.length === 0;
}
public get availableState():InputState<QueryFilterInstanceSchemaResource[]> {
return this.states.queries.filters;
}
/**
* Add a filter instantiation from the set of available filter schemas
*
* @param filter
*/
public add(filter:QueryFilterInstanceResource) {
this.state.doModify(filters => [...filters, filter]);
}
this.state.putValue(newState);
/**
* Modify a live filter and push it to the state.
* Avoids copying the resource.
*
* Returns whether the filter was found and modified
*/
public modify(id:string, modifier:(filter:QueryFilterInstanceResource) => void):boolean {
const index = this.findIndex(id);
if (index === -1) {
return false;
}
this.state.doModify(filters => {
modifier(filters[index]!);
return filters;
});
return true;
}
/**
* Get an instantiated filter without adding it to the current state
* @param filter
*/
public instantiate(filter:QueryFilterResource):QueryFilterInstanceResource {
let schema = _.find(this.availableSchemas, schema => (schema.filter.allowedValues as HalResource)[0].id === filter.id)!;
return schema.getFilter();
}
/**
* Remove one or more filters from the live state of filters.
* @param filters Filters to be removed
*/
public remove(...filters:QueryFilterInstanceResource[]) {
let set = new Set<string>(filters.map(f => f.id));
this.state.doModify(value => {
return value.filter(f => !set.has(f.id));
});
}
/**
* Return the remaining visible filters from the given filters set.
* @param filters Array of active filters, defaults to the current live state.
*/
public remainingVisibleFilters(filters = this.current) {
return this
.remainingFilters(filters)
.filter((filter) => this.hidden.indexOf(filter.id) === -1);
}
/**
* Return all available filter resources.
* They need to be instantiated before using them in this service.
*/
public get availableFilters():QueryFilterResource[] {
return this.availableSchemas.map(schema => schema.allowedFilterValue);
}
private get availableSchemas():QueryFilterInstanceSchemaResource[] {
return this.availableState.getValueOr([]);
}
/**
* Find an available filter by its ID. Can be used to instantiate or add
* with +get+ or +add+ methods on this service.
*
* @param id Internal identifier string of the filter
*/
public findAvailableFilter(id:string):QueryFilterResource|undefined {
return _.find(this.availableFilters, f => f.id === id);
}
/**
* Determine whether all given filters are completely defined.
* @param filters
*/
public isComplete(filters:QueryFilterInstanceResource[]):boolean {
return _.every(filters, filter => filter.isCompletelyDefined());
}
/**
* Compare the current set of filters to the given query.
* @param query
*/
public hasChanged(query:QueryResource) {
const comparer = (filter:QueryFilterInstanceResource[]) => filter.map(el => el.$source);
return !_.isEqual(
comparer(query.filters),
comparer(this.current)
comparer(this.rawFilters)
);
}
public valueFromQuery(query:QueryResource) {
return undefined;
}
/**
* Returns the live filter instance for the given ID, or undefined
* if it does not exist.
*
* @param id Identifier of the filter
*/
public find(id:string):QueryFilterInstanceResource|undefined {
return _.find(this.currentState.current, filter => filter.id === id);
const index = this.findIndex(id);
if (index === -1) {
return;
}
return this.rawFilters[index];
}
/**
* Returns the index of the filter, or -1 if it does not exist
* @param id Identifier of the filter
*/
public findIndex(id:string):number {
return _.findIndex(this.current, f => f.id === id);
}
public applyToQuery(query:QueryResource) {
query.filters = this.current;
query.filters = this.cloneFilters();
return true;
}
public get currentState():WorkPackageTableFilters {
return this.state.value as WorkPackageTableFilters;
/**
* Returns a shallow copy of the current filters.
* Modifications to filters themselves will still
*/
public get current():QueryFilterInstanceResource[] {
return [...this.rawFilters];
}
public get current():QueryFilterInstanceResource[]{
if (this.currentState) {
return cloneHalResourceCollection<QueryFilterInstanceResource>(this.currentState.current);
} else {
return [];
}
/**
* Returns a deep clone of the current filters set, may be used
* to modify the filters without altering this state.
*/
public cloneFilters() {
return cloneHalResourceCollection<QueryFilterInstanceResource>(this.rawFilters);
}
public replace(newState:WorkPackageTableFilters) {
this.state.putValue(newState);
/**
* Returns the live state array, used for inspection of the filters
* without modification.
*/
protected get rawFilters():Readonly<QueryFilterInstanceResource[]> {
return this.state.getValueOr([]);
}
public replaceIfComplete(newState:WorkPackageTableFilters) {
if (newState.isComplete()) {
this.state.putValue(newState);
public get currentlyVisibleFilters() {
const invisibleFilters = new Set(this.hidden);
invisibleFilters.delete('search');
return _.reject(this.currentFilterResources, (filter) => invisibleFilters.has(filter.id));
}
/**
* Replace this filter state, but only if the given filters are complete
* @param newState
*/
public replaceIfComplete(newState:QueryFilterInstanceResource[]) {
if (this.isComplete(newState)) {
this.update(newState);
}
}
public remove(removedFilter:QueryFilterInstanceResource) {
this.currentState.remove(removedFilter);
/**
* Filters service depends on two states
*/
public onReady() {
return combine(this.state, this.availableState)
.values$()
.pipe(
take(1),
mapTo(null)
)
.toPromise();
}
/**
* Get all filters that are not in the current active set
*/
private remainingFilters(filters = this.rawFilters) {
return _.differenceBy(this.availableFilters, filters, filter => filter.id);
}
this.state.putValue(this.currentState);
/**
* Map current filter instances to their FilterResource
*/
private get currentFilterResources():QueryFilterResource[] {
return this.rawFilters.map((filter:QueryFilterInstanceResource) => filter.filter);
}
private loadCurrentFiltersSchemas(filters:QueryFilterInstanceResource[]):Promise<{}> {
/**
* Ensure all filter schemas are loaded.
* @param filters
*/
private loadCurrentFiltersSchemas(filters:QueryFilterInstanceResource[]):Promise<unknown> {
return Promise.all(filters.map((filter:QueryFilterInstanceResource) => filter.schema.$load()));
}
}

@ -28,34 +28,33 @@
import {QueryResource} from 'core-app/modules/hal/resources/query-resource';
import {QueryGroupByResource} from 'core-app/modules/hal/resources/query-group-by-resource';
import {WorkPackageTableGroupBy} from '../wp-table-group-by';
import {WorkPackageQueryStateService, WorkPackageTableBaseService} from './wp-table-base.service';
import {WorkPackageQueryStateService} from './wp-table-base.service';
import {QueryColumn} from '../../wp-query/query-column';
import {InputState} from 'reactivestates';
import {WorkPackageCollectionResource} from 'core-app/modules/hal/resources/wp-collection-resource';
import {States} from 'core-components/states.service';
import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space";
import {Injectable} from '@angular/core';
import {WorkPackageTableColumnsService} from 'core-components/wp-fast-table/state/wp-table-columns.service';
import {cloneHalResource} from 'core-app/modules/hal/helpers/hal-resource-builder';
import {Observable} from "rxjs";
import {takeUntil} from "rxjs/operators";
@Injectable()
export class WorkPackageTableGroupByService extends WorkPackageTableBaseService<WorkPackageTableGroupBy> implements WorkPackageQueryStateService {
export class WorkPackageTableGroupByService extends WorkPackageQueryStateService<QueryGroupByResource|null> {
public constructor(readonly states:States,
readonly querySpace:IsolatedQuerySpace) {
super(querySpace);
}
public get state():InputState<WorkPackageTableGroupBy> {
public get state():InputState<QueryGroupByResource|null> {
return this.querySpace.groupBy;
}
valueFromQuery(query:QueryResource) {
return new WorkPackageTableGroupBy(query);
return query.groupBy || null;
}
public hasChanged(query:QueryResource) {
const comparer = (groupBy:QueryColumn|undefined) => groupBy ? groupBy.href : null;
const comparer = (groupBy:QueryColumn|null|undefined) => groupBy ? groupBy.href : null;
return !_.isEqual(
comparer(query.groupBy),
@ -64,7 +63,8 @@ export class WorkPackageTableGroupByService extends WorkPackageTableBaseService<
}
public applyToQuery(query:QueryResource) {
query.groupBy = cloneHalResource<QueryGroupByResource>(this.current);
const current = this.current;
query.groupBy = current === null ? undefined : current;
return true;
}
@ -72,46 +72,32 @@ export class WorkPackageTableGroupByService extends WorkPackageTableBaseService<
return !!_.find(this.available, candidate => candidate.id === column.id);
}
public set(groupBy:QueryGroupByResource|undefined) {
let currentState = this.currentState;
currentState.current = groupBy;
public update(groupBy:QueryGroupByResource|null) {
// hierarchies and group by are mutually exclusive
if (groupBy) {
var hierarchy = this.querySpace.hierarchies.value!;
hierarchy.current = false;
this.querySpace.hierarchies.putValue(hierarchy);
if (groupBy !== null) {
let hierarchy = this.querySpace.hierarchies.value!;
this.querySpace.hierarchies.putValue({ ...hierarchy, isVisible: false });
}
this.state.putValue(currentState);
super.update(groupBy);
}
public setBy(column:QueryColumn) {
let currentState = this.currentState;
let groupBy = _.find(this.available, candidate => candidate.id === column.id);
if (groupBy) {
this.set(groupBy);
this.update(groupBy);
}
}
protected get currentState():WorkPackageTableGroupBy {
return this.state.value as WorkPackageTableGroupBy;
public get current():QueryGroupByResource|null {
return this.state.getValueOr(null);
}
protected get availableState() {
return this.states.queries.groupBy;
}
public get current():QueryGroupByResource|undefined {
if (this.currentState) {
return this.currentState.current;
} else {
return undefined;
}
}
public get isEnabled():boolean {
return !!this.current;
}

@ -1,13 +1,17 @@
import {QueryResource} from 'core-app/modules/hal/resources/query-resource';
import {InputState} from 'reactivestates';
import {WorkPackageQueryStateService, WorkPackageTableBaseService} from './wp-table-base.service';
import {WorkPackageQueryStateService} from './wp-table-base.service';
import {WorkPackageTableHierarchies} from '../wp-table-hierarchies';
import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space";
import {Injectable} from '@angular/core';
import {WorkPackageTableSortByService} from "core-components/wp-fast-table/state/wp-table-sort-by.service";
import {WorkPackageTableGroupByService} from "core-components/wp-fast-table/state/wp-table-group-by.service";
@Injectable()
export class WorkPackageTableHierarchiesService extends WorkPackageTableBaseService<WorkPackageTableHierarchies> implements WorkPackageQueryStateService {
public constructor(querySpace:IsolatedQuerySpace) {
export class WorkPackageTableHierarchiesService extends WorkPackageQueryStateService<WorkPackageTableHierarchies> {
public constructor(protected readonly querySpace:IsolatedQuerySpace,
protected wpTableGroupBy:WorkPackageTableGroupByService,
protected wpTableSortBy:WorkPackageTableSortByService) {
super(querySpace);
}
@ -15,7 +19,7 @@ export class WorkPackageTableHierarchiesService extends WorkPackageTableBaseServ
return this.querySpace.hierarchies;
}
public valueFromQuery(query:QueryResource):WorkPackageTableHierarchies|undefined {
public valueFromQuery(query:QueryResource):WorkPackageTableHierarchies {
return new WorkPackageTableHierarchies(query.showHierarchies);
}
@ -34,27 +38,21 @@ export class WorkPackageTableHierarchiesService extends WorkPackageTableBaseServ
* Return whether the current hierarchy mode is active
*/
public get isEnabled():boolean {
return this.currentState.isEnabled;
return !!(this.current && this.current.isVisible);
}
public setEnabled(active:boolean = true) {
const state = this.currentState;
state.current = active;
state.last = null;
const state = { collapsed: {}, ...this.current, isVisible: active, last: null };
if (active) {
// hierarchies and group by are mutually exclusive
var groupBy = this.querySpace.groupBy.value!;
groupBy.current = undefined;
this.querySpace.groupBy.putValue(groupBy);
this.wpTableGroupBy.update(null);
// hierarchies and sort by are mutually exclusive
var sortBy = this.querySpace.sortBy.value!;
sortBy.current = [];
this.querySpace.sortBy.putValue(sortBy);
this.wpTableSortBy.update([]);
}
this.state.putValue(state);
this.update(state);
}
/**

@ -1,5 +1,5 @@
import {QueryResource} from 'core-app/modules/hal/resources/query-resource';
import {WorkPackageQueryStateService, WorkPackageTableBaseService} from './wp-table-base.service';
import {WorkPackageQueryStateService} from './wp-table-base.service';
import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space";
import {Injectable} from '@angular/core';
import {States} from 'core-components/states.service';
@ -9,7 +9,7 @@ import {BannersService} from "core-app/modules/common/enterprise/banners.service
import {HalResource} from "core-app/modules/hal/resources/hal-resource";
@Injectable()
export class WorkPackageTableHighlightingService extends WorkPackageTableBaseService<WorkPackageTableHighlight> implements WorkPackageQueryStateService {
export class WorkPackageTableHighlightingService extends WorkPackageQueryStateService<WorkPackageTableHighlight>{
public constructor(readonly states:States,
readonly Banners:BannersService,
readonly dynamicCssService:DynamicCssService,
@ -42,7 +42,7 @@ export class WorkPackageTableHighlightingService extends WorkPackageTableBaseSer
}
public get current():WorkPackageTableHighlight {
let value = this.state.getValueOr(new WorkPackageTableHighlight('inline'));
let value = this.state.getValueOr({ mode: 'inline' } as WorkPackageTableHighlight);
return this.filteredValue(value);
}
@ -55,6 +55,10 @@ export class WorkPackageTableHighlightingService extends WorkPackageTableBaseSer
}
public update(value:WorkPackageTableHighlight) {
if (_.isEmpty(value.selectedAttributes)) {
value.selectedAttributes = undefined;
}
super.update(this.filteredValue(value));
// Load dynamic highlighting CSS if enabled
@ -64,7 +68,8 @@ export class WorkPackageTableHighlightingService extends WorkPackageTableBaseSer
}
public valueFromQuery(query:QueryResource):WorkPackageTableHighlight {
return this.filteredValue(new WorkPackageTableHighlight(query.highlightingMode, query.highlightedAttributes));
const highlight = { mode: query.highlightingMode || 'inline', highlightedAttributes: query.highlightedAttributes };
return this.filteredValue(highlight);
}
public hasChanged(query:QueryResource) {

@ -48,7 +48,7 @@ import {QueryResource} from 'core-app/modules/hal/resources/query-resource';
import {HalResourceService} from 'core-app/modules/hal/services/hal-resource.service';
import {RelationResource} from 'core-app/modules/hal/resources/relation-resource';
export type RelationColumnType = 'toType' | 'ofType';
export type RelationColumnType = 'toType'|'ofType';
@Injectable()
export class WorkPackageTableRelationColumnsService extends WorkPackageTableBaseService<WorkPackageTableRelationColumns> {
@ -57,19 +57,15 @@ export class WorkPackageTableRelationColumnsService extends WorkPackageTableBase
public halResourceService:HalResourceService,
public wpCacheService:WorkPackageCacheService,
public wpRelations:WorkPackageRelationsService) {
super(querySpace);
super(querySpace);
}
public get state():InputState<WorkPackageTableRelationColumns> {
return this.querySpace.relationColumns;
}
public valueFromQuery(query:QueryResource):WorkPackageTableRelationColumns|undefined {
return undefined;
}
public initialize() {
this.initializeState();
public valueFromQuery(query:QueryResource):WorkPackageTableRelationColumns {
return {};
}
/**
@ -92,7 +88,7 @@ export class WorkPackageTableRelationColumnsService extends WorkPackageTableBase
}
// Only if the work package has anything expanded
const expanded = this.current.getExpandFor(workPackage.id);
const expanded = this.getExpandFor(workPackage.id);
if (expanded === undefined) {
return;
}
@ -145,7 +141,7 @@ export class WorkPackageTableRelationColumnsService extends WorkPackageTableBase
}
public relationColumnType(column:QueryColumn):RelationColumnType|null {
switch(column._type) {
switch (column._type) {
case queryColumnTypes.RELATION_TO_TYPE:
return 'toType';
case queryColumnTypes.RELATION_OF_TYPE:
@ -155,37 +151,29 @@ export class WorkPackageTableRelationColumnsService extends WorkPackageTableBase
}
}
public getExpandFor(workPackageId:string):string | undefined {
return this.current && this.current.getExpandFor(workPackageId);
public getExpandFor(workPackageId:string):string|undefined {
return this.current[workPackageId];
}
public expandFor(workPackageId:string, columnId:string) {
const current = this.current;
current.expandFor(workPackageId, columnId);
this.state.putValue(current);
public setExpandFor(workPackageId:string, columnId:string) {
this.state.doModify((value) => {
const update = { ...value };
update[workPackageId] = columnId;
return update;
});
}
public collapse(workPackageId:string) {
const current = this.current;
this.state.doModify((value:WorkPackageTableRelationColumns) => {
let update = {...value};
delete update[workPackageId];
current.collapse(workPackageId);
this.state.putValue(current);
return update;
});
}
public get current():WorkPackageTableRelationColumns {
return this.state.value!;
}
private initializeState() {
let current = this.current;
if (!current) {
current = new WorkPackageTableRelationColumns();
}
this.state.putValue(current);
return current;
return this.state.getValueOr({});
}
}

@ -30,33 +30,32 @@ import {States} from 'core-components/states.service';
import {combine, InputState} from 'reactivestates';
import {mapTo} from 'rxjs/operators';
import {QueryResource} from 'core-app/modules/hal/resources/query-resource';
import {WorkPackageTableSortBy} from '../wp-table-sort-by';
import {QueryColumn} from '../../wp-query/query-column';
import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space";
import {Injectable} from '@angular/core';
import {WorkPackageQueryStateService, WorkPackageTableBaseService} from './wp-table-base.service';
import {WorkPackageQueryStateService} from './wp-table-base.service';
import {Observable} from 'rxjs';
import {
QUERY_SORT_BY_ASC, QUERY_SORT_BY_DESC,
QUERY_SORT_BY_ASC,
QUERY_SORT_BY_DESC,
QuerySortByResource
} from 'core-app/modules/hal/resources/query-sort-by-resource';
import {cloneHalResourceCollection} from 'core-app/modules/hal/helpers/hal-resource-builder';
@Injectable()
export class WorkPackageTableSortByService extends WorkPackageTableBaseService<WorkPackageTableSortBy> implements WorkPackageQueryStateService {
export class WorkPackageTableSortByService extends WorkPackageQueryStateService<QuerySortByResource[]> {
constructor(readonly states:States,
readonly querySpace:IsolatedQuerySpace) {
constructor(protected readonly states:States,
protected readonly querySpace:IsolatedQuerySpace) {
super(querySpace);
}
public get state():InputState<WorkPackageTableSortBy> {
public get state():InputState<QuerySortByResource[]> {
return this.querySpace.sortBy;
}
public valueFromQuery(query:QueryResource) {
return new WorkPackageTableSortBy(query);
return [...query.sortBy];
}
public onReadyWithAvailable():Observable<null> {
@ -72,20 +71,17 @@ export class WorkPackageTableSortByService extends WorkPackageTableBaseService<W
return !_.isEqual(
comparer(query.sortBy),
comparer(this.current.current)
comparer(this.current)
);
}
public applyToQuery(query:QueryResource) {
if (this.current.current.length > 0) {
if (this.current.length > 0) {
// hierarchies and sort by are mutually exclusive
var hierarchies = this.querySpace.hierarchies.value!;
hierarchies.current = false;
hierarchies.last = null;
this.querySpace.hierarchies.putValue(hierarchies);
query.hierarchies = hierarchies.current;
let hierarchies = this.querySpace.hierarchies.value!;
this.querySpace.hierarchies.putValue({ ...hierarchies, isVisible: false, last: null });
}
query.sortBy = cloneHalResourceCollection<QuerySortByResource>(this.current.current);
query.sortBy = [...this.current];
return true;
}
@ -121,33 +117,17 @@ export class WorkPackageTableSortByService extends WorkPackageTableBaseService<W
}
public add(sortBy:QuerySortByResource) {
let currentState = this.current;
currentState.addCurrent(sortBy);
this.state.putValue(currentState);
}
public set(sortBys:QuerySortByResource[]) {
let currentState = this.current;
currentState.setCurrent(sortBys);
this.state.putValue(currentState);
this.state.doModify(current => current.concat(sortBy));
}
private get current():WorkPackageTableSortBy {
return this.state.value as WorkPackageTableSortBy;
public get current():QuerySortByResource[] {
return this.state.getValueOr([]);
}
private get availableState() {
return this.states.queries.sortBy;
}
public get currentSortBys():QuerySortByResource[] {
return this.current.current;
}
public get available():QuerySortByResource[] {
return this.availableState.getValueOr([]);
}

@ -27,32 +27,28 @@
// ++
import {InputState} from 'reactivestates';
import {WorkPackageQueryStateService, WorkPackageTableBaseService} from './wp-table-base.service';
import {WorkPackageQueryStateService} from './wp-table-base.service';
import {QueryResource} from 'core-app/modules/hal/resources/query-resource';
import {WorkPackageTableSum} from '../wp-table-sum';
import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space";
import {Injectable} from '@angular/core';
@Injectable()
export class WorkPackageTableSumService extends WorkPackageTableBaseService<WorkPackageTableSum> implements WorkPackageQueryStateService {
export class WorkPackageTableSumService extends WorkPackageQueryStateService<boolean> {
public constructor(querySpace:IsolatedQuerySpace) {
super(querySpace);
}
public get state():InputState<WorkPackageTableSum> {
public get state():InputState<boolean> {
return this.querySpace.sum;
}
public valueFromQuery(query:QueryResource) {
return new WorkPackageTableSum(query.sums);
return !!query.sums;
}
public initialize(query:QueryResource) {
let sum = new WorkPackageTableSum(query.sums);
this.state.putValue(sum);
this.state.putValue(!!query.sums);
}
public hasChanged(query:QueryResource) {
@ -65,33 +61,18 @@ export class WorkPackageTableSumService extends WorkPackageTableBaseService<Work
}
public toggle() {
let currentState = this.current;
currentState.toggle();
this.state.putValue(currentState);
this.state.putValue(!this.current);
}
public setEnabled(value:boolean) {
let currentState = this.current;
currentState.current = value;
this.state.putValue(currentState);
this.state.putValue(value);
}
public get isEnabled() {
return this.current.isEnabled;
}
private get current():WorkPackageTableSum {
return this.state.value as WorkPackageTableSum;
return this.current;
}
public get currentSum():boolean|undefined {
if (this.current) {
return this.current.current;
} else {
return undefined;
}
public get current():boolean {
return this.state.getValueOr(false);
}
}

@ -36,19 +36,23 @@ import {WorkPackageTableTimelineState} from './../wp-table-timeline';
import {WorkPackageQueryStateService, WorkPackageTableBaseService} from './wp-table-base.service';
@Injectable()
export class WorkPackageTableTimelineService extends WorkPackageTableBaseService<WorkPackageTableTimelineState> implements WorkPackageQueryStateService {
export class WorkPackageTableTimelineService extends WorkPackageQueryStateService<WorkPackageTableTimelineState> {
public constructor(querySpace:IsolatedQuerySpace) {
public constructor(protected readonly querySpace:IsolatedQuerySpace) {
super(querySpace);
}
public get state():InputState<WorkPackageTableTimelineState> {
return this.querySpace.timeline;
}
public valueFromQuery(query:QueryResource) {
return new WorkPackageTableTimelineState(query);
return {
...this.defaultState,
visible: query.timelineVisible,
zoomLevel: query.timelineZoomLevel,
labels: query.timelineLabels
};
}
public hasChanged(query:QueryResource) {
@ -69,18 +73,15 @@ export class WorkPackageTableTimelineService extends WorkPackageTableBaseService
public toggle() {
let currentState = this.current;
this.setVisible(!currentState.isVisible);
this.setVisible(!currentState.visible);
}
public setVisible(value:boolean) {
let currentState = this.current;
currentState.visible = value;
this.state.putValue(currentState);
this.state.putValue({...this.current, visible: value});
}
public get isVisible() {
return this.current.isVisible;
return this.current.visible;
}
public get zoomLevel() {
@ -89,20 +90,18 @@ export class WorkPackageTableTimelineService extends WorkPackageTableBaseService
public get labels() {
if (_.isEmpty(this.current.labels)) {
return this.current.defaultLabels;
return this.defaultLabels;
}
return this.current.labels;
}
public updateLabels(labels:TimelineLabels) {
let currentState = this.current;
currentState.labels = labels;
this.state.putValue(currentState);
this.modify({ labels: labels });
}
public getNormalizedLabels(workPackage:WorkPackageResource) {
let labels:TimelineLabels = _.clone(this.current.defaultLabels);
let labels:TimelineLabels = this.defaultLabels;
_.each(this.current.labels, (attribute:string | null, positionAsString:string) => {
// RR: Lodash typings declare the position as string. However, it is save to cast
@ -121,9 +120,7 @@ export class WorkPackageTableTimelineService extends WorkPackageTableBaseService
}
public setZoomLevel(level:TimelineZoomLevel) {
let currentState = this.current;
currentState.zoomLevel = level;
this.state.putValue(currentState);
this.modify({ zoomLevel: level });
}
public updateZoomWithDelta(delta:number) {
@ -140,9 +137,7 @@ export class WorkPackageTableTimelineService extends WorkPackageTableBaseService
}
public toggleAutoZoom(value = !this.current.autoZoom) {
let currentState = this.current;
currentState.autoZoom = value;
this.state.putValue(currentState);
this.modify({ autoZoom: value });
}
public isAutoZoomEnabled():boolean {
@ -150,6 +145,31 @@ export class WorkPackageTableTimelineService extends WorkPackageTableBaseService
}
public get current():WorkPackageTableTimelineState {
return this.state.value as WorkPackageTableTimelineState;
return this.state.getValueOr(this.defaultState);
}
/**
* Modify the state, updating with parts of properties
* @param update
*/
private modify(update:Partial<WorkPackageTableTimelineState>) {
this.update({ ...this.current, ...update });
}
private get defaultLabels():TimelineLabels {
return {
left: '',
right: '',
farRight: 'subject'
};
}
private get defaultState():WorkPackageTableTimelineState {
return {
autoZoom: true,
zoomLevel: 'days',
visible: false,
labels: this.defaultLabels
};
}
}

@ -15,7 +15,7 @@ import {PrimaryRenderPass, RenderedRow} from './builders/primary-render-pass';
import {WorkPackageTableEditingContext} from './wp-table-editing';
import {WorkPackageTableRow} from './wp-table.interfaces';
import {WorkPackageTableConfiguration, WorkPackageTableConfigurationObject} from 'core-app/components/wp-table/wp-table-configuration';
import {WorkPackageTableConfiguration} from 'core-app/components/wp-table/wp-table-configuration';
export class WorkPackageTable {

@ -1,31 +0,0 @@
// -- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
// ++
export abstract class WorkPackageTableBaseState<T> {
public current:T;
}

@ -1,116 +0,0 @@
// -- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {QueryFilterResource} from 'core-app/modules/hal/resources/query-filter-resource';
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource';
import {QueryFilterInstanceSchemaResource} from 'core-app/modules/hal/resources/query-filter-instance-schema-resource';
import {WorkPackageTableBaseState} from './wp-table-base';
import {cloneHalResourceCollection} from 'core-app/modules/hal/helpers/hal-resource-builder';
export class WorkPackageTableFilters extends WorkPackageTableBaseState<QueryFilterInstanceResource[]> {
public current:QueryFilterInstanceResource[] = [];
public hidden:string[] = [
'id',
'parent',
'datesInterval',
'precedes',
'follows',
'relates',
'duplicates',
'duplicated',
'blocks',
'blocked',
'partof',
'includes',
'requires',
'required',
'search',
'subjectOrId'
];
constructor(filters:QueryFilterInstanceResource[], public availableSchemas:QueryFilterInstanceSchemaResource[]) {
super();
this.current = filters;
}
public $copy() {
let filters = cloneHalResourceCollection<QueryFilterInstanceResource>(this.current);
let availableSchemas = cloneHalResourceCollection<QueryFilterInstanceSchemaResource>(this.availableSchemas);
return new WorkPackageTableFilters(filters, availableSchemas);
}
public add(filter:QueryFilterResource) {
let schema = _.find(this.availableSchemas,
schema => (schema.filter.allowedValues as QueryFilterResource[])[0].href === filter.href)!;
let newFilter = schema.getFilter();
this.current.push(newFilter);
return newFilter;
}
public remove(filter:QueryFilterInstanceResource) {
let index = this.current.indexOf(filter);
this.current.splice(index, 1);
}
public get remainingFilters() {
let activeFilterHrefs = this.currentFilters.map(filter => filter.href);
return _.remove(this.availableFilters, filter => activeFilterHrefs.indexOf(filter.href) === -1);
}
public get remainingVisibleFilters() {
return this.remainingFilters
.filter((filter) => this.hidden.indexOf(filter.id) === -1);
}
public isComplete():boolean {
return _.every(this.current, filter => filter.isCompletelyDefined());
}
public get currentlyVisibleFilters() {
const invisibleFilters = new Set(this.hidden);
invisibleFilters.delete('search');
return _.reject(this.currentFilters, (filter) => invisibleFilters.has(filter.id));
}
private get currentFilters() {
return this.current.map((filter:QueryFilterInstanceResource) => filter.filter);
}
public get availableFilters() {
return this.availableSchemas
.map(schema => (schema.filter.allowedValues as QueryFilterResource[])[0]);
}
}

@ -1,47 +0,0 @@
// -- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {QueryGroupByResource} from 'core-app/modules/hal/resources/query-group-by-resource';
import {QueryResource} from 'core-app/modules/hal/resources/query-resource';
import {WorkPackageTableBaseState} from './wp-table-base';
import {cloneHalResource} from 'core-app/modules/hal/helpers/hal-resource-builder';
export class WorkPackageTableGroupBy extends WorkPackageTableBaseState<QueryGroupByResource | undefined> {
public current:QueryGroupByResource | undefined;
constructor(query:QueryResource) {
super();
this.current = cloneHalResource<QueryGroupByResource>(query.groupBy);
}
public update(query:QueryResource|null) {
if (query) {
this.current = cloneHalResource<QueryGroupByResource>(query.groupBy);
}
}
}

@ -26,25 +26,12 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {WorkPackageTableBaseState} from './wp-table-base';
export class WorkPackageTableHierarchies {
public isVisible:boolean = false;
public last:string|null = null;
public collapsed:{[workPackageId:string]:boolean} = {};
export class WorkPackageTableHierarchies extends WorkPackageTableBaseState<boolean> {
public current:boolean;
public last:string|null;
public collapsed:{[workPackageId:string]:boolean};
constructor(isVisible:boolean) {
super();
this.current = isVisible;
this.last = null;
this.collapsed = {};
}
public toggle() {
this.current = !this.current;
}
public get isEnabled() {
return this.current;
constructor(visible:boolean) {
this.isVisible = visible;
}
}

@ -26,38 +26,10 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {QueryResource} from 'core-app/modules/hal/resources/query-resource';
import {HighlightingMode} from "core-components/wp-fast-table/builders/highlighting/highlighting-mode.const";
import {HalResource} from "core-app/modules/hal/resources/hal-resource";
export class WorkPackageTableHighlight {
private _selectedAttributes:HalResource[]|undefined;
constructor(public mode:HighlightingMode = 'inline',
selected:HalResource[]|undefined = undefined) {
this.selectedAttributes = selected;
}
public get selectedAttributes() {
return this._selectedAttributes;
}
public set selectedAttributes(val:HalResource[]|undefined) {
if (_.isEmpty(val)) {
this._selectedAttributes = undefined;
} else {
this._selectedAttributes = val;
}
}
public update(query:QueryResource|null) {
if (!query) {
this.mode = 'inline';
this.selectedAttributes = undefined;
return;
}
this.mode = query.highlightingMode;
this.selectedAttributes = query.selectedAttributes;
}
export interface WorkPackageTableHighlight {
mode:HighlightingMode;
selectedAttributes?:HalResource[];
}

@ -27,14 +27,12 @@
// ++
import {WorkPackageCollectionResource} from 'core-app/modules/hal/resources/wp-collection-resource'
import {WorkPackageTableBaseState} from './wp-table-base';
import {PaginationInstance} from 'core-components/table-pagination/pagination-instance';
export class WorkPackageTablePagination extends WorkPackageTableBaseState<PaginationInstance> {
export class WorkPackageTablePagination {
public current:PaginationInstance;
constructor(results:WorkPackageCollectionResource) {
super();
this.current = new PaginationInstance(results.offset, results.total, results.pageSize);
}

@ -26,27 +26,6 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {WorkPackageTableBaseState} from "./wp-table-base";
export interface RelationColumnStateValue {
export interface WorkPackageTableRelationColumns {
[workPackageId:string]:string;
}
export class WorkPackageTableRelationColumns extends WorkPackageTableBaseState<RelationColumnStateValue> {
constructor() {
super();
this.current = {};
}
public getExpandFor(workPackageId:string):string|undefined {
return this.current[workPackageId];
}
public expandFor(workPackageId:string, columnId:string) {
this.current[workPackageId] = columnId;
}
public collapse(workPackageId:string) {
delete this.current[workPackageId];
}
}

@ -1,59 +0,0 @@
// -- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {QueryResource} from 'core-app/modules/hal/resources/query-resource';
import {QuerySchemaResource} from 'core-app/modules/hal/resources/query-schema-resource';
import {WorkPackageTableBaseState} from './wp-table-base';
import {QueryColumn} from '../wp-query/query-column';
import {QuerySortByResource} from 'core-app/modules/hal/resources/query-sort-by-resource';
import {cloneHalResourceCollection} from 'core-app/modules/hal/helpers/hal-resource-builder';
export class WorkPackageTableSortBy extends WorkPackageTableBaseState<QuerySortByResource[]> {
public current:QuerySortByResource[] = [];
constructor(query:QueryResource) {
super();
this.current = cloneHalResourceCollection<QuerySortByResource>(query.sortBy);
}
public addCurrent(sortBy:QuerySortByResource) {
this.current.unshift(sortBy);
this.current = _.uniqBy(this.current,
sortBy => sortBy.column.$href)
.slice(0, 3);
}
public setCurrent(sortBys:QuerySortByResource[]) {
this.current = [];
_.reverse(sortBys);
_.each(sortBys, sortBy => this.addCurrent(sortBy));
}
}

@ -1,46 +0,0 @@
// -- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {WorkPackageTableBaseState} from "./wp-table-base";
export class WorkPackageTableSum extends WorkPackageTableBaseState<boolean> {
public current:boolean;
constructor(isSum:boolean) {
super();
this.current = isSum;
}
public toggle() {
this.current = !this.current;
}
public get isEnabled() {
return this.current;
}
}

@ -26,42 +26,14 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {WorkPackageTableBaseState} from './wp-table-base';
import {
QueryResource, TimelineLabels,
TimelineZoomLevel
} from 'core-app/modules/hal/resources/query-resource';
export class WorkPackageTableTimelineState extends WorkPackageTableBaseState<boolean> {
public autoZoom:boolean = true;
public visible:boolean;
public zoomLevel:TimelineZoomLevel;
public labels:TimelineLabels;
constructor(query:QueryResource) {
super();
this.visible = query.timelineVisible;
this.zoomLevel = query.timelineZoomLevel;
this.labels = query.timelineLabels;
}
public get current():boolean {
return this.isVisible;
}
public toggle() {
this.visible = !this.visible;
}
public get isVisible() {
return this.visible;
}
public get defaultLabels():TimelineLabels {
return {
left: '',
right: '',
farRight: 'subject'
};
}
export interface WorkPackageTableTimelineState {
autoZoom:boolean;
visible:boolean;
zoomLevel:TimelineZoomLevel;
labels:TimelineLabels;
}

@ -14,14 +14,6 @@ export interface WorkPackageTableRow {
group:GroupObject|null;
}
export interface GroupableColumn {
name:string;
title:string;
sortable:boolean;
groupable:boolean;
custom_field:boolean;
}
export interface WPTableRowSelectionState {
// Map of selected rows
selected:{[workPackageId:string]:boolean};

@ -104,7 +104,7 @@ export class WorkPackageStatesInitializationService {
this.wpTablePagination.initialize(query, results);
this.wpTableRelationColumns.initialize();
this.wpTableRelationColumns.initialize(query, results);
this.wpTableAdditionalElements.initialize(results.elements);
}

@ -1,10 +1,11 @@
import {Component, Inject, Injector} from '@angular/core';
import {I18nService} from 'core-app/modules/common/i18n/i18n.service';
import {TabComponent} from 'core-components/wp-table/configuration-modal/tab-portal-outlet';
import {WorkPackageTableGroupByService} from 'core-components/wp-fast-table/state/wp-table-group-by.service';
import {QueryGroupByResource} from 'core-app/modules/hal/resources/query-group-by-resource';
import {WorkPackageTableHierarchiesService} from 'core-components/wp-fast-table/state/wp-table-hierarchy.service';
import {WorkPackageTableSumService} from 'core-components/wp-fast-table/state/wp-table-sum.service';
import {Component, Injector} from "@angular/core";
@Component({
templateUrl: './display-settings-tab.component.html'
@ -15,7 +16,7 @@ export class WpTableConfigurationDisplaySettingsTab implements TabComponent {
public displayMode:'hierarchy'|'grouped'|'default' = 'default';
// Grouping
public currentGroup:QueryGroupByResource|undefined;
public currentGroup:QueryGroupByResource|null;
public availableGroups:QueryGroupByResource[] = [];
// Sums row display
@ -50,8 +51,8 @@ export class WpTableConfigurationDisplaySettingsTab implements TabComponent {
this.wpTableHierarchies.setEnabled(this.displayMode === 'hierarchy');
// Update grouping state
let group = this.displayMode === 'grouped' ? this.currentGroup : undefined;
this.wpTableGroupBy.set(group);
let group = this.displayMode === 'grouped' ? this.currentGroup : null;
this.wpTableGroupBy.update(group);
// Update sums state
this.wpTableSums.setEnabled(this.displaySums);
@ -59,7 +60,7 @@ export class WpTableConfigurationDisplaySettingsTab implements TabComponent {
public updateGroup(href:string) {
this.displayMode = 'grouped';
this.currentGroup = _.find(this.availableGroups, group => group.href === href);
this.currentGroup = _.find(this.availableGroups, group => group.href === href) || null;
}
ngOnInit() {
@ -69,7 +70,7 @@ export class WpTableConfigurationDisplaySettingsTab implements TabComponent {
this.displayMode = 'grouped';
}
this.displaySums = this.wpTableSums.currentSum || false;
this.displaySums = this.wpTableSums.current;
this.wpTableGroupBy
.onReady()

@ -1,16 +1,16 @@
import {Component, Inject, Injector} from '@angular/core';
import {Component, Injector} from '@angular/core';
import {I18nService} from 'core-app/modules/common/i18n/i18n.service';
import {TabComponent} from 'core-components/wp-table/configuration-modal/tab-portal-outlet';
import {WorkPackageFiltersService} from 'core-components/filters/wp-filters/wp-filters.service';
import {WorkPackageTableFiltersService} from 'core-components/wp-fast-table/state/wp-table-filters.service';
import {WorkPackageTableFilters} from 'core-components/wp-fast-table/wp-table-filters';
import {QueryFilterInstanceResource} from "core-app/modules/hal/resources/query-filter-instance-resource";
@Component({
templateUrl: './filters-tab.component.html'
})
export class WpTableConfigurationFiltersTab implements TabComponent {
public filters:WorkPackageTableFilters|undefined;
public filters:QueryFilterInstanceResource[] = [];
public eeShowBanners:boolean = false;
public text = {
@ -32,7 +32,7 @@ export class WpTableConfigurationFiltersTab implements TabComponent {
this.eeShowBanners = jQuery('body').hasClass('ee-banners-visible');
this.wpTableFilters
.onReady()
.then(() => this.filters = this.wpTableFilters.currentState.$copy());
.then(() => this.filters = this.wpTableFilters.current);
}
public onSave() {

@ -53,9 +53,7 @@ export class WpTableConfigurationHighlightingTab implements TabComponent {
public onSave() {
let mode = this.highlightingMode;
let highlightedAttributes:HalResource[] = this.selectedAttributesAsHal();
const newValue = new WorkPackageTableHighlight(mode, highlightedAttributes);
this.wpTableHighlight.update(newValue);
this.wpTableHighlight.update({ mode: mode, selectedAttributes: highlightedAttributes });
}
private selectedAttributesAsHal() {

@ -55,7 +55,7 @@ export class WpTableConfigurationSortByTab implements TabComponent {
.filter(object => object.column !== null)
.map(object => this.getMatchingSort(object.column.href!, object.direction));
this.wpTableSortBy.set(_.compact(sortElements));
this.wpTableSortBy.update(_.compact(sortElements));
}
ngOnInit() {
@ -79,7 +79,7 @@ export class WpTableConfigurationSortByTab implements TabComponent {
// QuerySortByResources are doubled for each column (one for asc/desc direction)
this.allColumns = _.uniqBy(allColumns, 'href');
_.each(this.wpTableSortBy.currentSortBys, sort => {
_.each(this.wpTableSortBy.current, sort => {
if (!sort.column.$href!.endsWith('/parent')) {
this.sortationObjects.push(
new SortModalObject({name: sort.column.name, href: sort.column.$href},

@ -64,13 +64,13 @@ export class WpTableConfigurationRelationSelectorComponent implements OnInit {
this.wpTableFilters
.onReady()
.then(() => {
self.availableRelationFilters = self.relationFiltersOf(self.wpTableFilters.currentState.availableFilters) as QueryFilterResource[];
self.availableRelationFilters = self.relationFiltersOf(self.wpTableFilters.availableFilters) as QueryFilterResource[];
self.setSelectedRelationFilter();
});
}
private setSelectedRelationFilter():void {
let currentRelationFilters:QueryFilterInstanceResource[] = this.relationFiltersOf(this.wpTableFilters.currentState.current) as QueryFilterInstanceResource[];
let currentRelationFilters:QueryFilterInstanceResource[] = this.relationFiltersOf(this.wpTableFilters.current) as QueryFilterInstanceResource[];
if (currentRelationFilters.length > 0) {
this.selectedRelationFilter = _.find(this.availableRelationFilters, { id: currentRelationFilters[0].id }) as QueryFilterResource;
} else {
@ -87,8 +87,8 @@ export class WpTableConfigurationRelationSelectorComponent implements OnInit {
}
private removeRelationFiltersFromCurrentState() {
let filtersToRemove:QueryFilterInstanceResource[] = this.relationFiltersOf(this.wpTableFilters.currentState.current) as QueryFilterInstanceResource[];
_.each(filtersToRemove, (filter) => this.wpTableFilters.currentState.remove(filter));
let filtersToRemove = this.relationFiltersOf(this.wpTableFilters.current) as QueryFilterInstanceResource[];
this.wpTableFilters.remove(...filtersToRemove);
}
private relationFiltersOf(filters:QueryFilterResource[]|QueryFilterInstanceResource[]):QueryFilterResource[]|QueryFilterInstanceResource[] {
@ -96,10 +96,12 @@ export class WpTableConfigurationRelationSelectorComponent implements OnInit {
}
private addFilterToCurrentState(filter:QueryFilterResource):void {
let addedFilter = this.wpTableFilters.currentState.add(filter);
let operator:QueryOperatorResource = this.getOperatorForId(addedFilter, '=');
addedFilter.operator = operator;
addedFilter.values = [{href: '/api/v3/work_packages/{id}'}] as HalResource[];
let newFilter = this.wpTableFilters.instantiate(filter);
let operator:QueryOperatorResource = this.getOperatorForId(newFilter, '=');
newFilter.operator = operator;
newFilter.values = [{href: '/api/v3/work_packages/{id}'}] as HalResource[];
this.wpTableFilters.add(newFilter);
}
private getOperatorForId(filter:QueryFilterResource, id:string):QueryOperatorResource {

@ -1,8 +1,6 @@
import {AfterViewInit, Component, EventEmitter, Input, OnDestroy, OnInit, Output} from '@angular/core';
import {WorkPackageTableTimelineService} from 'core-components/wp-fast-table/state/wp-table-timeline.service';
import {WorkPackageTablePaginationService} from 'core-components/wp-fast-table/state/wp-table-pagination.service';
import {withLatestFrom} from 'rxjs/operators';
import {untilComponentDestroyed} from 'ng2-rx-componentdestroyed';
import {OpTableActionFactory} from 'core-components/wp-table/table-actions/table-action';
import {OpTableActionsService} from 'core-components/wp-table/table-actions/table-actions.service';
import {QueryResource} from 'core-app/modules/hal/resources/query-resource';
@ -11,9 +9,9 @@ import {WorkPackageCollectionResource} from 'core-app/modules/hal/resources/wp-c
import {WpTableConfigurationModalComponent} from 'core-components/wp-table/configuration-modal/wp-table-configuration.modal';
import {OpModalService} from 'core-components/op-modals/op-modal.service';
import {WorkPackageEmbeddedBaseComponent} from "core-components/wp-table/embedded/wp-embedded-base.component";
import {WorkPackageTableFilters} from "core-components/wp-fast-table/wp-table-filters";
import {QueryFormResource} from "core-app/modules/hal/resources/query-form-resource";
import {QueryFormDmService} from "core-app/modules/hal/dm-services/query-form-dm.service";
import {QueryFilterInstanceResource} from "core-app/modules/hal/resources/query-filter-instance-resource";
@Component({
selector: 'wp-embedded-table',
@ -26,7 +24,7 @@ export class WorkPackageEmbeddedTableComponent extends WorkPackageEmbeddedBaseCo
@Input() public tableActions:OpTableActionFactory[] = [];
@Input() public externalHeight:boolean = false;
@Output() public onFiltersChanged = new EventEmitter<WorkPackageTableFilters>();
@Output() public onFiltersChanged = new EventEmitter<QueryFilterInstanceResource[]>();
readonly QueryDm:QueryDmService = this.injector.get(QueryDmService);
readonly opModalService:OpModalService = this.injector.get(OpModalService);

@ -102,7 +102,7 @@ export class SortHeaderDirective implements OnDestroy, AfterViewInit {
takeUntil(componentDestroyed(this))
)
.subscribe(() => {
let latestSortElement = this.wpTableSortBy.currentSortBys[0];
let latestSortElement = this.wpTableSortBy.current[0];
if (!latestSortElement || this.headerColumn.$href !== latestSortElement.column.$href) {
this.currentSortDirection = null;
@ -140,10 +140,8 @@ export class SortHeaderDirective implements OnDestroy, AfterViewInit {
this.isHierarchyDisabled = this.wpTableGroupBy.isEnabled;
// Disable hierarchy mode when group by is active
this.wpTableGroupBy.state.values$()
.pipe(
takeUntil(componentDestroyed(this))
)
this.wpTableGroupBy
.observeUntil(componentDestroyed(this))
.subscribe(() => {
this.isHierarchyDisabled = this.wpTableGroupBy.isEnabled;
});

@ -34,20 +34,7 @@ import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/iso
import * as moment from 'moment';
import {Moment} from 'moment';
import {componentDestroyed, untilComponentDestroyed} from 'ng2-rx-componentdestroyed';
import {debounceTime, delay, filter, map, take, takeUntil, throttleTime, withLatestFrom} from 'rxjs/operators';
import {debugLog, timeOutput} from '../../../../helpers/debug_output';
import {States} from '../../../states.service';
import {WorkPackageNotificationService} from '../../../wp-edit/wp-notification.service';
import {RenderedRow} from '../../../wp-fast-table/builders/primary-render-pass';
import {WorkPackageTableHierarchiesService} from '../../../wp-fast-table/state/wp-table-hierarchy.service';
import {WorkPackageTableTimelineService} from '../../../wp-fast-table/state/wp-table-timeline.service';
import {WorkPackageTable} from '../../../wp-fast-table/wp-fast-table';
import {WorkPackageTableTimelineState} from '../../../wp-fast-table/wp-table-timeline';
import {WorkPackageRelationsService} from '../../../wp-relations/wp-relations.service';
import {selectorTimelineSide} from '../../wp-table-scroll-sync';
import {WorkPackagesTableController} from '../../wp-table.directive';
import {WorkPackageTimelineCell} from '../cells/wp-timeline-cell';
import {WorkPackageTimelineCellsRenderer} from '../cells/wp-timeline-cells-renderer';
import {debounceTime, filter, map, takeUntil, withLatestFrom} from 'rxjs/operators';
import {
calculateDaySpan,
getPixelPerDayForZoomLevel,
@ -59,7 +46,19 @@ import {
} from '../wp-timeline';
import {DynamicCssService} from "core-app/modules/common/dynamic-css/dynamic-css.service";
import {input, InputState} from "reactivestates";
import {Subject} from "rxjs";
import {WorkPackageTable} from "core-components/wp-fast-table/wp-fast-table";
import {RenderedRow} from "core-components/wp-fast-table/builders/primary-render-pass";
import {WorkPackageTimelineCellsRenderer} from "core-components/wp-table/timeline/cells/wp-timeline-cells-renderer";
import {States} from "core-components/states.service";
import {WorkPackagesTableController} from "core-components/wp-table/wp-table.directive";
import {WorkPackageTableTimelineService} from "core-components/wp-fast-table/state/wp-table-timeline.service";
import {WorkPackageNotificationService} from "core-components/wp-edit/wp-notification.service";
import {WorkPackageRelationsService} from "core-components/wp-relations/wp-relations.service";
import {WorkPackageTableHierarchiesService} from "core-components/wp-fast-table/state/wp-table-hierarchy.service";
import {WorkPackageTableTimelineState} from "core-components/wp-fast-table/wp-table-timeline";
import {WorkPackageTimelineCell} from "core-components/wp-table/timeline/cells/wp-timeline-cell";
import {selectorTimelineSide} from "core-components/wp-table/wp-table-scroll-sync";
import {debugLog, timeOutput} from "core-app/helpers/debug_output";
@Component({
selector: 'wp-timeline-container',
@ -106,7 +105,6 @@ export class WorkPackageTimelineTableController implements AfterViewInit, OnDest
private wpRelations:WorkPackageRelationsService,
private wpTableHierarchies:WorkPackageTableHierarchiesService,
readonly I18n:I18nService) {
'ngInject';
}
ngAfterViewInit() {
@ -150,7 +148,7 @@ export class WorkPackageTimelineTableController implements AfterViewInit, OnDest
// Refresh timeline view when becoming visible
this.querySpace.timeline.values$()
.pipe(
filter((timelineState:WorkPackageTableTimelineState) => timelineState.isVisible),
filter((timelineState:WorkPackageTableTimelineState) => timelineState.visible),
takeUntil(componentDestroyed(this))
)
.subscribe((timelineState:WorkPackageTableTimelineState) => {
@ -251,7 +249,7 @@ export class WorkPackageTimelineTableController implements AfterViewInit, OnDest
.pipe(
withLatestFrom(this.querySpace.timeline.values$()),
takeUntil(componentDestroyed(this)),
filter(([, timelineState]) => this.initialized && timelineState.isVisible),
filter(([, timelineState]) => this.initialized && timelineState.visible),
map(([[wpId]]) => wpId),
filter((wpId) => this.cellsRenderer.hasCell(wpId))
)

@ -126,7 +126,7 @@ export class WorkPackageTableTimelineRelations implements OnInit, OnDestroy {
this.querySpace.timeline.values$()
)
.pipe(
filter(([rendered, timeline]) => timeline.isVisible),
filter(([rendered, timeline]) => timeline.visible),
takeUntil(componentDestroyed(this)),
map(([rendered, _]) => rendered)
)

@ -73,7 +73,7 @@ export class WorkPackageTableSumsRowController implements AfterViewInit {
takeUntil(this.querySpace.stopAllSubscriptions)
)
.subscribe(([columns, resource, sum]) => {
if (sum.isEnabled && resource.sumsSchema) {
if (sum && resource.sumsSchema) {
resource.sumsSchema.$load().then((schema:SchemaResource) => {
this.refresh(columns, resource, schema);
});

@ -39,21 +39,16 @@
</th>
</tr>
</thead>
<tbody class="work-package--empty-tbody" *ngIf="!isEmbedded && (query.hasError || rowcount === 0)">
<tbody class="work-package--empty-tbody" *ngIf="!isEmbedded && query && rowcount === 0">
<tr id="empty-row-notification">
<td [attr.colspan]="columns.length + 1">
<span *ngIf="!query.hasError">
<span>
<op-icon icon-classes="icon-info1 icon-context"></op-icon>
<span>
<strong>{{text.noResults.title}}</strong>
{{text.noResults.description}}
</span>
</span>
<span *ngIf="query.hasError">
<op-icon icon-classes="wp-table--faulty-query-icon icon-warning icon-context"></op-icon>
<strong>{{text.faultyQuery.title}}</strong>
<span>{{text.faultyQuery.description}}</span>
</span>
</td>
</tr>
</tbody>

@ -96,7 +96,7 @@ export class WorkPackagesTableController implements OnInit, OnDestroy {
public rowcount:number;
public groupBy:QueryGroupByResource | undefined;
public groupBy:QueryGroupByResource | null;
public columns:QueryColumn[];
@ -104,8 +104,6 @@ export class WorkPackagesTableController implements OnInit, OnDestroy {
public timelineVisible:boolean;
public readonly uniqueTableIdentifier = `wp-table--container-${randomString(16)}`;
constructor(readonly elementRef:ElementRef,
readonly injector:Injector,
readonly states:States,
@ -135,10 +133,6 @@ export class WorkPackagesTableController implements OnInit, OnDestroy {
title: I18n.t('js.work_packages.no_results.title'),
description: I18n.t('js.work_packages.no_results.description')
},
faultyQuery: {
title: I18n.t('js.work_packages.faulty_query.title'),
description: I18n.t('js.work_packages.faulty_query.description')
},
configureTable: I18n.t('js.toolbar.settings.configure_view'),
tableSummary: I18n.t('js.work_packages.table.summary'),
tableSummaryHints: [
@ -160,15 +154,15 @@ export class WorkPackagesTableController implements OnInit, OnDestroy {
this.query = this.querySpace.query.value!;
this.rowcount = results.count;
this.groupBy = groupBy.current;
this.groupBy = groupBy;
this.columns = columns;
// Total columns = all available columns + id + checkbox
this.numTableColumns = this.columns.length + 2;
if (this.timelineVisible !== timelines.current) {
this.scrollSyncUpdate(timelines.current);
if (this.timelineVisible !== timelines.visible) {
this.scrollSyncUpdate(timelines.visible);
}
this.timelineVisible = timelines.current;
this.timelineVisible = timelines.visible;
});
// Locate table and timeline elements

@ -109,7 +109,7 @@ export class AttachmentListItemComponent {
}
_.pull(this.resource.attachments.elements, this.attachment);
this.states.forResource(this.resource!).putValue(this.resource);
this.states.forResource(this.resource!)!.putValue(this.resource);
if (!!this.selfDestroy) {
this

@ -91,7 +91,7 @@ export class AttachmentsComponent implements OnInit, OnDestroy {
}
public setupResourceUpdateListener() {
this.states.forResource(this.resource).changes$()
this.states.forResource(this.resource)!.changes$()
.pipe(
takeUntil(componentDestroyed(this)),
filter(newResource => !!newResource)

@ -16,6 +16,7 @@ import {I18nService} from "core-app/modules/common/i18n/i18n.service";
import {NotificationsService} from "core-app/modules/common/notifications/notifications.service";
import {DomSanitizer} from "@angular/platform-browser";
import {WorkPackagesListChecksumService} from "core-components/wp-list/wp-list-checksum.service";
import {QueryFilterInstanceResource} from "core-app/modules/hal/resources/query-filter-instance-resource";
@Component({
templateUrl: './wp-calendar.template.html',
@ -59,10 +60,11 @@ export class WorkPackagesCalendarController implements OnInit, OnDestroy {
let calendarView = this.calendarElement.fullCalendar('getView')!;
let startDate = (calendarView.start as Moment).format('YYYY-MM-DD');
let endDate = (calendarView.end as Moment).format('YYYY-MM-DD');
let filtersEmpty = this.wpTableFilters.isEmpty;
if (!this.wpTableFilters.currentState && this.querySpace.query.value) {
if (filtersEmpty && this.querySpace.query.value) {
// nothing to do
} else if (!this.wpTableFilters.currentState) {
} else if (filtersEmpty) {
let queryProps = this.defaultQueryProps(startDate, endDate);
if (this.$state.params.query_props) {
@ -72,14 +74,11 @@ export class WorkPackagesCalendarController implements OnInit, OnDestroy {
this.wpListService.fromQueryParams({ query_props: queryProps }, this.projectIdentifier).toPromise();
} else {
let params = this.$state.params;
let filtersState = this.wpTableFilters.currentState;
let datesIntervalFilter = _.find(filtersState.current, {'id': 'datesInterval'}) as any;
datesIntervalFilter.values[0] = startDate;
datesIntervalFilter.values[1] = endDate;
this.wpTableFilters.replace(filtersState);
this.wpTableFilters.modify('datesInterval', (datesIntervalFilter) => {
datesIntervalFilter.values[0] = startDate;
datesIntervalFilter.values[1] = endDate;
});
}
}

@ -33,8 +33,8 @@ import {
ElementRef,
OnDestroy,
OnInit,
Renderer2,
ViewChild
Query,
Renderer2
} from '@angular/core';
import {FocusHelperService} from 'app/modules/common/focus/focus-helper';
import {I18nService} from 'app/modules/common/i18n/i18n.service';
@ -42,14 +42,14 @@ import {DynamicBootstrapper} from "app/globals/dynamic-bootstrapper";
import {HalResourceService} from "app/modules/hal/services/hal-resource.service";
import {GlobalSearchService} from "app/modules/global_search/global-search.service";
import {untilComponentDestroyed} from "ng2-rx-componentdestroyed";
import {WorkPackageTableFilters} from "app/components/wp-fast-table/wp-table-filters";
import {QueryResource} from "app/modules/hal/resources/query-resource";
import {WorkPackageFiltersService} from "app/components/filters/wp-filters/wp-filters.service";
import {UrlParamsHelperService} from "app/components/wp-query/url-params-helper";
import {WorkPackageTableConfigurationObject} from "core-components/wp-table/wp-table-configuration";
import {WorkPackageIsolatedQuerySpaceDirective} from "core-app/modules/work_packages/query-space/wp-isolated-query-space.directive";
import {cloneHalResource} from "core-app/modules/hal/helpers/hal-resource-builder";
import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space";
import {QueryFilterInstanceResource} from "core-app/modules/hal/resources/query-filter-instance-resource";
import {WorkPackageTableFiltersService} from "core-components/wp-fast-table/state/wp-table-filters.service";
export const globalSearchWorkPackagesSelector = 'global-search-work-packages';
@ -65,7 +65,6 @@ export const globalSearchWorkPackagesSelector = 'global-search-work-packages';
})
export class GlobalSearchWorkPackagesComponent implements OnInit, OnDestroy, AfterViewInit {
public filters:WorkPackageTableFilters;
public queryProps:{ [key:string]:any };
public resultsHidden = false;
@ -85,6 +84,7 @@ export class GlobalSearchWorkPackagesComponent implements OnInit, OnDestroy, Aft
readonly I18n:I18nService,
readonly halResourceService:HalResourceService,
readonly globalSearchService:GlobalSearchService,
readonly wpTableFilters:WorkPackageTableFiltersService,
readonly querySpace:IsolatedQuerySpace,
readonly wpFilters:WorkPackageFiltersService,
readonly cdRef:ChangeDetectorRef,
@ -125,10 +125,10 @@ export class GlobalSearchWorkPackagesComponent implements OnInit, OnDestroy, Aft
// Nothing to do
}
public onFiltersChanged(filters:WorkPackageTableFilters) {
if (filters.isComplete()) {
public onFiltersChanged(filters:QueryFilterInstanceResource[]) {
if (this.wpTableFilters.isComplete(filters)) {
const query = cloneHalResource(this.querySpace.query.value!) as QueryResource;
query.filters = filters.current;
query.filters = filters;
this.queryProps = this.UrlParamsHelper.buildV3GetQueryFromQueryResource(query);
}
}

@ -1,11 +1,7 @@
import {RenderedRow} from 'app/components/wp-fast-table/builders/primary-render-pass';
import {WorkPackageTableFilters} from 'app/components/wp-fast-table/wp-table-filters';
import {WorkPackageTableGroupBy} from 'app/components/wp-fast-table/wp-table-group-by';
import {WorkPackageTableHierarchies} from 'app/components/wp-fast-table/wp-table-hierarchies';
import {WorkPackageTablePagination} from 'app/components/wp-fast-table/wp-table-pagination';
import {WorkPackageTableRelationColumns} from 'app/components/wp-fast-table/wp-table-relation-columns';
import {WorkPackageTableSortBy} from 'app/components/wp-fast-table/wp-table-sort-by';
import {WorkPackageTableSum} from 'app/components/wp-fast-table/wp-table-sum';
import {WorkPackageTableTimelineState} from 'app/components/wp-fast-table/wp-table-timeline';
import {WPTableRowSelectionState} from 'app/components/wp-fast-table/wp-table.interfaces';
import {combine, derive, DerivedState, input, InputState, State, StatesGroup} from 'reactivestates';
@ -20,6 +16,9 @@ import {WorkPackageTableHighlight} from "core-components/wp-fast-table/wp-table-
import {QueryFormResource} from "core-app/modules/hal/resources/query-form-resource";
import {WPFocusState} from "core-components/wp-fast-table/state/wp-table-focus.service";
import {QueryColumn} from "core-components/wp-query/query-column";
import {QueryFilterInstanceResource} from "core-app/modules/hal/resources/query-filter-instance-resource";
import {QueryGroupByResource} from "core-app/modules/hal/resources/query-group-by-resource";
import {QuerySortByResource} from "core-app/modules/hal/resources/query-sort-by-resource";
@Injectable()
export class IsolatedQuerySpace extends StatesGroup {
@ -43,16 +42,16 @@ export class IsolatedQuerySpace extends StatesGroup {
// all groups returned as results
groups = input<GroupObject[]>();
// Set of columns in strict order of appearance
columns = input<QueryColumn[]>();
columns = input<QueryColumn[]>();
// Set of filters
filters = input<WorkPackageTableFilters>();
filters = input<QueryFilterInstanceResource[]>();
// Active and available sort by
sortBy = input<WorkPackageTableSortBy>();
sortBy = input<QuerySortByResource[]>();
// Active and available group by
groupBy = input<WorkPackageTableGroupBy>();
groupBy = input<QueryGroupByResource|null>();
// is query summed
sum = input<WorkPackageTableSum>();
sum = input<boolean>();
// pagination information
pagination = input<WorkPackageTablePagination>();
// Table row selection state
@ -105,13 +104,13 @@ export class TableRenderingStates {
this.querySpace.rows,
this.querySpace.columns,
this.querySpace.sum,
this.querySpace.groupBy,
this.querySpace.sortBy,
this.querySpace.groupBy,
this.querySpace.additionalRequiredWorkPackages
);
onQueryUpdated:DerivedState<[WorkPackageResource[], QueryColumn[], WorkPackageTableSum, WorkPackageTableGroupBy, WorkPackageTableSortBy, null], [undefined], null, undefined> =
derive(this.combinedquerySpaces, ($,) => $.pipe(mapTo(null)));
onQueryUpdated:DerivedState<unknown[], [undefined], null, undefined> =
derive(this.combinedquerySpaces, ($, ) => $.pipe(mapTo(null)));
}
export class UserUpdaterStates {

@ -120,11 +120,11 @@ export abstract class WorkPackagesViewBase implements OnInit, OnDestroy {
* @param service Work package query state service to listento
* @param firstPage If the service requests a change, load the first page
*/
protected setupChangeObserver(service:WorkPackageQueryStateService, firstPage:boolean = false) {
protected setupChangeObserver(service:WorkPackageQueryStateService<unknown>, firstPage:boolean = false) {
const queryState = this.querySpace.query;
this.querySpace.ready
.fireOnStateChange(service.state, 'Query loaded')
.fireOnStateChange(service.readonlyState, 'Query loaded')
.values$()
.pipe(
untilComponentDestroyed(this),

Loading…
Cancel
Save