Use wp-list as entry component

pull/6179/head
Oliver Günther 7 years ago
parent bc69b3a1d7
commit 4137d0668d
No known key found for this signature in database
GPG Key ID: 88872239EB414F99
  1. 6
      frontend/app/components/angular/angular-injector-bridge.functions.ts
  2. 14
      frontend/app/components/routing/ui-router.config.ts
  3. 233
      frontend/app/components/routing/wp-details/wp-details.controller.test.ts
  4. 20
      frontend/app/components/routing/wp-list/wp-list.component.ts
  5. 56
      frontend/app/components/routing/wp-list/wp-list.router.directive.ts
  6. 6
      frontend/app/components/routing/wp-show/wp-show.controller.ts
  7. 37
      frontend/app/components/routing/wp-split-view/wp-split-view.component.ts
  8. 0
      frontend/app/components/routing/wp-split-view/wp-split-view.html
  9. 56
      frontend/app/components/routing/wp-view-base/wp-view-base.controller.ts

@ -1,8 +1,9 @@
/**
* Returns the currently bootstrapped injector from the application.
* Not applicable until after the application bootstrapping is done.
*/
import {Injector} from '@angular/core';
export function $currentInjector() {
return (window as any).ngInjector || angular.element(document.body).injector();
}
@ -29,6 +30,7 @@
*
* @param target The target to inject into
* @param dependencies A set of dependencies to inject
* @deprecated
*/
export function $injectFields(target:any, ...dependencies:string[]) {
let $injector = $currentInjector();
@ -36,3 +38,5 @@
target[dep] = $injector.get(dep);
});
}

@ -29,6 +29,8 @@
import {openprojectModule} from '../../angular-modules';
import {FirstRouteService} from 'app/components/routing/first-route-service';
import {Transition, TransitionService, UrlMatcherFactory, UrlService} from '@uirouter/core';
import {WorkPackageSplitViewComponent} from 'core-components/routing/wp-split-view/wp-split-view.component';
import {WorkPackagesListComponent} from 'core-components/routing/wp-list/wp-list.component';
const panels = {
get overview() {
@ -81,7 +83,7 @@ openprojectModule
$urlMatcherFactoryProvider.strictMode(false);
console.error("Config ui-router.config");
console.error('Config ui-router.config');
// Prepend the baseurl to the route to avoid using a base tag
// For more information, see
@ -100,7 +102,6 @@ openprojectModule
query_id: { dynamic: true },
query_props: { dynamic: true }
},
onEnter: () => console.error("ENTERING!"),
templateUrl: '/components/routing/main/work-packages.html',
controller: 'WorkPackagesController'
})
@ -149,8 +150,7 @@ openprojectModule
.state('work-packages.list', {
url: '',
controller: 'WorkPackagesListRouter',
template: '<wp-list></wp-list>',
component: WorkPackagesListComponent,
reloadOnSearch: false,
onEnter: () => angular.element('body').addClass('action-index'),
onExit: () => angular.element('body').removeClass('action-index')
@ -182,9 +182,7 @@ openprojectModule
.state('work-packages.list.details', {
redirectTo: 'work-packages.list.details.overview',
url: '/details/{workPackageId:[0-9]+}',
templateUrl: '/components/routing/wp-details/wp.list.details.html',
controller: 'WorkPackageDetailsController',
controllerAs: '$ctrl',
component: WorkPackageSplitViewComponent,
reloadOnSearch: false,
params: {
focus: {
@ -211,7 +209,7 @@ openprojectModule
$transitions:TransitionService,
$window:ng.IWindowService) => {
console.error("RUN ui-router config");
console.error('RUN ui-router config');
$trace.enable(1);
// Our application is still a hybrid one, meaning most routes are still

@ -1,233 +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 {
opApiModule, opServicesModule, openprojectModule,
wpControllersModule, wpServicesModule
} from "../../../angular-modules";
describe('WorkPackageDetailsController', () => {
var scope:any;
var promise:any;
var buildController:any;
var ctrl:any;
var I18n:any = {t: angular.identity};
var workPackage:any = {
props: {},
embedded: {
author: {
props: {
id: 1,
status: 'active'
}
},
id: 99,
project: {
props: {
id: 1
}
},
activities: {
links: {
self: {href: "/api/v3/work_packages/820/activities"}
},
_type: "Collection",
total: 0,
count: 0,
embedded: {
elements: []
}
},
watchers: [],
attachments: {
links: {
self: {href: "/api/v3/work_packages/820/attachments"}
},
_type: "Collection",
total: 0,
count: 0,
embedded: {
elements: []
}
},
type: {
props: {
name: 'Milestone'
}
},
relations: [
{
props: {
_type: "Relation::Relates"
},
links: {
relatedFrom: {
fetch: sinon.spy()
},
relatedTo: {
fetch: sinon.spy()
}
}
}
]
},
links: {
self: {href: "it's a me, it's... you know..."},
availableWatchers: {
fetch: () => {
return {then: angular.noop};
}
},
schema: {
fetch: () => {
return {then: angular.noop};
}
}
},
link: {
addWatcher: {
fetch: () => {
return {then: angular.noop};
}
}
}
};
beforeEach(angular.mock.module(openprojectModule.name, opApiModule.name, 'openproject.layout',
wpControllersModule.name, wpServicesModule.name, opServicesModule.name));
beforeEach(angular.mock.module('openproject.templates', function ($provide:any) {
$provide.constant('ConfigurationService', {
isTimezoneSet: sinon.stub().returns(false),
warnOnLeavingUnsaved: sinon.stub().returns(false)
});
}));
beforeEach(angular.mock.inject(($rootScope:any,
$controller:any,
$injector:ng.auto.IInjectorService,
$state:any,
$q:any,
$httpBackend:any,
WorkPackageService:any) => {
$httpBackend.when('GET', '/api/v3/work_packages/99').respond(workPackage);
(window as any).ngInjector = $injector;
WorkPackageService.getWorkPackage = () => {
return $q.when(workPackage)
};
buildController = () => {
var testState = {
params: {workPackageId: 99},
includes: sinon.stub().returns(true),
go: sinon.stub(),
current: {url: '/activity'}
};
scope = $rootScope.$new();
ctrl = $controller("WorkPackageDetailsController", {
$scope: scope,
$state: testState,
I18n: I18n,
ConfigurationService: {
commentsSortedInDescendingOrder: () => {
return false;
}
},
workPackage: workPackage,
});
promise = ctrl.initialized.promise;
};
}));
describe('initialisation', () => {
it('should initialise', () => {
return buildController();
});
});
describe('#scope.canViewWorkPackageWatchers', () => {
describe('when the work package does not contain the embedded watchers property', () => {
beforeEach(() => {
workPackage.embedded.watchers = undefined;
buildController();
});
it('returns false', () => {
expect(promise).to.eventually.be.fulfilled.then(() => {
expect(scope.canViewWorkPackageWatchers()).to.be.false;
});
});
});
describe('when the work package contains the embedded watchers property', () => {
beforeEach(() => {
workPackage.embedded.watchers = [];
return buildController();
});
it('returns true', () => {
expect(promise).to.eventually.be.fulfilled.then(() => {
expect(scope.canViewWorkPackageWatchers()).to.be.true;
});
});
});
});
describe('work package properties', () => {
describe('relations', () => {
beforeEach(() => {
return buildController();
});
it('Relation::Relates', () => {
expect(promise).to.eventually.be.fulfilled.then(() => {
expect(scope.relatedTo).to.be.ok;
});
});
it('is the embedded type', () => {
expect(promise).to.eventually.be.fulfilled.then(() => {
expect(scope.type.props.name).to.eql('Milestone');
});
});
});
});
describe('showStaticPagePath', () => {
it('points to old show page', () => {
expect(promise).to.eventually.be.fulfilled.then(() => {
expect(scope.showStaticPagePath).to.eql('/work_packages/99');
});
});
});
});

@ -27,7 +27,7 @@
// ++
import {Component, Inject, OnDestroy, OnInit} from '@angular/core';
import {StateService} from '@uirouter/core';
import {StateService, StateParams, TransitionService} from '@uirouter/core';
import {untilComponentDestroyed} from 'ng2-rx-componentdestroyed';
import {auditTime, distinctUntilChanged, filter, withLatestFrom} from 'rxjs/operators';
import {debugLog} from '../../../helpers/debug_output';
@ -131,6 +131,20 @@ export class WorkPackagesListComponent implements OnInit, OnDestroy {
this.wpTableRefresh.clear('Table controller scope destroyed.');
}
/**
* Callback from ui-router when params in this state changed.
* @param {StateParams} params
*/
public uiOnParamsChanged(params:StateParams) {
console.log('params changed to %O', params);
let newChecksum = params.query_props;
let newId = params.query_id && parseInt(params.query_id);
this.wpListChecksumService.executeIfOutdated(newId, newChecksum, () => {
this.wpListService.loadCurrentQueryFromParams(params['projectPath']);
});
}
private setupQueryObservers() {
this.states.tableRendering.onQueryUpdated.values$().pipe()
.take(1)
@ -152,7 +166,8 @@ export class WorkPackagesListComponent implements OnInit, OnDestroy {
this.wpListChecksumService.setToQuery(query, pagination);
});
this.states.query.context.fireOnStateChange(this.wpTablePagination.state, 'Query loaded').values$().pipe(
this.states.query.context.fireOnStateChange(this.wpTablePagination.state,
'Query loaded').values$().pipe(
untilComponentDestroyed(this),
withLatestFrom(this.states.query.resource.values$())
).subscribe(([pagination, query]) => {
@ -227,7 +242,6 @@ export class WorkPackagesListComponent implements OnInit, OnDestroy {
}
}
updateTitle(query:QueryResource) {
if (query.id) {
this.selectedTitle = query.name;

@ -1,56 +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 {WorkPackagesListService} from 'core-components/wp-list/wp-list.service';
import {WorkPackagesListChecksumService} from 'core-components/wp-list/wp-list-checksum.service';
import {StateParams} from '@uirouter/core';
function WorkPackagesListRouterController($scope:any,
$stateParams:StateParams,
wpListChecksumService:WorkPackagesListChecksumService,
wpListService:WorkPackagesListService) {
$scope.$watchCollection(
() => {
return {
query_id: $stateParams['query_id'],
query_props: $stateParams['query_props']
};
},
(params:{ query_id:any, query_props:any }) => {
let newChecksum = params.query_props;
let newId = params.query_id && parseInt(params.query_id);
wpListChecksumService.executeIfOutdated(newId, newChecksum, function() {
wpListService.loadCurrentQueryFromParams($stateParams['projectPath']);
});
});
}
angular
.module('openproject.workPackages.controllers')
.controller('WorkPackagesListRouter', WorkPackagesListRouterController);

@ -27,15 +27,14 @@
// ++
import {wpControllersModule} from "../../../angular-modules";
import {HalResource} from "../../api/api-v3/hal-resources/hal-resource.service";
import {UserResource} from "../../api/api-v3/hal-resources/user-resource.service";
import {WorkPackageResourceInterface} from "../../api/api-v3/hal-resources/work-package-resource.service";
import {WorkPackageViewController} from "../wp-view-base/wp-view-base.controller";
import {WorkPackagesListChecksumService} from "../../wp-list/wp-list-checksum.service";
import {WorkPackageMoreMenuService} from '../../work-packages/work-package-more-menu.service'
import {WorkPackageTableFocusService} from "core-components/wp-fast-table/state/wp-table-focus.service";
import {StateService} from '@uirouter/core';
import {TypeResource} from "core-components/api/api-v3/hal-resources/type-resource.service";
import {Injector} from '@angular/core';
export class WorkPackageShowController extends WorkPackageViewController {
@ -54,10 +53,11 @@ export class WorkPackageShowController extends WorkPackageViewController {
private wpMoreMenu:WorkPackageMoreMenuService;
constructor(public $scope:any,
public injector:Injector,
public $state:StateService,
public wpTableFocus:WorkPackageTableFocusService,
protected wpMoreMenuService:WorkPackageMoreMenuService) {
super($scope, $state.params['workPackageId']);
super(injector, $state.params['workPackageId']);
this.observeWorkPackage();
}

@ -26,27 +26,31 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {wpControllersModule} from "../../../angular-modules";
import {scopedObservable} from "../../../helpers/angular-rx-utils";
import {States} from "../../states.service";
import {WorkPackageTableSelection} from "../../wp-fast-table/state/wp-table-selection.service";
import {KeepTabService} from "../../wp-panels/keep-tab/keep-tab.service";
import {WorkPackageViewController} from "../wp-view-base/wp-view-base.controller";
import {WorkPackageEditingService} from '../../wp-edit-form/work-package-editing-service';
import {FirstRouteService} from "core-components/routing/first-route-service";
import {WorkPackageTableFocusService} from "core-components/wp-fast-table/state/wp-table-focus.service";
import {States} from '../../states.service';
import {WorkPackageTableSelection} from '../../wp-fast-table/state/wp-table-selection.service';
import {KeepTabService} from '../../wp-panels/keep-tab/keep-tab.service';
import {WorkPackageViewController} from '../wp-view-base/wp-view-base.controller';
import {FirstRouteService} from 'core-components/routing/first-route-service';
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
import {StateService} from '@uirouter/core';
import {Component, Inject, Injector} from '@angular/core';
import {componentDestroyed} from 'ng2-rx-componentdestroyed';
import {$stateToken} from 'core-app/angular4-transition-utils';
export class WorkPackageDetailsController extends WorkPackageViewController {
@Component({
template: require('!!raw-loader!./wp-split-view.html'),
selector: 'wp-split-view-entry',
})
export class WorkPackageSplitViewComponent extends WorkPackageViewController {
constructor(public $scope:ng.IScope,
constructor(public injector:Injector,
public states:States,
public firstRoute:FirstRouteService,
public keepTab:KeepTabService,
public wpTableSelection:WorkPackageTableSelection,
public wpTableFocus:WorkPackageTableFocusService,
public $state:StateService) {
super($scope, $state.params['workPackageId']);
@Inject($stateToken) readonly $state:StateService) {
super(injector, $state.params['workPackageId']);
this.observeWorkPackage();
let wpId = $state.params['workPackageId'];
@ -65,10 +69,9 @@ export class WorkPackageDetailsController extends WorkPackageViewController {
this.wpTableSelection.setRowState(wpId, true);
}
scopedObservable(
$scope,
this.wpTableFocus.whenChanged()
).subscribe(newId => {
.takeUntil(componentDestroyed(this))
.subscribe(newId => {
const idSame = wpId.toString() === newId.toString();
if (!idSame && $state.includes('work-packages.list.details')) {
$state.go(
@ -97,5 +100,3 @@ export class WorkPackageDetailsController extends WorkPackageViewController {
this.text.goTofullScreen = this.I18n.t('js.work_packages.message_successful_show_in_fullscreen');
}
}
wpControllersModule.controller('WorkPackageDetailsController', WorkPackageDetailsController);

@ -37,25 +37,22 @@ import {$injectFields} from '../../angular/angular-injector-bridge.functions';
import {WorkPackageEditingService} from '../../wp-edit-form/work-package-editing-service';
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
import {StateService} from '@uirouter/core';
export class WorkPackageViewController {
protected $q:ng.IQService;
protected $state:StateService;
protected states:States;
protected $rootScope:ng.IRootScopeService;
protected keepTab:KeepTabService;
protected wpCacheService:WorkPackageCacheService;
protected WorkPackageService:any;
protected PathHelper:op.PathHelper;
protected I18n:op.I18n;
protected wpTableRefresh:WorkPackageTableRefreshService;
protected wpEditing:WorkPackageEditingService;
protected wpTableFocus:WorkPackageTableFocusService;
// Helper promise to detect when the controller has been initialized
// (when a WP has loaded).
public initialized:ng.IDeferred<any>;
import {Injector, OnDestroy} from '@angular/core';
import {I18nToken} from 'core-app/angular4-transition-utils';
import PathHelper = op.PathHelper;
import {PathHelperService} from 'core-components/common/path-helper/path-helper.service';
import {componentDestroyed} from 'ng2-rx-componentdestroyed';
export class WorkPackageViewController implements OnDestroy {
public wpCacheService:WorkPackageCacheService = this.injector.get(WorkPackageCacheService);
public states:States = this.injector.get(States);
public I18n:op.I18n = this.injector.get(I18nToken);
public keepTab:KeepTabService = this.injector.get(KeepTabService);
public PathHelper:PathHelperService = this.injector.get(PathHelperService);
public wpTableRefresh:WorkPackageTableRefreshService = this.injector.get(WorkPackageTableRefreshService);
protected wpEditing:WorkPackageEditingService = this.injector.get(WorkPackageEditingService);
protected wpTableFocus:WorkPackageTableFocusService = this.injector.get(WorkPackageTableFocusService);
// Static texts
public text:any = {};
@ -67,16 +64,11 @@ export class WorkPackageViewController {
protected focusAnchorLabel:string;
public showStaticPagePath:string;
constructor(public $scope:ng.IScope,
protected workPackageId:string) {
$injectFields(this, '$q', '$state', 'keepTab', 'wpCacheService', 'WorkPackageService',
'states', 'wpEditing', 'PathHelper', 'I18n', 'wpTableRefresh', 'wpTableFocus');
this.initialized = this.$q.defer();
constructor(public injector:Injector, protected workPackageId:string) {
this.initializeTexts();
}
public $onInit() {
ngOnDestroy():void {
// Created for interface compliance
}
@ -85,11 +77,11 @@ export class WorkPackageViewController {
* Needs to be run explicitly by descendants.
*/
protected observeWorkPackage() {
scopedObservable(this.$scope, this.wpCacheService.loadWorkPackage(this.workPackageId).values$())
this.wpCacheService.loadWorkPackage(this.workPackageId).values$()
.takeUntil(componentDestroyed(this))
.subscribe((wp:WorkPackageResourceInterface) => {
this.workPackage = wp;
this.init();
this.initialized.resolve();
});
}
@ -113,10 +105,12 @@ export class WorkPackageViewController {
});
// Preselect this work package for future list operations
this.showStaticPagePath = this.PathHelper.workPackagePath(this.workPackage);
this.showStaticPagePath = this.PathHelper.workPackagePath(this.workPackageId);
// Listen to tab changes to update the tab label
scopedObservable(this.$scope, this.keepTab.observable).subscribe((tabs:any) => {
this.keepTab.observable
.takeUntil(componentDestroyed(this))
.subscribe((tabs:any) => {
this.updateFocusAnchorLabel(tabs.active);
});
}
@ -142,5 +136,3 @@ export class WorkPackageViewController {
return this.workPackage.isEditable;
}
}
wpControllersModule.controller('WorkPackageViewController', WorkPackageViewController);

Loading…
Cancel
Save