diff --git a/frontend/src/app/ckeditor/ckeditor-augmented-textarea.component.ts b/frontend/src/app/ckeditor/ckeditor-augmented-textarea.component.ts index 4cfa91f21e..9c8cdab241 100644 --- a/frontend/src/app/ckeditor/ckeditor-augmented-textarea.component.ts +++ b/frontend/src/app/ckeditor/ckeditor-augmented-textarea.component.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) diff --git a/frontend/src/app/components/filters/filter-container/filter-container.directive.ts b/frontend/src/app/components/filters/filter-container/filter-container.directive.ts index b3d2ef65ec..4f101b597a 100644 --- a/frontend/src/app/components/filters/filter-container/filter-container.directive.ts +++ b/frontend/src/app/components/filters/filter-container/filter-container.directive.ts @@ -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(componentDestroyed(this)); + @Output() public filtersChanged = new DebouncedEventEmitter(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); } diff --git a/frontend/src/app/components/filters/query-filters/query-filters.component.html b/frontend/src/app/components/filters/query-filters/query-filters.component.html index 7d1ed00d6c..2c37c0ca94 100644 --- a/frontend/src/app/components/filters/query-filters/query-filters.component.html +++ b/frontend/src/app/components/filters/query-filters/query-filters.component.html @@ -1,4 +1,4 @@ -
+
- +
  • (componentDestroyed(this)); + @Output() public filtersChanged = new DebouncedEventEmitter(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]; } } diff --git a/frontend/src/app/components/filters/quick-filter-by-text-input/quick-filter-by-text-input.component.ts b/frontend/src/app/components/filters/quick-filter-by-text-input/quick-filter-by-text-input.component.ts index ba67b05cdd..9d71a82f12 100644 --- a/frontend/src/app/components/filters/quick-filter-by-text-input/quick-filter-by-text-input.component.ts +++ b/frontend/src/app/components/filters/quick-filter-by-text-input/quick-filter-by-text-input.component.ts @@ -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(componentDestroyed(this)); + @Output() public filterChanged = new DebouncedEventEmitter(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')!; }); } diff --git a/frontend/src/app/components/states.service.ts b/frontend/src/app/components/states.service.ts index 302a12a3a8..8585a63f53 100644 --- a/frontend/src/app/components/states.service.ts +++ b/frontend/src/app/components/states.service.ts @@ -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(); - /* /api/v3/wiki_pages */ - wikiPages = multiInput(); - /* /api/v3/wiki_pages */ posts = multiInput(); @@ -37,7 +34,7 @@ export class States extends StatesGroup { /* /api/v3/types */ types = multiInput(); - /* /api/v3/types */ + /* /api/v3/statuses */ statuses = multiInput(); /* /api/v3/users */ @@ -49,20 +46,28 @@ export class States extends StatesGroup { // Global events to isolated changes changes = new GlobalStateChanges(); - forResource(resource:HalResource):InputState { - let stateName = _.camelCase(resource._type) + 's'; + // Additional state map that can be dynamically registered. + additional:{[id:string]:MultiInputState} = {}; + + forResource(resource:HalResource):InputState|undefined { + const stateName = _.camelCase(resource._type) + 's'; + let state = this.additional[stateName]; + + if (!state) { + state = this.additional[stateName] = multiInput(); + } - return this[stateName].get(resource.id); + return state && state.get(resource.id); } public add(name:string, state:MultiInputState) { - this[name] = state; + this.additional[name] = state; } } export class GlobalStateChanges { // Global subject on changes to the given query ID - queries = new Subject(); + queries = new Subject(); } export class QueryAvailableDataStates { @@ -75,6 +80,6 @@ export class QueryAvailableDataStates { // Available GroupBy columns groupBy = input(); - // 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(); } diff --git a/frontend/src/app/components/wp-buttons/wp-filter-button/wp-filter-button.component.ts b/frontend/src/app/components/wp-buttons/wp-filter-button/wp-filter-button.component.ts index de2d5fc9ab..286fbb9a80 100644 --- a/frontend/src/app/components/wp-buttons/wp-filter-button/wp-filter-button.component.ts +++ b/frontend/src/app/components/wp-buttons/wp-filter-button/wp-filter-button.component.ts @@ -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; }); } diff --git a/frontend/src/app/components/wp-fast-table/handlers/cell/relations-cell-handler.ts b/frontend/src/app/components/wp-fast-table/handlers/cell/relations-cell-handler.ts index fff5a62221..3d5d6ca299 100644 --- a/frontend/src/app/components/wp-fast-table/handlers/cell/relations-cell-handler.ts +++ b/frontend/src/app/components/wp-fast-table/handlers/cell/relations-cell-handler.ts @@ -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; diff --git a/frontend/src/app/components/wp-fast-table/handlers/state/hierarchy-transformer.ts b/frontend/src/app/components/wp-fast-table/handlers/state/hierarchy-transformer.ts index 4dd005d066..8528f329f3 100644 --- a/frontend/src/app/components/wp-fast-table/handlers/state/hierarchy-transformer.ts +++ b/frontend/src/app/components/wp-fast-table/handlers/state/hierarchy-transformer.ts @@ -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; }); } diff --git a/frontend/src/app/components/wp-fast-table/handlers/state/timeline-transformer.ts b/frontend/src/app/components/wp-fast-table/handlers/state/timeline-transformer.ts index d0daff460c..89df239c5d 100644 --- a/frontend/src/app/components/wp-fast-table/handlers/state/timeline-transformer.ts +++ b/frontend/src/app/components/wp-fast-table/handlers/state/timeline-transformer.ts @@ -16,7 +16,7 @@ export class TimelineTransformer { takeUntil(this.querySpace.stopAllSubscriptions) ) .subscribe((state:WorkPackageTableTimelineState) => { - this.renderVisibility(state.isVisible); + this.renderVisibility(state.visible); }); } diff --git a/frontend/src/app/components/wp-fast-table/state/wp-table-base.service.ts b/frontend/src/app/components/wp-fast-table/state/wp-table-base.service.ts index 507e770808..4ac496ea7b 100644 --- a/frontend/src/app/components/wp-fast-table/state/wp-table-base.service.ts +++ b/frontend/src/app/components/wp-fast-table/state/wp-table-base.service.ts @@ -36,15 +36,9 @@ import {WorkPackageCollectionResource} from 'core-app/modules/hal/resources/wp-c export abstract class WorkPackageTableBaseService { - constructor(readonly querySpace:IsolatedQuerySpace) { + constructor(protected readonly querySpace:IsolatedQuerySpace) { } - /** - * Return the state this service cares for from the table state. - * @returns {InputState} - */ - public abstract get state():InputState; - /** * Get the state value from the current query. * @@ -84,21 +78,52 @@ export abstract class WorkPackageTableBaseService { ) .toPromise(); } + + + /** + * Return the state this service cares for from the table state. + * @returns {InputState} + */ + protected abstract get state():InputState; + + /** + * Return a public read-only state + */ +public get readonlyState():State { + 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 extends WorkPackageTableBaseService { /** * 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; + abstract applyToQuery(query:QueryResource):boolean; } diff --git a/frontend/src/app/components/wp-fast-table/state/wp-table-columns.service.ts b/frontend/src/app/components/wp-fast-table/state/wp-table-columns.service.ts index d622ad40e4..105387bfd9 100644 --- a/frontend/src/app/components/wp-fast-table/state/wp-table-columns.service.ts +++ b/frontend/src/app/components/wp-fast-table/state/wp-table-columns.service.ts @@ -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 implements WorkPackageQueryStateService { +export class WorkPackageTableColumnsService extends WorkPackageQueryStateService { 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[]) { diff --git a/frontend/src/app/components/wp-fast-table/state/wp-table-filters.service.ts b/frontend/src/app/components/wp-fast-table/state/wp-table-filters.service.ts index cb1d67c692..44c7bea7b5 100644 --- a/frontend/src/app/components/wp-fast-table/state/wp-table-filters.service.ts +++ b/frontend/src/app/components/wp-fast-table/state/wp-table-filters.service.ts @@ -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 implements WorkPackageQueryStateService { +export class WorkPackageTableFiltersService extends WorkPackageQueryStateService { + public hidden:Readonly = [ + '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 { + protected get state():InputState { 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()); + let filters = cloneHalResourceCollection(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 { + 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(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(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(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 { + 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 { return Promise.all(filters.map((filter:QueryFilterInstanceResource) => filter.schema.$load())); } } diff --git a/frontend/src/app/components/wp-fast-table/state/wp-table-group-by.service.ts b/frontend/src/app/components/wp-fast-table/state/wp-table-group-by.service.ts index a18c32aed1..73fbe6d047 100644 --- a/frontend/src/app/components/wp-fast-table/state/wp-table-group-by.service.ts +++ b/frontend/src/app/components/wp-fast-table/state/wp-table-group-by.service.ts @@ -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 implements WorkPackageQueryStateService { +export class WorkPackageTableGroupByService extends WorkPackageQueryStateService { public constructor(readonly states:States, readonly querySpace:IsolatedQuerySpace) { super(querySpace); } - public get state():InputState { + public get state():InputState { 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(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; } diff --git a/frontend/src/app/components/wp-fast-table/state/wp-table-hierarchy.service.ts b/frontend/src/app/components/wp-fast-table/state/wp-table-hierarchy.service.ts index 859477e6d3..b9bb08abba 100644 --- a/frontend/src/app/components/wp-fast-table/state/wp-table-hierarchy.service.ts +++ b/frontend/src/app/components/wp-fast-table/state/wp-table-hierarchy.service.ts @@ -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 implements WorkPackageQueryStateService { - public constructor(querySpace:IsolatedQuerySpace) { +export class WorkPackageTableHierarchiesService extends WorkPackageQueryStateService { + 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); } /** diff --git a/frontend/src/app/components/wp-fast-table/state/wp-table-highlighting.service.ts b/frontend/src/app/components/wp-fast-table/state/wp-table-highlighting.service.ts index 3cec8b9de2..5a79206fab 100644 --- a/frontend/src/app/components/wp-fast-table/state/wp-table-highlighting.service.ts +++ b/frontend/src/app/components/wp-fast-table/state/wp-table-highlighting.service.ts @@ -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 implements WorkPackageQueryStateService { +export class WorkPackageTableHighlightingService extends WorkPackageQueryStateService{ 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) { diff --git a/frontend/src/app/components/wp-fast-table/state/wp-table-relation-columns.service.ts b/frontend/src/app/components/wp-fast-table/state/wp-table-relation-columns.service.ts index b58f5b743b..813eb5022e 100644 --- a/frontend/src/app/components/wp-fast-table/state/wp-table-relation-columns.service.ts +++ b/frontend/src/app/components/wp-fast-table/state/wp-table-relation-columns.service.ts @@ -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 { @@ -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 { 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({}); } } diff --git a/frontend/src/app/components/wp-fast-table/state/wp-table-sort-by.service.ts b/frontend/src/app/components/wp-fast-table/state/wp-table-sort-by.service.ts index a4c5088cd2..187821d35c 100644 --- a/frontend/src/app/components/wp-fast-table/state/wp-table-sort-by.service.ts +++ b/frontend/src/app/components/wp-fast-table/state/wp-table-sort-by.service.ts @@ -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 implements WorkPackageQueryStateService { +export class WorkPackageTableSortByService extends WorkPackageQueryStateService { - constructor(readonly states:States, - readonly querySpace:IsolatedQuerySpace) { + constructor(protected readonly states:States, + protected readonly querySpace:IsolatedQuerySpace) { super(querySpace); } - public get state():InputState { + public get state():InputState { return this.querySpace.sortBy; } public valueFromQuery(query:QueryResource) { - return new WorkPackageTableSortBy(query); + return [...query.sortBy]; } public onReadyWithAvailable():Observable { @@ -72,20 +71,17 @@ export class WorkPackageTableSortByService extends WorkPackageTableBaseService 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(this.current.current); + query.sortBy = [...this.current]; return true; } @@ -121,33 +117,17 @@ export class WorkPackageTableSortByService extends WorkPackageTableBaseService 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([]); } diff --git a/frontend/src/app/components/wp-fast-table/state/wp-table-sum.service.ts b/frontend/src/app/components/wp-fast-table/state/wp-table-sum.service.ts index 9e65643df3..4d4d7c9a32 100644 --- a/frontend/src/app/components/wp-fast-table/state/wp-table-sum.service.ts +++ b/frontend/src/app/components/wp-fast-table/state/wp-table-sum.service.ts @@ -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 implements WorkPackageQueryStateService { +export class WorkPackageTableSumService extends WorkPackageQueryStateService { public constructor(querySpace:IsolatedQuerySpace) { super(querySpace); } - - public get state():InputState { + public get state():InputState { 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 implements WorkPackageQueryStateService { +export class WorkPackageTableTimelineService extends WorkPackageQueryStateService { - public constructor(querySpace:IsolatedQuerySpace) { + public constructor(protected readonly querySpace:IsolatedQuerySpace) { super(querySpace); } - public get state():InputState { 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) { + 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 + }; } } diff --git a/frontend/src/app/components/wp-fast-table/wp-fast-table.ts b/frontend/src/app/components/wp-fast-table/wp-fast-table.ts index e8860f6a17..3427e536ad 100644 --- a/frontend/src/app/components/wp-fast-table/wp-fast-table.ts +++ b/frontend/src/app/components/wp-fast-table/wp-fast-table.ts @@ -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 { diff --git a/frontend/src/app/components/wp-fast-table/wp-table-base.ts b/frontend/src/app/components/wp-fast-table/wp-table-base.ts deleted file mode 100644 index c8c70e7e7e..0000000000 --- a/frontend/src/app/components/wp-fast-table/wp-table-base.ts +++ /dev/null @@ -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 { - public current:T; -} diff --git a/frontend/src/app/components/wp-fast-table/wp-table-filters.ts b/frontend/src/app/components/wp-fast-table/wp-table-filters.ts deleted file mode 100644 index f38ace3d40..0000000000 --- a/frontend/src/app/components/wp-fast-table/wp-table-filters.ts +++ /dev/null @@ -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 { - - 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(this.current); - let availableSchemas = cloneHalResourceCollection(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]); - } -} diff --git a/frontend/src/app/components/wp-fast-table/wp-table-group-by.ts b/frontend/src/app/components/wp-fast-table/wp-table-group-by.ts deleted file mode 100644 index 0b9a2066a4..0000000000 --- a/frontend/src/app/components/wp-fast-table/wp-table-group-by.ts +++ /dev/null @@ -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 { - public current:QueryGroupByResource | undefined; - - constructor(query:QueryResource) { - super(); - this.current = cloneHalResource(query.groupBy); - } - - public update(query:QueryResource|null) { - if (query) { - this.current = cloneHalResource(query.groupBy); - } - } -} diff --git a/frontend/src/app/components/wp-fast-table/wp-table-hierarchies.ts b/frontend/src/app/components/wp-fast-table/wp-table-hierarchies.ts index 59584f1ce2..01b5f8275a 100644 --- a/frontend/src/app/components/wp-fast-table/wp-table-hierarchies.ts +++ b/frontend/src/app/components/wp-fast-table/wp-table-hierarchies.ts @@ -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 { - 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; } } diff --git a/frontend/src/app/components/wp-fast-table/wp-table-highlight.ts b/frontend/src/app/components/wp-fast-table/wp-table-highlight.ts index 3a86105948..78377435bc 100644 --- a/frontend/src/app/components/wp-fast-table/wp-table-highlight.ts +++ b/frontend/src/app/components/wp-fast-table/wp-table-highlight.ts @@ -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[]; } diff --git a/frontend/src/app/components/wp-fast-table/wp-table-pagination.ts b/frontend/src/app/components/wp-fast-table/wp-table-pagination.ts index 9499d6629f..ffcf154dac 100644 --- a/frontend/src/app/components/wp-fast-table/wp-table-pagination.ts +++ b/frontend/src/app/components/wp-fast-table/wp-table-pagination.ts @@ -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 { +export class WorkPackageTablePagination { public current:PaginationInstance; constructor(results:WorkPackageCollectionResource) { - super(); this.current = new PaginationInstance(results.offset, results.total, results.pageSize); } diff --git a/frontend/src/app/components/wp-fast-table/wp-table-relation-columns.ts b/frontend/src/app/components/wp-fast-table/wp-table-relation-columns.ts index fe17d6707e..298a890b3d 100644 --- a/frontend/src/app/components/wp-fast-table/wp-table-relation-columns.ts +++ b/frontend/src/app/components/wp-fast-table/wp-table-relation-columns.ts @@ -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 { - 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]; - } -} diff --git a/frontend/src/app/components/wp-fast-table/wp-table-sort-by.ts b/frontend/src/app/components/wp-fast-table/wp-table-sort-by.ts deleted file mode 100644 index f6a68c30dc..0000000000 --- a/frontend/src/app/components/wp-fast-table/wp-table-sort-by.ts +++ /dev/null @@ -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 { - public current:QuerySortByResource[] = []; - - constructor(query:QueryResource) { - super(); - this.current = cloneHalResourceCollection(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)); - } -} diff --git a/frontend/src/app/components/wp-fast-table/wp-table-sum.ts b/frontend/src/app/components/wp-fast-table/wp-table-sum.ts deleted file mode 100644 index e6768528be..0000000000 --- a/frontend/src/app/components/wp-fast-table/wp-table-sum.ts +++ /dev/null @@ -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 { - public current:boolean; - - constructor(isSum:boolean) { - super(); - this.current = isSum; - } - - public toggle() { - this.current = !this.current; - } - - public get isEnabled() { - return this.current; - } -} diff --git a/frontend/src/app/components/wp-fast-table/wp-table-timeline.ts b/frontend/src/app/components/wp-fast-table/wp-table-timeline.ts index 2c3c18a145..4bec9a0113 100644 --- a/frontend/src/app/components/wp-fast-table/wp-table-timeline.ts +++ b/frontend/src/app/components/wp-fast-table/wp-table-timeline.ts @@ -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 { - 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; } diff --git a/frontend/src/app/components/wp-fast-table/wp-table.interfaces.ts b/frontend/src/app/components/wp-fast-table/wp-table.interfaces.ts index 8097a7bd44..bae7f10fb6 100644 --- a/frontend/src/app/components/wp-fast-table/wp-table.interfaces.ts +++ b/frontend/src/app/components/wp-fast-table/wp-table.interfaces.ts @@ -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}; diff --git a/frontend/src/app/components/wp-list/wp-states-initialization.service.ts b/frontend/src/app/components/wp-list/wp-states-initialization.service.ts index b3ce265088..b0e2dc1944 100644 --- a/frontend/src/app/components/wp-list/wp-states-initialization.service.ts +++ b/frontend/src/app/components/wp-list/wp-states-initialization.service.ts @@ -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); } diff --git a/frontend/src/app/components/wp-table/configuration-modal/tabs/display-settings-tab.component.ts b/frontend/src/app/components/wp-table/configuration-modal/tabs/display-settings-tab.component.ts index ca171c241f..bbf3c6500a 100644 --- a/frontend/src/app/components/wp-table/configuration-modal/tabs/display-settings-tab.component.ts +++ b/frontend/src/app/components/wp-table/configuration-modal/tabs/display-settings-tab.component.ts @@ -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() diff --git a/frontend/src/app/components/wp-table/configuration-modal/tabs/filters-tab.component.ts b/frontend/src/app/components/wp-table/configuration-modal/tabs/filters-tab.component.ts index 9832b6f2ce..1d715109d2 100644 --- a/frontend/src/app/components/wp-table/configuration-modal/tabs/filters-tab.component.ts +++ b/frontend/src/app/components/wp-table/configuration-modal/tabs/filters-tab.component.ts @@ -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() { diff --git a/frontend/src/app/components/wp-table/configuration-modal/tabs/highlighting-tab.component.ts b/frontend/src/app/components/wp-table/configuration-modal/tabs/highlighting-tab.component.ts index 12a5999865..757bbdd2e2 100644 --- a/frontend/src/app/components/wp-table/configuration-modal/tabs/highlighting-tab.component.ts +++ b/frontend/src/app/components/wp-table/configuration-modal/tabs/highlighting-tab.component.ts @@ -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() { diff --git a/frontend/src/app/components/wp-table/configuration-modal/tabs/sort-by-tab.component.ts b/frontend/src/app/components/wp-table/configuration-modal/tabs/sort-by-tab.component.ts index 17e224b3c3..9de14e12b3 100644 --- a/frontend/src/app/components/wp-table/configuration-modal/tabs/sort-by-tab.component.ts +++ b/frontend/src/app/components/wp-table/configuration-modal/tabs/sort-by-tab.component.ts @@ -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}, diff --git a/frontend/src/app/components/wp-table/configuration-modal/wp-table-configuration-relation-selector.ts b/frontend/src/app/components/wp-table/configuration-modal/wp-table-configuration-relation-selector.ts index 9965bd47aa..0ea9efe1f8 100644 --- a/frontend/src/app/components/wp-table/configuration-modal/wp-table-configuration-relation-selector.ts +++ b/frontend/src/app/components/wp-table/configuration-modal/wp-table-configuration-relation-selector.ts @@ -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 { diff --git a/frontend/src/app/components/wp-table/embedded/wp-embedded-table.component.ts b/frontend/src/app/components/wp-table/embedded/wp-embedded-table.component.ts index fb7329101a..2a0fcdb35f 100644 --- a/frontend/src/app/components/wp-table/embedded/wp-embedded-table.component.ts +++ b/frontend/src/app/components/wp-table/embedded/wp-embedded-table.component.ts @@ -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(); + @Output() public onFiltersChanged = new EventEmitter(); readonly QueryDm:QueryDmService = this.injector.get(QueryDmService); readonly opModalService:OpModalService = this.injector.get(OpModalService); diff --git a/frontend/src/app/components/wp-table/sort-header/sort-header.directive.ts b/frontend/src/app/components/wp-table/sort-header/sort-header.directive.ts index dce9f124dd..38f57c6af6 100644 --- a/frontend/src/app/components/wp-table/sort-header/sort-header.directive.ts +++ b/frontend/src/app/components/wp-table/sort-header/sort-header.directive.ts @@ -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; }); diff --git a/frontend/src/app/components/wp-table/timeline/container/wp-timeline-container.directive.ts b/frontend/src/app/components/wp-table/timeline/container/wp-timeline-container.directive.ts index f81196ed69..ed9e59f23d 100644 --- a/frontend/src/app/components/wp-table/timeline/container/wp-timeline-container.directive.ts +++ b/frontend/src/app/components/wp-table/timeline/container/wp-timeline-container.directive.ts @@ -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)) ) diff --git a/frontend/src/app/components/wp-table/timeline/global-elements/wp-timeline-relations.directive.ts b/frontend/src/app/components/wp-table/timeline/global-elements/wp-timeline-relations.directive.ts index 372f424401..81456e3496 100644 --- a/frontend/src/app/components/wp-table/timeline/global-elements/wp-timeline-relations.directive.ts +++ b/frontend/src/app/components/wp-table/timeline/global-elements/wp-timeline-relations.directive.ts @@ -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) ) diff --git a/frontend/src/app/components/wp-table/wp-table-sums-row/wp-table-sums-row.directive.ts b/frontend/src/app/components/wp-table/wp-table-sums-row/wp-table-sums-row.directive.ts index 155fd41bfd..639054239e 100644 --- a/frontend/src/app/components/wp-table/wp-table-sums-row/wp-table-sums-row.directive.ts +++ b/frontend/src/app/components/wp-table/wp-table-sums-row/wp-table-sums-row.directive.ts @@ -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); }); diff --git a/frontend/src/app/components/wp-table/wp-table.directive.html b/frontend/src/app/components/wp-table/wp-table.directive.html index 75c401c4c9..99d25ee21b 100644 --- a/frontend/src/app/components/wp-table/wp-table.directive.html +++ b/frontend/src/app/components/wp-table/wp-table.directive.html @@ -39,21 +39,16 @@ - + - + {{text.noResults.title}} {{text.noResults.description}} - - - {{text.faultyQuery.title}} - {{text.faultyQuery.description}} - diff --git a/frontend/src/app/components/wp-table/wp-table.directive.ts b/frontend/src/app/components/wp-table/wp-table.directive.ts index 70de6c9fd8..12bb3c5f97 100644 --- a/frontend/src/app/components/wp-table/wp-table.directive.ts +++ b/frontend/src/app/components/wp-table/wp-table.directive.ts @@ -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 diff --git a/frontend/src/app/modules/attachments/attachment-list/attachment-list-item.component.ts b/frontend/src/app/modules/attachments/attachment-list/attachment-list-item.component.ts index 78c7770f05..68f0d7b334 100644 --- a/frontend/src/app/modules/attachments/attachment-list/attachment-list-item.component.ts +++ b/frontend/src/app/modules/attachments/attachment-list/attachment-list-item.component.ts @@ -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 diff --git a/frontend/src/app/modules/attachments/attachments.component.ts b/frontend/src/app/modules/attachments/attachments.component.ts index a6e0c7bb7e..6fe1cc1330 100644 --- a/frontend/src/app/modules/attachments/attachments.component.ts +++ b/frontend/src/app/modules/attachments/attachments.component.ts @@ -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) diff --git a/frontend/src/app/modules/calendar/wp-calendar/wp-calendar.component.ts b/frontend/src/app/modules/calendar/wp-calendar/wp-calendar.component.ts index e21766bfca..f41b1e0147 100644 --- a/frontend/src/app/modules/calendar/wp-calendar/wp-calendar.component.ts +++ b/frontend/src/app/modules/calendar/wp-calendar/wp-calendar.component.ts @@ -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; + }); } } diff --git a/frontend/src/app/modules/global_search/global-search-work-packages.component.ts b/frontend/src/app/modules/global_search/global-search-work-packages.component.ts index 5f52aa9be6..733cd75e5b 100644 --- a/frontend/src/app/modules/global_search/global-search-work-packages.component.ts +++ b/frontend/src/app/modules/global_search/global-search-work-packages.component.ts @@ -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); } } diff --git a/frontend/src/app/modules/work_packages/query-space/isolated-query-space.ts b/frontend/src/app/modules/work_packages/query-space/isolated-query-space.ts index a65230af87..a95fc63add 100644 --- a/frontend/src/app/modules/work_packages/query-space/isolated-query-space.ts +++ b/frontend/src/app/modules/work_packages/query-space/isolated-query-space.ts @@ -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(); // Set of columns in strict order of appearance -columns = input(); + columns = input(); // Set of filters - filters = input(); + filters = input(); // Active and available sort by - sortBy = input(); + sortBy = input(); // Active and available group by - groupBy = input(); + groupBy = input(); // is query summed - sum = input(); + sum = input(); // pagination information pagination = input(); // 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 = + derive(this.combinedquerySpaces, ($, ) => $.pipe(mapTo(null))); } export class UserUpdaterStates { diff --git a/frontend/src/app/modules/work_packages/routing/wp-view-base/work-packages-view.base.ts b/frontend/src/app/modules/work_packages/routing/wp-view-base/work-packages-view.base.ts index 51f8f15c5a..1ab30c4675 100644 --- a/frontend/src/app/modules/work_packages/routing/wp-view-base/work-packages-view.base.ts +++ b/frontend/src/app/modules/work_packages/routing/wp-view-base/work-packages-view.base.ts @@ -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, 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),