// -- 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 {scopeDestroyed$, scopedObservable} from "../../../helpers/angular-rx-utils"; import {QueryResource} from "../../api/api-v3/hal-resources/query-resource.service"; import {LoadingIndicatorService} from "../../common/loading-indicator/loading-indicator.service"; import {States} from "../../states.service"; import {WorkPackageTableColumnsService} from "../../wp-fast-table/state/wp-table-columns.service"; import {WorkPackageTableFiltersService} from "../../wp-fast-table/state/wp-table-filters.service"; import {WorkPackageTableGroupByService} from "../../wp-fast-table/state/wp-table-group-by.service"; import {WorkPackageTablePaginationService} from "../../wp-fast-table/state/wp-table-pagination.service"; import {WorkPackageTableSortByService} from "../../wp-fast-table/state/wp-table-sort-by.service"; import {WorkPackageTableSumService} from "../../wp-fast-table/state/wp-table-sum.service"; import {WorkPackageTablePagination} from "../../wp-fast-table/wp-table-pagination"; import {WorkPackageTableHierarchiesService} from './../../wp-fast-table/state/wp-table-hierarchy.service'; import {WorkPackagesListChecksumService} from "../../wp-list/wp-list-checksum.service"; import {WorkPackagesListService} from "../../wp-list/wp-list.service"; import {WorkPackageTableTimelineService} from "../../wp-fast-table/state/wp-table-timeline.service"; import {WorkPackageTableBaseService} from "../../wp-fast-table/state/wp-table-base.service"; import {WorkPackageTableBaseState} from "../../wp-fast-table/wp-table-base"; import {WorkPackageTableRefreshService} from "../../wp-table/wp-table-refresh-request.service"; import {debugLog} from "../../../helpers/debug_output"; function WorkPackagesListController($scope:any, $state:ng.ui.IStateService, AuthorisationService:any, states:States, wpTableRefresh:WorkPackageTableRefreshService, wpTableColumns:WorkPackageTableColumnsService, wpTableSortBy:WorkPackageTableSortByService, wpTableGroupBy:WorkPackageTableGroupByService, wpTableFilters:WorkPackageTableFiltersService, wpTableSum:WorkPackageTableSumService, wpTableTimeline:WorkPackageTableTimelineService, wpTableHierarchies:WorkPackageTableHierarchiesService, wpTablePagination:WorkPackageTablePaginationService, wpListService:WorkPackagesListService, wpListChecksumService:WorkPackagesListChecksumService, loadingIndicator:LoadingIndicatorService, I18n:op.I18n) { $scope.projectIdentifier = $state.params['projectPath'] || null; $scope.I18n = I18n; $scope.text = { 'jump_to_pagination': I18n.t('js.work_packages.jump_marks.pagination'), 'text_jump_to_pagination': I18n.t('js.work_packages.jump_marks.label_pagination') }; // Setup function initialSetup() { const loadingRequired = wpListChecksumService.isUninitialized(); // Listen to changes on the query state objects setupQueryObservers(); // Require initial loading of the list if not yet done if (loadingRequired) { wpTableRefresh.clear('Impending query loading.'); loadQuery(); } // Listen for refresh changes setupRefreshObserver(); } // Teardown $scope.$on('$destroy', () => { wpTableRefresh.clear("Table controller scope destroyed."); }); function setupQueryObservers() { scopedObservable($scope, states.table.query.values$()) .withLatestFrom( wpTablePagination.observeOnScope($scope) ).subscribe(([query, pagination]) => { $scope.tableInformationLoaded = true; updateTitle(query); wpListChecksumService.updateIfDifferent(query, pagination as WorkPackageTablePagination); }); wpTablePagination.observeOnScope($scope) .withLatestFrom(scopedObservable($scope, states.table.query.values$())) .subscribe(([pagination, query]) => { if (wpListChecksumService.isQueryOutdated(query, pagination as WorkPackageTablePagination)) { wpListChecksumService.update(query, pagination as WorkPackageTablePagination); updateResultsVisibly(); } }); setupChangeObserver(wpTableFilters, 'filters'); setupChangeObserver(wpTableGroupBy, 'groupBy'); setupChangeObserver(wpTableSortBy, 'sortBy'); setupChangeObserver(wpTableSum, 'sums'); setupChangeObserver(wpTableTimeline, 'timelineVisible', false); setupChangeObserver(wpTableHierarchies, 'showHierarchies', false); setupChangeObserver(wpTableColumns, 'columns', false); } function setupChangeObserver(service:WorkPackageTableBaseService, name:string, triggerUpdate:boolean = true) { const queryState = states.table.query; service .observeUntil(scopeDestroyed$($scope)) .filter(() => !isAnyDependentStateClear()) // Avoid updating while not all states are initialized .filter(() => queryState.hasValue()) .filter((stateValue:WorkPackageTableBaseState) => { // Avoid updating if the query is up-to-date // (Loaded into query elsewhere) const queryValue = stateValue.comparerFunction()(queryState.value![name]); return _.isEqual(queryValue, stateValue.extractedCompareValue) === false; }) .distinctUntilChanged( (a,b) => _.isEqual(a,b), (stateValue:WorkPackageTableBaseState) => stateValue.extractedCompareValue ) .subscribe((stateValue:WorkPackageTableBaseState) => { const newQuery = queryState.value!; newQuery[name] = _.cloneDeep(stateValue.current); states.table.query.putValue(newQuery); if (triggerUpdate) { updateResultsVisibly(true); } }); } /** * Setup the listener for members of the table to request a refresh of the entire table * through the refresh service. */ function setupRefreshObserver() { wpTableRefresh.state .values$('Refresh listener in wp-list.controller') .throttleTime(2000) .takeUntil(scopeDestroyed$($scope)) .subscribe((refreshVisibly:boolean) => { if (refreshVisibly) { debugLog("Refreshing work package results visibly."); updateResultsVisibly(); } else { debugLog("Refreshing work package results in the background."); updateResults(); } }); } function loadQuery() { wpListChecksumService.clear(); loadingIndicator.table.promise = wpListService.fromQueryParams($state.params, $scope.projectIdentifier); } $scope.setAnchorToNextElement = function () { // Skip to next when visible, otherwise skip to previous const selectors = '#pagination--next-link, #pagination--prev-link, #pagination-empty-text'; const visibleLink = jQuery(selectors) .not(':hidden') .first(); if (visibleLink.length) { visibleLink.focus(); } } function updateResults() { return wpListService.reloadCurrentResultsList() } function updateToFirstResultsPage() { return wpListService.loadCurrentResultsListFirstPage(); } function updateResultsVisibly(firstPage:boolean = false) { if (firstPage) { loadingIndicator.table.promise = updateToFirstResultsPage(); } else { loadingIndicator.table.promise = updateResults(); } } $scope.allowed = function(model:string, permission:string) { return AuthorisationService.can(model, permission); }; initialSetup(); function updateTitle(query:QueryResource) { if (query.id) { $scope.selectedTitle = query.name; } else { $scope.selectedTitle = I18n.t('js.label_work_package_plural'); } } $scope.$watchCollection( () => { return { query_id: $state.params['query_id'], query_props: $state.params['query_props'] }; }, (params:any) => { let newChecksum = params.query_props; let newId = params.query_id && parseInt(params.query_id); wpListChecksumService.executeIfOutdated(newId, newChecksum, loadQuery); }); // The combineLatest retains the last value of each observable regardless of // whether it has become null|undefined in the meantime. // As we alter the query's property from it's dependent states, we have to ensure // that we do not set them if he dependent state does depend on another query with // the value only being available because it is still retained. function isAnyDependentStateClear() { return !states.table.pagination.value || !states.table.filters.value || !states.table.columns.value || !states.table.sortBy.value || !states.table.groupBy.value || !states.table.timelineVisible.value || !states.table.hierarchies.value || !states.table.sum.value; } } angular .module('openproject.workPackages.controllers') .controller('WorkPackagesListController', WorkPackagesListController);