diff --git a/frontend/app/angular4-modules.ts b/frontend/app/angular4-modules.ts index 1808a4b1a0..558b07912b 100644 --- a/frontend/app/angular4-modules.ts +++ b/frontend/app/angular4-modules.ts @@ -52,7 +52,7 @@ import {WorkPackageTableSelection} from 'core-components/wp-fast-table/state/wp- import {WorkPackageTableSortByService} from 'core-components/wp-fast-table/state/wp-table-sort-by.service'; import {WorkPackageTableTimelineService} from 'core-components/wp-fast-table/state/wp-table-timeline.service'; import {WpInlineCreateDirectiveUpgraded} from 'core-components/wp-inline-create/wp-inline-create.directive'; -import {KeepTabService} from 'core-components/wp-panels/keep-tab/keep-tab.service'; +import {KeepTabService} from 'core-components/wp-single-view-tabs/keep-tab/keep-tab.service'; import {WorkPackageRelationsService} from 'core-components/wp-relations/wp-relations.service'; import {WpResizerDirectiveUpgraded} from 'core-components/wp-resizer/wp-resizer.directive'; import {SortHeaderDirective} from 'core-components/wp-table/sort-header/sort-header.directive'; @@ -75,7 +75,7 @@ import { halRequestToken, I18nToken, NotificationsServiceToken, - PathHelperToken, + PathHelperToken, TimezoneServiceToken, upgradeService, upgradeServiceWithToken, wpMoreMenuServiceToken } from './angular4-transition-utils'; @@ -114,6 +114,19 @@ import {WorkPackageEditFieldComponent} from 'core-components/wp-edit/wp-edit-fie import {WorkPackageCreateService} from 'core-components/wp-create/wp-create.service'; import {WorkPackageWatcherButtonComponent} from 'core-components/work-packages/wp-watcher-button/wp-watcher-button.component'; import {WorkPackageSplitViewToolbarComponent} from 'core-components/wp-details/wp-details-toolbar.component'; +import {WorkPackageOverviewTabComponent} from 'core-components/wp-single-view-tabs/overview-tab/overview-tab.component'; +import {CurrentProjectService} from 'core-components/projects/current-project.service'; +import {WorkPackageSingleViewComponent} from 'core-components/work-packages/wp-single-view/wp-single-view.component'; +import {WorkPackageStatusButtonComponent} from 'core-components/wp-buttons/wp-status-button/wp-status-button.component'; +import {Ng1AttributeHelpTextWrapper} from 'core-components/common/help-texts/attribute-help-text-ng1-wrapper'; +import {WorkPackageReplacementLabelComponent} from 'core-components/wp-edit/wp-edit-field/wp-replacement-label.component'; +import {FocusWithinDirective} from 'core-components/common/focus-within/focus-within.directive'; +import {AuthoringComponent} from 'core-components/common/authoring/authoring.component'; +import {Ng1WorkPackageAttachmentsUploadWrapper} from 'core-components/wp-attachments/wp-attachments-upload/wp-attachments-upload-ng1-wrapper'; +import {WorkPackageAttachmentListComponent} from 'core-components/wp-attachments/wp-attachment-list/wp-attachment-list.component'; +import {WorkPackageAttachmentListItemComponent} from 'core-components/wp-attachments/wp-attachment-list/wp-attachment-list-item.component'; +import {OpDateTimeUpgradedDirective} from 'core-components/common/date/op-date-time.upgraded.directive'; +import {UserLinkUpgradedComponent} from 'core-components/user/user-link/user-link.upgraded.component'; @NgModule({ imports: [ @@ -136,6 +149,7 @@ import {WorkPackageSplitViewToolbarComponent} from 'core-components/wp-details/w upgradeServiceWithToken('PathHelper', PathHelperToken), upgradeServiceWithToken('halRequest', halRequestToken), upgradeServiceWithToken('wpMoreMenuService', wpMoreMenuServiceToken), + upgradeServiceWithToken('TimezoneService', TimezoneServiceToken), upgradeService('wpRelations', WorkPackageRelationsService), upgradeService('wpCacheService', WorkPackageCacheService), upgradeService('wpEditing', WorkPackageEditingService), @@ -164,6 +178,7 @@ import {WorkPackageSplitViewToolbarComponent} from 'core-components/wp-details/w upgradeService('contextMenu', ContextMenuService), upgradeService('authorisationService', AuthorisationService), upgradeService('ConfigurationService', ConfigurationService), + upgradeService('currentProject', CurrentProjectService), // Split view upgradeService('wpCreate', WorkPackageCreateService), upgradeService('firstRoute', FirstRouteService), @@ -208,6 +223,20 @@ import {WorkPackageSplitViewToolbarComponent} from 'core-components/wp-details/w WorkPackageWatcherButtonComponent, WorkPackageSubjectComponent, + // Single view + WorkPackageOverviewTabComponent, + WorkPackageSingleViewComponent, + WorkPackageStatusButtonComponent, + Ng1AttributeHelpTextWrapper, + WorkPackageReplacementLabelComponent, + FocusWithinDirective, + AuthoringComponent, + Ng1WorkPackageAttachmentsUploadWrapper, + WorkPackageAttachmentListComponent, + WorkPackageAttachmentListItemComponent, + OpDateTimeUpgradedDirective, + UserLinkUpgradedComponent, + // WP Edit Fields WorkPackageEditFieldComponent, ], @@ -222,7 +251,10 @@ import {WorkPackageSplitViewToolbarComponent} from 'core-components/wp-details/w AddSectionDropdownComponent, // Split view - WorkPackageSplitViewComponent + WorkPackageSplitViewComponent, + + // Single view + WorkPackageOverviewTabComponent, ] }) export class OpenProjectModule { diff --git a/frontend/app/angular4-transition-utils.ts b/frontend/app/angular4-transition-utils.ts index c0de13f961..ef38289910 100644 --- a/frontend/app/angular4-transition-utils.ts +++ b/frontend/app/angular4-transition-utils.ts @@ -43,6 +43,7 @@ export const v3PathToken = new InjectionToken('v3Path'); export const PathHelperToken = new InjectionToken('PathHelper'); export const halRequestToken = new InjectionToken('halRequest'); export const wpMoreMenuServiceToken = new InjectionToken('wpMoreMenuService'); +export const TimezoneServiceToken = new InjectionToken('TimezoneService'); export const $httpToken = new InjectionToken('$http'); export const halResourceFactoryToken = new InjectionToken('halResourceFactory'); diff --git a/frontend/app/components/common/authoring/authoring.component.ts b/frontend/app/components/common/authoring/authoring.component.ts new file mode 100644 index 0000000000..1ba79deebc --- /dev/null +++ b/frontend/app/components/common/authoring/authoring.component.ts @@ -0,0 +1,79 @@ +//-- copyright +// OpenProject is a project management system. +// Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details. +//++ + +import {Component, Inject, Input, OnInit} from '@angular/core'; +import {I18nToken, TimezoneServiceToken} from '../../../angular4-transition-utils'; +import {PathHelperService} from 'core-components/common/path-helper/path-helper.service'; +import {HalResource} from 'core-components/api/api-v3/hal-resources/hal-resource.service'; +import {opUiComponentsModule} from 'core-app/angular-modules'; +import {downgradeComponent} from '@angular/upgrade/static'; + +@Component({ + template: require('!!raw-loader!./authoring.html'), + selector: 'authoring', +}) +export class AuthoringComponent implements OnInit { + // scope: { createdOn: '=', author: '=', showAuthorAsLink: '=', project: '=', activity: '=' }, + @Input('createdOn') createdOn:string; + @Input('author') author:HalResource; + @Input('showAuthorAsLink') showAuthorAsLink:boolean; + @Input('project') project:any; + @Input('activity') activity:any; + + public createdOnTime:any; + public timeago:any; + public time:any; + public userLink:string; + + public constructor(readonly PathHelper:PathHelperService, + @Inject(I18nToken) readonly I18n:op.I18n, + @Inject(TimezoneServiceToken) readonly TimezoneService:any) { + + } + + ngOnInit() { + this.createdOnTime = this.TimezoneService.parseDatetime(this.createdOn); + this.timeago = this.createdOnTime.fromNow(); + this.time = this.createdOnTime.format('LLL'); + this.userLink = this.PathHelper.userPath(this.author.id); + } + + public activityFromPath(from:any) { + var path = this.PathHelper.projectActivityPath(this.project); + + if (from) { + path += '?from=' + from; + } + + return path; + } +} + +opUiComponentsModule.directive('authoring', + downgradeComponent({component: AuthoringComponent}) +); diff --git a/frontend/app/components/common/authoring/authoring.html b/frontend/app/components/common/authoring/authoring.html new file mode 100644 index 0000000000..ac53613597 --- /dev/null +++ b/frontend/app/components/common/authoring/authoring.html @@ -0,0 +1,18 @@ +
+ + + + + + + + + + +
diff --git a/frontend/app/ui_components/date/date-time-directive.js b/frontend/app/components/common/date/op-date-time.directive.ts similarity index 85% rename from frontend/app/ui_components/date/date-time-directive.js rename to frontend/app/components/common/date/op-date-time.directive.ts index 8023872359..171bdad20a 100644 --- a/frontend/app/ui_components/date/date-time-directive.js +++ b/frontend/app/components/common/date/op-date-time.directive.ts @@ -26,18 +26,21 @@ // See docs/COPYRIGHT.rdoc for more details. //++ -module.exports = function($compile, TimezoneService) { +import {opUiComponentsModule} from '../../../angular-modules'; + +function opDateTimeDirective($compile:ng.ICompileService, TimezoneService:any) { return { restrict: 'EA', - replace: true, scope: { dateTimeValue: '=' }, // Note: we cannot reuse op-date here as this does not apply the user's configured timezone template: '{{date}} {{time}}', - link: function(scope, element, attrs) { + link: function(scope:any, element:ng.IAugmentedJQuery) { var c = TimezoneService.formattedDatetimeComponents(scope.dateTimeValue); scope.date = c[0]; scope.time = c[1]; $compile(element.contents())(scope); } }; -}; +} + +opUiComponentsModule.directive('opDateTime', opDateTimeDirective); diff --git a/frontend/app/components/common/date/op-date-time.upgraded.directive.ts b/frontend/app/components/common/date/op-date-time.upgraded.directive.ts new file mode 100644 index 0000000000..1c3b38da13 --- /dev/null +++ b/frontend/app/components/common/date/op-date-time.upgraded.directive.ts @@ -0,0 +1,43 @@ +//-- copyright +// OpenProject is a project management system. +// Copyright (C) 2012-2017 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-2017 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 {Component, Directive, ElementRef, Injector, Input, Output} from '@angular/core'; +import {UpgradeComponent} from '@angular/upgrade/static'; +import {UserResource} from 'core-components/api/api-v3/hal-resources/user-resource.service'; + +@Directive({ + selector: 'op-date-time' +}) +export class OpDateTimeUpgradedDirective extends UpgradeComponent { + + @Input('dateTimeValue') dateTimeValue:any; + + constructor(elementRef:ElementRef, injector:Injector) { + super('opDateTime', elementRef, injector); + } +} diff --git a/frontend/app/components/common/focus-within/focus-within.directive.ts b/frontend/app/components/common/focus-within/focus-within.directive.ts index 8f1ee55d3e..395ab57df1 100644 --- a/frontend/app/components/common/focus-within/focus-within.directive.ts +++ b/frontend/app/components/common/focus-within/focus-within.directive.ts @@ -29,46 +29,57 @@ import {wpDirectivesModule} from '../../../angular-modules'; import {scopedObservable} from '../../../helpers/angular-rx-utils'; import {BehaviorSubject} from 'rxjs'; +import {Directive, ElementRef, Input, OnDestroy, OnInit} from '@angular/core'; +import {componentDestroyed} from 'ng2-rx-componentdestroyed'; +import {downgradeComponent} from '@angular/upgrade/static'; // with courtesy of http://stackoverflow.com/a/29722694/3206935 -function focusWithinDirective($timeout:ng.ITimeoutService) { - return { - restrict: 'A', +@Directive({ + selector: '[focus-within]' +}) +export class FocusWithinDirective implements OnInit, OnDestroy { + @Input('focusWithinSelector') selector:string; - scope: { - selector: '=focusWithin' - }, + private $element:ng.IAugmentedJQuery; - link: (scope:any, element:ng.IAugmentedJQuery) => { - let focusedObservable = new BehaviorSubject(false); + constructor(private elementRef:ElementRef) { - scopedObservable( - scope, - focusedObservable - ) - .auditTime(50) - .subscribe(focused => { - element.toggleClass('-focus', focused); - }); + } + ngOnInit() { + this.$element = jQuery(this.elementRef.nativeElement); + let focusedObservable = new BehaviorSubject(false); - let focusListener = function () { - focusedObservable.next(true); - }; - element[0].addEventListener('focus', focusListener, true); + focusedObservable + .auditTime(50) + .takeUntil(componentDestroyed(this)) + .subscribe(focused => { + this.$element.toggleClass('-focus', focused); + }); - let blurListener = function () { - focusedObservable.next(false); - }; - element[0].addEventListener('blur', blurListener, true); - $timeout(() => { - element.addClass('focus-within--trigger'); - element.find(scope.selector).addClass('focus-within--depending'); - }, 0); - } - }; + let focusListener = function () { + focusedObservable.next(true); + }; + this.$element[0].addEventListener('focus', focusListener, true); + + let blurListener = function () { + focusedObservable.next(false); + }; + this.$element[0].addEventListener('blur', blurListener, true); + + setTimeout(() => { + this.$element.addClass('focus-within--trigger'); + this.$element.find(this.selector).addClass('focus-within--depending'); + }, 0); + } + + ngOnDestroy() { + // NOthing to do + } } -wpDirectivesModule.directive('focusWithin', focusWithinDirective); +wpDirectivesModule.directive('focusWithin', + downgradeComponent({component: FocusWithinDirective}) +); diff --git a/frontend/app/components/common/help-texts/attribute-help-text-ng1-wrapper.ts b/frontend/app/components/common/help-texts/attribute-help-text-ng1-wrapper.ts new file mode 100644 index 0000000000..40dd5c8458 --- /dev/null +++ b/frontend/app/components/common/help-texts/attribute-help-text-ng1-wrapper.ts @@ -0,0 +1,30 @@ +// This Angular directive will act as an interface to the "upgraded" AngularJS component +// query-filters +import { + Directive, DoCheck, ElementRef, Inject, Injector, Input, OnChanges, OnDestroy, + OnInit, SimpleChanges +} from '@angular/core'; +import {UpgradeComponent} from '@angular/upgrade/static'; + +@Directive({selector: 'ng1-attribute-help-text-wrapper'}) +export class Ng1AttributeHelpTextWrapper extends UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy { + @Input('attribute') public attribute:string; + @Input('attributeScope') public attributeScope:string; + @Input('helpTextId') public helpTextId?:string; + @Input('additionalLabel') public additionalLabel?:string; + + constructor(@Inject(ElementRef) elementRef:ElementRef, @Inject(Injector) injector:Injector) { + // We must pass the name of the directive as used by AngularJS to the super + super('attributeHelpText', elementRef, injector); + } + + // For this class to work when compiled with AoT, we must implement these lifecycle hooks + // because the AoT compiler will not realise that the super class implements them + ngOnInit() { super.ngOnInit(); } + + ngOnChanges(changes:SimpleChanges) { super.ngOnChanges(changes); } + + ngDoCheck() { super.ngDoCheck(); } + + ngOnDestroy() { super.ngOnDestroy(); } +} diff --git a/frontend/app/components/routing/ui-router.config.ts b/frontend/app/components/routing/ui-router.config.ts index d673415e94..636febab98 100644 --- a/frontend/app/components/routing/ui-router.config.ts +++ b/frontend/app/components/routing/ui-router.config.ts @@ -31,13 +31,14 @@ 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'; +import {WorkPackageOverviewTabComponent} from 'core-components/wp-single-view-tabs/overview-tab/overview-tab.component'; const panels = { get overview() { return { url: '/overview', reloadOnSearch: false, - template: '' + component: WorkPackageOverviewTabComponent }; }, diff --git a/frontend/app/components/routing/wp-split-view/wp-split-view.component.ts b/frontend/app/components/routing/wp-split-view/wp-split-view.component.ts index 3a60edf9eb..c46dff0fe9 100644 --- a/frontend/app/components/routing/wp-split-view/wp-split-view.component.ts +++ b/frontend/app/components/routing/wp-split-view/wp-split-view.component.ts @@ -28,7 +28,7 @@ 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 {KeepTabService} from '../../wp-single-view-tabs/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'; diff --git a/frontend/app/components/routing/wp-view-base/wp-view-base.controller.ts b/frontend/app/components/routing/wp-view-base/wp-view-base.controller.ts index b8604570c1..faaef4f361 100644 --- a/frontend/app/components/routing/wp-view-base/wp-view-base.controller.ts +++ b/frontend/app/components/routing/wp-view-base/wp-view-base.controller.ts @@ -31,7 +31,7 @@ import {scopedObservable} from '../../../helpers/angular-rx-utils'; import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service'; import {States} from '../../states.service'; import {WorkPackageCacheService} from '../../work-packages/work-package-cache.service'; -import {KeepTabService} from '../../wp-panels/keep-tab/keep-tab.service'; +import {KeepTabService} from '../../wp-single-view-tabs/keep-tab/keep-tab.service'; import {WorkPackageTableRefreshService} from '../../wp-table/wp-table-refresh-request.service'; import {$injectFields} from '../../angular/angular-injector-bridge.functions'; import {WorkPackageEditingService} from '../../wp-edit-form/work-package-editing-service'; diff --git a/frontend/app/components/wp-panels/overview-panel/overview-panel.directive.ts b/frontend/app/components/user/user-link/user-link.upgraded.component.ts similarity index 64% rename from frontend/app/components/wp-panels/overview-panel/overview-panel.directive.ts rename to frontend/app/components/user/user-link/user-link.upgraded.component.ts index dff89b9bdd..6fe2d1a670 100644 --- a/frontend/app/components/wp-panels/overview-panel/overview-panel.directive.ts +++ b/frontend/app/components/user/user-link/user-link.upgraded.component.ts @@ -1,12 +1,12 @@ -// -- copyright +//-- copyright // OpenProject is a project management system. -// Copyright (C) 2012-2015 the OpenProject Foundation (OPF) +// Copyright (C) 2012-2017 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) 2006-2017 Jean-Philippe Lang // Copyright (C) 2010-2013 the ChiliProject Team // // This program is free software; you can redistribute it and/or @@ -24,15 +24,20 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // See doc/COPYRIGHT.rdoc for more details. -// ++ +//++ -import {wpDirectivesModule} from "../../../angular-modules"; +import {Component, Directive, ElementRef, Injector, Input, Output} from '@angular/core'; +import {UpgradeComponent} from '@angular/upgrade/static'; +import {UserResource} from 'core-components/api/api-v3/hal-resources/user-resource.service'; -function overviewPanel(){ - return { - restrict: 'E', - templateUrl: '/components/wp-panels/overview-panel/overview-panel.directive.html', - }; -} +@Directive({ + selector: 'user-link' +}) +export class UserLinkUpgradedComponent extends UpgradeComponent { + + @Input() user:UserResource; -wpDirectivesModule.directive('overviewPanel', overviewPanel); + constructor(elementRef:ElementRef, injector:Injector) { + super('userLink', elementRef, injector); + } +} diff --git a/frontend/app/components/work-packages/wp-attachments-formattable-field/wp-attachments-formattable.directive.ts b/frontend/app/components/work-packages/wp-attachments-formattable-field/wp-attachments-formattable.directive.ts index d6a73f5b17..61c6758afa 100644 --- a/frontend/app/components/work-packages/wp-attachments-formattable-field/wp-attachments-formattable.directive.ts +++ b/frontend/app/components/work-packages/wp-attachments-formattable-field/wp-attachments-formattable.directive.ts @@ -1,6 +1,6 @@ import {InsertMode, ViewMode} from './wp-attachments-formattable.enums'; import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service'; -import {KeepTabService} from '../../wp-panels/keep-tab/keep-tab.service'; +import {KeepTabService} from '../../wp-single-view-tabs/keep-tab/keep-tab.service'; import {openprojectModule} from '../../../angular-modules'; import {WorkPackageCacheService} from '../work-package-cache.service'; import {MarkupModel} from './models/markup-model'; diff --git a/frontend/app/components/work-packages/wp-single-view/wp-single-view.directive.ts b/frontend/app/components/work-packages/wp-single-view/wp-single-view.component.ts similarity index 73% rename from frontend/app/components/work-packages/wp-single-view/wp-single-view.directive.ts rename to frontend/app/components/work-packages/wp-single-view/wp-single-view.component.ts index 470fec0cf7..6bc8fd66be 100644 --- a/frontend/app/components/work-packages/wp-single-view/wp-single-view.directive.ts +++ b/frontend/app/components/work-packages/wp-single-view/wp-single-view.component.ts @@ -26,20 +26,19 @@ // See doc/COPYRIGHT.rdoc for more details. // ++ -import {opWorkPackagesModule} from '../../../angular-modules'; -import {scopedObservable} from '../../../helpers/angular-rx-utils'; import {debugLog} from '../../../helpers/debug_output'; import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service'; import {DisplayField} from '../../wp-display/wp-display-field/wp-display-field.module'; import {WorkPackageDisplayFieldService} from '../../wp-display/wp-display-field/wp-display-field.service'; import {WorkPackageCacheService} from '../work-package-cache.service'; -import {WorkPackageEditFieldGroupController} from "../../wp-edit/wp-edit-field/wp-edit-field-group.directive"; -import { - WorkPackageEditingService -} from '../../wp-edit-form/work-package-editing-service'; +import {WorkPackageEditingService} from '../../wp-edit-form/work-package-editing-service'; import {States} from '../../states.service'; import {CurrentProjectService} from '../../projects/current-project.service'; -import {StateParams} from '@uirouter/core'; +import {Component, Inject, Input, OnDestroy, OnInit} from '@angular/core'; +import {PathHelperService} from 'core-components/common/path-helper/path-helper.service'; +import {I18nToken} from 'core-app/angular4-transition-utils'; +import {componentDestroyed} from 'ng2-rx-componentdestroyed'; +import {WorkPackageEditFieldGroupDirective} from 'core-components/wp-edit/wp-edit-field/wp-edit-field-group.directive'; interface FieldDescriptor { name:string; @@ -55,9 +54,12 @@ interface GroupDescriptor { members:FieldDescriptor[]; } -export class WorkPackageSingleViewController { - public wpEditFieldGroup:WorkPackageEditFieldGroupController; - public workPackage:WorkPackageResourceInterface; +@Component({ + template: require('!!raw-loader!./wp-single-view.html'), + selector: 'wp-single-view', +}) +export class WorkPackageSingleViewComponent implements OnInit, OnDestroy { + @Input('workPackage') public workPackage:WorkPackageResourceInterface; // Grouped fields returned from API public groupedFields:GroupDescriptor[] = []; @@ -66,32 +68,52 @@ export class WorkPackageSingleViewController { href:string|null, field?:FieldDescriptor[] }; - public text:any; - public scope:any; + public text = { + attachments: { + label: this.I18n.t('js.label_attachments') + }, + project: { + required: this.I18n.t('js.project.required_outside_context'), + context: this.I18n.t('js.project.context'), + switchTo: this.I18n.t('js.project.click_to_switch_context'), + }, + + fields: { + description: this.I18n.t('js.work_packages.properties.description'), + }, + description: { + placeholder: this.I18n.t('js.work_packages.placeholders.description') + }, + date: { + startDate: this.I18n.t('js.label_no_start_date'), + dueDate: this.I18n.t('js.label_no_due_date') + }, + infoRow: { + createdBy: this.I18n.t('js.label_created_by'), + lastUpdatedOn: this.I18n.t('js.label_last_updated_on') + }, + }; protected firstTimeFocused:boolean = false; - constructor(protected $scope:ng.IScope, - protected $rootScope:ng.IRootScopeService, - protected $stateParams:StateParams, - protected I18n:op.I18n, + constructor(@Inject(I18nToken) readonly I18n:op.I18n, + public wpEditFieldGroup:WorkPackageEditFieldGroupDirective, protected currentProject:CurrentProjectService, - protected PathHelper:any, + protected PathHelper:PathHelperService, protected states:States, protected wpEditing:WorkPackageEditingService, protected wpDisplayField:WorkPackageDisplayFieldService, protected wpCacheService:WorkPackageCacheService) { } - public initialize() { - // Create I18n texts - this.setupI18nTexts(); - + public ngOnInit() { if (this.workPackage.attachments) { this.workPackage.attachments.updateElements(); } - scopedObservable(this.$scope, this.wpEditing.temporaryEditResource(this.workPackage.id).values$()) + this.wpEditing.temporaryEditResource(this.workPackage.id) + .values$() + .takeUntil(componentDestroyed(this)) .subscribe((resource:WorkPackageResourceInterface) => { // Prepare the fields that are required always const isNew = this.workPackage.isNew; @@ -126,6 +148,10 @@ export class WorkPackageSingleViewController { }); } + ngOnDestroy() { + // Nothing to do + } + /** * Returns whether a group should be hidden due to being empty * (e.g., consists only of CFs and none of them are active in this project. @@ -144,6 +170,16 @@ export class WorkPackageSingleViewController { return this.wpEditFieldGroup.inEditMode && !field.writable; } + /** + * angular 2 doesn't support track by property any more but requires a custom function + * https://github.com/angular/angular/issues/12969 + * @param index + * @param elem + */ + public trackByName(_index:number, elem:{ name: string }) { + return elem.name; + } + /* * Returns the work package label */ @@ -158,29 +194,6 @@ export class WorkPackageSingleViewController { return this.I18n.t('js.project.work_package_belongs_to', { projectname: project }); } - private setupI18nTexts() { - this.text = { - project: { - required: this.I18n.t('js.project.required_outside_context'), - context: this.I18n.t('js.project.context'), - switchTo: this.I18n.t('js.project.click_to_switch_context'), - }, - dropFiles: this.I18n.t('js.label_drop_files'), - dropFilesHint: this.I18n.t('js.label_drop_files_hint'), - fields: { - description: this.I18n.t('js.work_packages.properties.description'), - }, - date: { - startDate: this.I18n.t('js.label_no_start_date'), - dueDate: this.I18n.t('js.label_no_due_date') - }, - infoRow: { - createdBy: this.I18n.t('js.label_created_by'), - lastUpdatedOn: this.I18n.t('js.label_last_updated_on') - }, - }; - } - /** * Maps the grouped fields into their display fields. * May return multiple fields (for the date virtual field). @@ -247,29 +260,3 @@ export class WorkPackageSingleViewController { } } - -function wpSingleViewDirective():any { - - return { - restrict: 'E', - templateUrl: '/components/work-packages/wp-single-view/wp-single-view.directive.html', - - scope: { - workPackage: '=?' - }, - - require: ['^wpEditFieldGroup'], - link: (scope:any, - element:ng.IAugmentedJQuery, - attrs:any, - controllers: [WorkPackageEditFieldGroupController]) => { - scope.$ctrl.wpEditFieldGroup = controllers[0]; - scope.$ctrl.initialize(); - }, - bindToController: true, - controller: WorkPackageSingleViewController, - controllerAs: '$ctrl' - }; -} - -opWorkPackagesModule.directive('wpSingleView', wpSingleViewDirective); diff --git a/frontend/app/components/work-packages/wp-single-view/wp-single-view.directive.html b/frontend/app/components/work-packages/wp-single-view/wp-single-view.directive.html deleted file mode 100644 index af9c195be0..0000000000 --- a/frontend/app/components/work-packages/wp-single-view/wp-single-view.directive.html +++ /dev/null @@ -1,170 +0,0 @@ -
-
- -
- -
- - - -
- : - - . - - . -
- - -
- -
-
-
-
-
-

-
-
- {{ descriptor.label }} - * - -
-
- -
-
-
-
- -
-
-

- -
- - -

-
-
- -
-
-
-

-
-
-
- - -
-
- -
- -
-
-

-
-
- -
-
-
- - {{ descriptor.label }} - * - -
-
- - - - - -
-
- {{ descriptor.label }} - -
-
- - - - - - - -
-
-
-
-
- -
-
-
-
-

- {{ ::$ctrl.I18n.t('js.label_attachments') }} -

-
-
- - - -
-
- -

- {{ ::$ctrl.text.dropFiles }}
- {{ ::$ctrl.text.dropFilesHint }} -

-
-
-
-
-
diff --git a/frontend/app/components/work-packages/wp-single-view/wp-single-view.html b/frontend/app/components/work-packages/wp-single-view/wp-single-view.html new file mode 100644 index 0000000000..997a544997 --- /dev/null +++ b/frontend/app/components/work-packages/wp-single-view/wp-single-view.html @@ -0,0 +1,173 @@ +
+
+ +
+ +
+ + + +
+ : + + . + + . +
+ + +
+ +
+
+
+
+
+

+
+
+ + {{ descriptor.label }} + * + + +
+
+ +
+
+
+
+ +
+
+

+ +
+ + +

+
+
+ +
+
+
+

+
+
+
+ + +
+
+ +
+ +
+
+

+
+
+ +
+
+ +
+ + {{ descriptor.label }} + * + + +
+
+ + + + + +
+
+ + {{ descriptor.label }} + + +
+
+ + + + + + + +
+
+
+
+
+
+ +
+
+
+
+

+
+
+ + + + +
+
diff --git a/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list.directive.ts b/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list-item.component.ts similarity index 56% rename from frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list.directive.ts rename to frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list-item.component.ts index c4413fc70d..22dcc1f20f 100644 --- a/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list.directive.ts +++ b/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list-item.component.ts @@ -26,44 +26,37 @@ // See doc/COPYRIGHT.rdoc for more details. //++ -import {wpDirectivesModule} from '../../../angular-modules'; import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service'; -import {UploadFile} from '../../api/op-file-upload/op-file-upload.service'; -import {HalResource} from '../../api/api-v3/hal-resources/hal-resource.service'; import {WorkPackageNotificationService} from '../../wp-edit/wp-notification.service'; - -export class WorkPackageAttachmentListController { - public workPackage: WorkPackageResourceInterface; - public text: any = {}; - - public itemTemplateUrl = - '/components/wp-attachments/wp-attachment-list/wp-attachment-list-item.html'; - - constructor(protected wpNotificationsService:WorkPackageNotificationService, I18n:op.I18n) { - this.text = { - destroyConfirmation: I18n.t('js.text_attachment_destroy_confirmation'), - removeFile: (arg:any) => I18n.t('js.label_remove_file', arg) - }; - - if (this.workPackage.attachments) { - this.workPackage.attachments.updateElements(); - } +import {Component, Inject, Input} from '@angular/core'; +import {I18nToken} from 'core-app/angular4-transition-utils'; +import {HalResource} from 'core-components/api/api-v3/hal-resources/hal-resource.service'; + +@Component({ + template: require('!!raw-loader!./wp-attachment-list-item.html'), + selector: 'wp-attachment-list-item', +}) +export class WorkPackageAttachmentListItemComponent { + @Input('workPackage') public workPackage:WorkPackageResourceInterface; + @Input('attachment') public attachment:HalResource; + + public text = { + destroyConfirmation: this.I18n.t('js.text_attachment_destroy_confirmation'), + removeFile: (arg:any) => this.I18n.t('js.label_remove_file', arg) } -} -function wpAttachmentListDirective():any { - return { - restrict: 'E', - templateUrl: '/components/wp-attachments/wp-attachment-list/wp-attachment-list.directive.html', + constructor(protected wpNotificationsService:WorkPackageNotificationService, + @Inject(I18nToken) readonly I18n:op.I18n) { + } - scope: { - workPackage: '=' - }, + public confirmRemoveAttachment($event:JQueryEventObject) { + if (!window.confirm(this.text.destroyConfirmation)) { + $event.stopImmediatePropagation(); + $event.preventDefault(); + return false; + } - controller: WorkPackageAttachmentListController, - controllerAs: '$ctrl', - bindToController: true - }; + this.workPackage.removeAttachment(this.attachment); + return false; + } } - -wpDirectivesModule.directive('wpAttachmentList', wpAttachmentListDirective); diff --git a/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list-item.html b/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list-item.html index aa46915f21..ceb7bc1c5d 100644 --- a/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list-item.html +++ b/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list-item.html @@ -1,29 +1,30 @@ + focus-within + focusWithinSelector=".inplace-edit--icon-wrapper"> - {{ ::attachment.fileName || attachment.customName || attachment.name }} + {{ attachment.fileName || attachment.customName || attachment.name }} + [createdOn]="attachment.createdAt" + [author]="attachment.author" + [showAuthorAsLink]="false" + *ngIf="!workPackage.isNew"> - + *ngIf="!!attachment.$links.delete || workPackage.isNew" + (click)="confirmRemoveAttachment($event)"> + diff --git a/frontend/app/components/wp-buttons/wp-status-button/wp-status-button.directive.ts b/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list.component.ts similarity index 65% rename from frontend/app/components/wp-buttons/wp-status-button/wp-status-button.directive.ts rename to frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list.component.ts index a9c2a7f078..1ecd1cf43d 100644 --- a/frontend/app/components/wp-buttons/wp-status-button/wp-status-button.directive.ts +++ b/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list.component.ts @@ -1,4 +1,4 @@ -// -- copyright +//-- copyright // OpenProject is a project management system. // Copyright (C) 2012-2015 the OpenProject Foundation (OPF) // @@ -24,24 +24,22 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // See doc/COPYRIGHT.rdoc for more details. -// ++ +//++ -import {wpButtonsModule} from '../../../angular-modules'; +import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service'; +import {Component, Input, OnInit} from '@angular/core'; +import {HalResource} from 'core-components/api/api-v3/hal-resources/hal-resource.service'; -function wpCreateButton() { - return { - restrict: 'E', - templateUrl: '/components/wp-buttons/wp-status-button/wp-status-button.directive.html', +@Component({ + template: require('!!raw-loader!./wp-attachment-list.html'), + selector: 'wp-attachment-list', +}) +export class WorkPackageAttachmentListComponent implements OnInit { + @Input('workPackage') public workPackage:WorkPackageResourceInterface; - scope: { - allowed: '=', - workPackage: '=' - }, - - bindToController: true, - controllerAs: '$ctrl', - controller: 'WorkPackageStatusButtonController' + ngOnInit() { + if (this.workPackage.attachments) { + this.workPackage.attachments.updateElements(); + } } } - -wpButtonsModule.directive('wpStatusButton', wpCreateButton); diff --git a/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list.directive.html b/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list.directive.html deleted file mode 100644 index ef77a90d15..0000000000 --- a/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list.directive.html +++ /dev/null @@ -1,16 +0,0 @@ -
-
- - - - -
-
diff --git a/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list.html b/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list.html new file mode 100644 index 0000000000..f289deee0b --- /dev/null +++ b/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list.html @@ -0,0 +1,14 @@ +
+
+ + + + + + +
+
diff --git a/frontend/app/components/wp-attachments/wp-attachments-upload/wp-attachments-upload-ng1-wrapper.ts b/frontend/app/components/wp-attachments/wp-attachments-upload/wp-attachments-upload-ng1-wrapper.ts new file mode 100644 index 0000000000..93d539c373 --- /dev/null +++ b/frontend/app/components/wp-attachments/wp-attachments-upload/wp-attachments-upload-ng1-wrapper.ts @@ -0,0 +1,56 @@ +//-- 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. +//++ + +// This Angular directive will act as an interface to the "upgraded" AngularJS component +import { + Directive, DoCheck, ElementRef, Inject, Injector, Input, OnChanges, OnDestroy, + OnInit, SimpleChanges +} from '@angular/core'; +import {UpgradeComponent} from '@angular/upgrade/static'; +import {WorkPackageResourceInterface} from 'core-components/api/api-v3/hal-resources/work-package-resource.service'; + +@Directive({selector: 'ng1-wp-attachments-upload-wrapper'}) +export class Ng1WorkPackageAttachmentsUploadWrapper extends UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy { + @Input('workPackage') workPackage:WorkPackageResourceInterface; + + constructor(@Inject(ElementRef) elementRef:ElementRef, @Inject(Injector) injector:Injector) { + // We must pass the name of the directive as used by AngularJS to the super + super('wpAttachmentsUpload', elementRef, injector); + } + + // For this class to work when compiled with AoT, we must implement these lifecycle hooks + // because the AoT compiler will not realise that the super class implements them + ngOnInit() { super.ngOnInit(); } + + ngOnChanges(changes:SimpleChanges) { super.ngOnChanges(changes); } + + ngDoCheck() { super.ngDoCheck(); } + + ngOnDestroy() { super.ngOnDestroy(); } +} + diff --git a/frontend/app/components/wp-attachments/wp-attachments-upload/wp-attachments-upload.directive.ts b/frontend/app/components/wp-attachments/wp-attachments-upload/wp-attachments-upload.directive.ts index a58ce742db..0a708bd333 100644 --- a/frontend/app/components/wp-attachments/wp-attachments-upload/wp-attachments-upload.directive.ts +++ b/frontend/app/components/wp-attachments/wp-attachments-upload/wp-attachments-upload.directive.ts @@ -38,7 +38,9 @@ export class WorkPackageUploadDirectiveController { constructor(protected $q:ng.IQService, ConfigurationService:any, protected I18n:op.I18n) { this.text = { - uploadLabel: I18n.t('js.label_add_attachments') + uploadLabel: I18n.t('js.label_add_attachments'), + dropFiles: I18n.t('js.label_drop_files'), + dropFilesHint: I18n.t('js.label_drop_files_hint') }; ConfigurationService.api().then((settings:any) => { this.maxFileSize = settings.maximumAttachmentFileSize; @@ -66,22 +68,7 @@ function wpUploadDirective():any { return { restrict: 'AE', - template: ` -
-
`, - - transclude: true, + templateUrl: '/components/wp-attachments/wp-attachments-upload/wp-attachments-upload.html', scope: { workPackage: '=' diff --git a/frontend/app/components/wp-attachments/wp-attachments-upload/wp-attachments-upload.html b/frontend/app/components/wp-attachments/wp-attachments-upload/wp-attachments-upload.html new file mode 100644 index 0000000000..76a955e488 --- /dev/null +++ b/frontend/app/components/wp-attachments/wp-attachments-upload/wp-attachments-upload.html @@ -0,0 +1,21 @@ +
+
+
+ +

+ {{ ::text.dropFiles }}
+ {{ ::text.dropFilesHint }} +

+
+
+
diff --git a/frontend/app/components/wp-buttons/wp-details-view-button/wp-details-view-button.component.ts b/frontend/app/components/wp-buttons/wp-details-view-button/wp-details-view-button.component.ts index 92498bce0f..8dc4bf1cb8 100644 --- a/frontend/app/components/wp-buttons/wp-details-view-button/wp-details-view-button.component.ts +++ b/frontend/app/components/wp-buttons/wp-details-view-button/wp-details-view-button.component.ts @@ -27,7 +27,7 @@ // ++ import {wpButtonsModule} from '../../../angular-modules'; -import {KeepTabService} from '../../wp-panels/keep-tab/keep-tab.service'; +import {KeepTabService} from '../../wp-single-view-tabs/keep-tab/keep-tab.service'; import {States} from '../../states.service'; import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service'; import {StateService} from '@uirouter/core'; diff --git a/frontend/app/components/wp-buttons/wp-status-button/wp-status-button.controller.ts b/frontend/app/components/wp-buttons/wp-status-button/wp-status-button.component.ts similarity index 68% rename from frontend/app/components/wp-buttons/wp-status-button/wp-status-button.controller.ts rename to frontend/app/components/wp-buttons/wp-status-button/wp-status-button.component.ts index 9c742c0622..6ec1212370 100644 --- a/frontend/app/components/wp-buttons/wp-status-button/wp-status-button.controller.ts +++ b/frontend/app/components/wp-buttons/wp-status-button/wp-status-button.component.ts @@ -26,24 +26,25 @@ // See doc/COPYRIGHT.rdoc for more details. // ++ -import {wpButtonsModule} from '../../../angular-modules'; -import {WorkPackageEditingService} from '../../wp-edit-form/work-package-editing-service'; -import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service'; +import {WorkPackageResourceInterface} from 'core-components/api/api-v3/hal-resources/work-package-resource.service'; +import {WorkPackageEditingService} from 'core-components/wp-edit-form/work-package-editing-service'; +import {Component, Inject, Input} from '@angular/core'; +import {I18nToken} from 'core-app/angular4-transition-utils'; -export default class WorkPackageStatusButtonController { - public workPackage:WorkPackageResourceInterface; - public text:any; - public allowed:boolean; +@Component({ + template: require('!!raw-loader!./wp-status-button.html'), + selector: 'wp-status-button', +}) +export class WorkPackageStatusButtonComponent { + @Input('workPackage') public workPackage:WorkPackageResourceInterface; + @Input('allowed') public allowed:boolean; - constructor(protected I18n:op.I18n, - protected wpEditing:WorkPackageEditingService) { - this.text = { - explanation: I18n.t('js.label_edit_status') - }; - } + public text = { + explanation: this.I18n.t('js.label_edit_status') + }; - public $onInit() { - // Created for interface compliance + constructor(@Inject(I18nToken) readonly I18n:op.I18n, + protected wpEditing:WorkPackageEditingService) { } public isDisabled() { @@ -56,5 +57,3 @@ export default class WorkPackageStatusButtonController { return changeset.value('status'); } } - -wpButtonsModule.controller('WorkPackageStatusButtonController', WorkPackageStatusButtonController); diff --git a/frontend/app/components/wp-buttons/wp-status-button/wp-status-button.directive.html b/frontend/app/components/wp-buttons/wp-status-button/wp-status-button.directive.html deleted file mode 100644 index a92aa27b5b..0000000000 --- a/frontend/app/components/wp-buttons/wp-status-button/wp-status-button.directive.html +++ /dev/null @@ -1,14 +0,0 @@ -
- -
diff --git a/frontend/app/components/wp-buttons/wp-status-button/wp-status-button.html b/frontend/app/components/wp-buttons/wp-status-button/wp-status-button.html new file mode 100644 index 0000000000..2b18a4548b --- /dev/null +++ b/frontend/app/components/wp-buttons/wp-status-button/wp-status-button.html @@ -0,0 +1,15 @@ +
+ +
diff --git a/frontend/app/components/wp-edit/wp-edit-field/wp-replacement-label.directive.ts b/frontend/app/components/wp-edit/wp-edit-field/wp-replacement-label.component.ts similarity index 53% rename from frontend/app/components/wp-edit/wp-edit-field/wp-replacement-label.directive.ts rename to frontend/app/components/wp-edit/wp-edit-field/wp-replacement-label.component.ts index f4e05ada69..e9fe9fd00d 100644 --- a/frontend/app/components/wp-edit/wp-edit-field/wp-replacement-label.directive.ts +++ b/frontend/app/components/wp-edit/wp-edit-field/wp-replacement-label.component.ts @@ -26,13 +26,24 @@ // See doc/COPYRIGHT.rdoc for more details. // ++ -import {WorkPackageEditFieldGroupController} from './wp-edit-field-group.directive'; -export class WorkPackageReplacementLabelController { - public wpEditFieldGroup:WorkPackageEditFieldGroupController; - public fieldName:string; - constructor(protected $scope:ng.IScope, - protected $element:ng.IAugmentedJQuery) { +import {WorkPackageEditFieldGroupDirective} from 'core-components/wp-edit/wp-edit-field/wp-edit-field-group.directive'; +import {Component, ElementRef, Input, OnInit} from '@angular/core'; + +@Component({ + template: require('!!raw-loader!./wp-replacement-label.html'), + selector: 'wp-replacement-label', +}) +export class WorkPackageReplacementLabelComponent implements OnInit { + @Input('fieldName') public fieldName:string; + private $element:ng.IAugmentedJQuery; + + constructor(protected wpEditFieldGroup:WorkPackageEditFieldGroupDirective, + protected elementRef:ElementRef) { + } + + ngOnInit() { + this.$element = angular.element(this.elementRef.nativeElement); } public activate(evt:JQueryEventObject) { @@ -42,38 +53,9 @@ export class WorkPackageReplacementLabelController { return true; } - this.wpEditFieldGroup.fields[this.fieldName].handleUserActivate(null); + const field = this.wpEditFieldGroup.fields[this.fieldName]; + field && field.handleUserActivate(null); + return false; } } - -function wpReplacementLabelLink(scope:ng.IScope, - element:ng.IAugmentedJQuery, - attrs:ng.IAttributes, - controllers:[WorkPackageEditFieldGroupController, WorkPackageReplacementLabelController]) { - - controllers[1].wpEditFieldGroup = controllers[0]; -} - -function wpReplacementLabel():any { - return { - restrict: 'A', - templateUrl: '/components/wp-edit/wp-edit-field/wp-replacement-label.directive.html', - transclude: true, - - scope: { - fieldName: '=wpReplacementLabel', - }, - - require: ['^wpEditFieldGroup', 'wpReplacementLabel'], - link: wpReplacementLabelLink, - - controller: WorkPackageReplacementLabelController, - controllerAs: 'vm', - bindToController: true - }; -} - -angular - .module('openproject') - .directive('wpReplacementLabel', wpReplacementLabel); diff --git a/frontend/app/components/wp-edit/wp-edit-field/wp-replacement-label.directive.html b/frontend/app/components/wp-edit/wp-edit-field/wp-replacement-label.directive.html deleted file mode 100644 index a06ca1bc8d..0000000000 --- a/frontend/app/components/wp-edit/wp-edit-field/wp-replacement-label.directive.html +++ /dev/null @@ -1,4 +0,0 @@ - - diff --git a/frontend/app/components/wp-edit/wp-edit-field/wp-replacement-label.html b/frontend/app/components/wp-edit/wp-edit-field/wp-replacement-label.html new file mode 100644 index 0000000000..d25258fed6 --- /dev/null +++ b/frontend/app/components/wp-edit/wp-edit-field/wp-replacement-label.html @@ -0,0 +1,4 @@ + + diff --git a/frontend/app/components/wp-fast-table/builders/context-link-icon-builder.ts b/frontend/app/components/wp-fast-table/builders/context-link-icon-builder.ts index 26c17b681f..7814528b6c 100644 --- a/frontend/app/components/wp-fast-table/builders/context-link-icon-builder.ts +++ b/frontend/app/components/wp-fast-table/builders/context-link-icon-builder.ts @@ -1,6 +1,6 @@ import {Injector} from '@angular/core'; import {$stateToken, I18nToken} from 'core-app/angular4-transition-utils'; -import {KeepTabService} from 'core-components/wp-panels/keep-tab/keep-tab.service'; +import {KeepTabService} from 'core-components/wp-single-view-tabs/keep-tab/keep-tab.service'; import {opIconElement} from '../../../helpers/op-icon-builder'; import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service'; import {wpCellTdClassName} from './cell-builder'; diff --git a/frontend/app/components/wp-fast-table/builders/ui-state-link-builder.ts b/frontend/app/components/wp-fast-table/builders/ui-state-link-builder.ts index 72f85128b7..ab79903a7e 100644 --- a/frontend/app/components/wp-fast-table/builders/ui-state-link-builder.ts +++ b/frontend/app/components/wp-fast-table/builders/ui-state-link-builder.ts @@ -1,4 +1,4 @@ -import {KeepTabService} from 'core-components/wp-panels/keep-tab/keep-tab.service'; +import {KeepTabService} from 'core-components/wp-single-view-tabs/keep-tab/keep-tab.service'; import {StateService} from '@uirouter/core'; export const uiStateLinkClass = '__ui-state-link'; diff --git a/frontend/app/components/wp-fast-table/handlers/row/click-handler.ts b/frontend/app/components/wp-fast-table/handlers/row/click-handler.ts index 70d5e76a48..bdb4514b29 100644 --- a/frontend/app/components/wp-fast-table/handlers/row/click-handler.ts +++ b/frontend/app/components/wp-fast-table/handlers/row/click-handler.ts @@ -4,7 +4,7 @@ import {$stateToken} from 'core-app/angular4-transition-utils'; import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service'; import {debugLog} from '../../../../helpers/debug_output'; import {States} from '../../../states.service'; -import {KeepTabService} from '../../../wp-panels/keep-tab/keep-tab.service'; +import {KeepTabService} from '../../../wp-single-view-tabs/keep-tab/keep-tab.service'; import {tdClassName} from '../../builders/cell-builder'; import {tableRowClassName} from '../../builders/rows/single-row-builder'; import {WorkPackageTableSelection} from '../../state/wp-table-selection.service'; diff --git a/frontend/app/components/wp-fast-table/handlers/row/wp-state-links-handler.ts b/frontend/app/components/wp-fast-table/handlers/row/wp-state-links-handler.ts index 94fe7b3e3e..e466cc066b 100644 --- a/frontend/app/components/wp-fast-table/handlers/row/wp-state-links-handler.ts +++ b/frontend/app/components/wp-fast-table/handlers/row/wp-state-links-handler.ts @@ -3,7 +3,7 @@ import {$stateToken} from 'core-app/angular4-transition-utils'; import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service'; import {WorkPackageResource} from '../../../api/api-v3/hal-resources/work-package-resource.service'; import {States} from '../../../states.service'; -import {KeepTabService} from '../../../wp-panels/keep-tab/keep-tab.service'; +import {KeepTabService} from '../../../wp-single-view-tabs/keep-tab/keep-tab.service'; import {tableRowClassName} from '../../builders/rows/single-row-builder'; import {uiStateLinkClass} from '../../builders/ui-state-link-builder'; import {WorkPackageTableSelection} from '../../state/wp-table-selection.service'; diff --git a/frontend/app/components/wp-panels/overview-panel/overview-panel.directive.html b/frontend/app/components/wp-panels/overview-panel/overview-panel.directive.html deleted file mode 100644 index 47d10074fa..0000000000 --- a/frontend/app/components/wp-panels/overview-panel/overview-panel.directive.html +++ /dev/null @@ -1,11 +0,0 @@ - - -
-
-
-

{{ I18n.t('js.label_latest_activity') }}

-
-
- - -
diff --git a/frontend/app/components/wp-panels/activity-panel/activity-entry-info.ts b/frontend/app/components/wp-single-view-tabs/activity-panel/activity-entry-info.ts similarity index 100% rename from frontend/app/components/wp-panels/activity-panel/activity-entry-info.ts rename to frontend/app/components/wp-single-view-tabs/activity-panel/activity-entry-info.ts diff --git a/frontend/app/components/wp-panels/activity-panel/activity-panel-default.directive.html b/frontend/app/components/wp-single-view-tabs/activity-panel/activity-panel-default.directive.html similarity index 100% rename from frontend/app/components/wp-panels/activity-panel/activity-panel-default.directive.html rename to frontend/app/components/wp-single-view-tabs/activity-panel/activity-panel-default.directive.html diff --git a/frontend/app/components/wp-panels/activity-panel/activity-panel-overview.directive.html b/frontend/app/components/wp-single-view-tabs/activity-panel/activity-panel-overview.directive.html similarity index 100% rename from frontend/app/components/wp-panels/activity-panel/activity-panel-overview.directive.html rename to frontend/app/components/wp-single-view-tabs/activity-panel/activity-panel-overview.directive.html diff --git a/frontend/app/components/wp-panels/activity-panel/activity-panel.directive.ts b/frontend/app/components/wp-single-view-tabs/activity-panel/activity-panel.directive.ts similarity index 100% rename from frontend/app/components/wp-panels/activity-panel/activity-panel.directive.ts rename to frontend/app/components/wp-single-view-tabs/activity-panel/activity-panel.directive.ts diff --git a/frontend/app/components/wp-panels/activity-panel/wp-activity.service.ts b/frontend/app/components/wp-single-view-tabs/activity-panel/wp-activity.service.ts similarity index 100% rename from frontend/app/components/wp-panels/activity-panel/wp-activity.service.ts rename to frontend/app/components/wp-single-view-tabs/activity-panel/wp-activity.service.ts diff --git a/frontend/app/components/wp-panels/keep-tab/keep-tab.service.test.ts b/frontend/app/components/wp-single-view-tabs/keep-tab/keep-tab.service.test.ts similarity index 100% rename from frontend/app/components/wp-panels/keep-tab/keep-tab.service.test.ts rename to frontend/app/components/wp-single-view-tabs/keep-tab/keep-tab.service.test.ts diff --git a/frontend/app/components/wp-panels/keep-tab/keep-tab.service.ts b/frontend/app/components/wp-single-view-tabs/keep-tab/keep-tab.service.ts similarity index 100% rename from frontend/app/components/wp-panels/keep-tab/keep-tab.service.ts rename to frontend/app/components/wp-single-view-tabs/keep-tab/keep-tab.service.ts diff --git a/frontend/app/components/wp-single-view-tabs/overview-tab/overview-tab.component.ts b/frontend/app/components/wp-single-view-tabs/overview-tab/overview-tab.component.ts new file mode 100644 index 0000000000..401e10ed10 --- /dev/null +++ b/frontend/app/components/wp-single-view-tabs/overview-tab/overview-tab.component.ts @@ -0,0 +1,58 @@ +// -- 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 {Component, Inject, Input, OnDestroy} from '@angular/core'; +import {Transition} from '@uirouter/core'; +import {I18nToken} from '../../../angular4-transition-utils'; +import {WorkPackageCacheService} from 'core-components/work-packages/work-package-cache.service'; +import {WorkPackageResourceInterface} from 'core-components/api/api-v3/hal-resources/work-package-resource.service'; +import {componentDestroyed} from 'ng2-rx-componentdestroyed'; +@Component({ + template: require('!!raw-loader!./overview-tab.html'), + selector: 'wp-overview-tab', +}) +export class WorkPackageOverviewTabComponent implements OnDestroy { + public workPackageId:string; + public workPackage:WorkPackageResourceInterface; + public tabName = this.I18n.t('js.label_latest_activity'); + + public constructor(@Inject(I18nToken) readonly I18n:op.I18n, + readonly $transition:Transition, + readonly wpCacheService:WorkPackageCacheService) { + + this.workPackageId = this.$transition.params('to').workPackageId; + wpCacheService.loadWorkPackage(this.workPackageId) + .values$() + .takeUntil(componentDestroyed(this)) + .subscribe((wp) => this.workPackage = wp); + } + + ngOnDestroy() { + // Nothing to do + } +} diff --git a/frontend/app/components/wp-single-view-tabs/overview-tab/overview-tab.html b/frontend/app/components/wp-single-view-tabs/overview-tab/overview-tab.html new file mode 100644 index 0000000000..2d8fbe5e43 --- /dev/null +++ b/frontend/app/components/wp-single-view-tabs/overview-tab/overview-tab.html @@ -0,0 +1,13 @@ + + +
+
+
+

+
+
+ + + +
diff --git a/frontend/app/components/wp-panels/relations-panel/relations-panel.directive.html b/frontend/app/components/wp-single-view-tabs/relations-panel/relations-panel.directive.html similarity index 100% rename from frontend/app/components/wp-panels/relations-panel/relations-panel.directive.html rename to frontend/app/components/wp-single-view-tabs/relations-panel/relations-panel.directive.html diff --git a/frontend/app/components/wp-panels/relations-panel/relations-panel.directive.ts b/frontend/app/components/wp-single-view-tabs/relations-panel/relations-panel.directive.ts similarity index 100% rename from frontend/app/components/wp-panels/relations-panel/relations-panel.directive.ts rename to frontend/app/components/wp-single-view-tabs/relations-panel/relations-panel.directive.ts diff --git a/frontend/app/components/wp-panels/watchers-panel/watchers-panel.controller.ts b/frontend/app/components/wp-single-view-tabs/watchers-panel/watchers-panel.controller.ts similarity index 100% rename from frontend/app/components/wp-panels/watchers-panel/watchers-panel.controller.ts rename to frontend/app/components/wp-single-view-tabs/watchers-panel/watchers-panel.controller.ts diff --git a/frontend/app/components/wp-panels/watchers-panel/watchers-panel.directive.html b/frontend/app/components/wp-single-view-tabs/watchers-panel/watchers-panel.directive.html similarity index 100% rename from frontend/app/components/wp-panels/watchers-panel/watchers-panel.directive.html rename to frontend/app/components/wp-single-view-tabs/watchers-panel/watchers-panel.directive.html diff --git a/frontend/app/components/wp-panels/watchers-panel/watchers-panel.directive.test.js b/frontend/app/components/wp-single-view-tabs/watchers-panel/watchers-panel.directive.test.js similarity index 100% rename from frontend/app/components/wp-panels/watchers-panel/watchers-panel.directive.test.js rename to frontend/app/components/wp-single-view-tabs/watchers-panel/watchers-panel.directive.test.js diff --git a/frontend/app/components/wp-panels/watchers-panel/watchers-panel.directive.ts b/frontend/app/components/wp-single-view-tabs/watchers-panel/watchers-panel.directive.ts similarity index 100% rename from frontend/app/components/wp-panels/watchers-panel/watchers-panel.directive.ts rename to frontend/app/components/wp-single-view-tabs/watchers-panel/watchers-panel.directive.ts diff --git a/frontend/app/components/wp-watchers/wp-watcher-entry.directive.ts b/frontend/app/components/wp-watchers/wp-watcher-entry.directive.ts index 9e6369a606..985bbe060e 100644 --- a/frontend/app/components/wp-watchers/wp-watcher-entry.directive.ts +++ b/frontend/app/components/wp-watchers/wp-watcher-entry.directive.ts @@ -26,7 +26,7 @@ // See doc/COPYRIGHT.rdoc for more details. //++ -import {WatchersPanelController} from '../wp-panels/watchers-panel/watchers-panel.controller'; +import {WatchersPanelController} from '../wp-single-view-tabs/watchers-panel/watchers-panel.controller'; import {UserResource} from '../api/api-v3/hal-resources/user-resource.service'; import {wpDirectivesModule} from '../../angular-modules'; diff --git a/frontend/app/templates/components/authoring.html b/frontend/app/templates/components/authoring.html deleted file mode 100644 index f42dce4e73..0000000000 --- a/frontend/app/templates/components/authoring.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/app/ui_components/authoring-directive.js b/frontend/app/ui_components/authoring-directive.js deleted file mode 100644 index edadd3a7ed..0000000000 --- a/frontend/app/ui_components/authoring-directive.js +++ /dev/null @@ -1,67 +0,0 @@ -//-- copyright -// OpenProject is a project management system. -// Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details. -//++ - -// TODO move to UI components -module.exports = function(I18n, PathHelper, TimezoneService) { - return { - restrict: 'E', - replace: true, - scope: { createdOn: '=', author: '=', showAuthorAsLink: '=', project: '=', activity: '=' }, - templateUrl: '/templates/components/authoring.html', - link: function(scope, element, attrs) { - moment.locale(I18n.locale); - - var createdOn = TimezoneService.parseDatetime(scope.createdOn); - var timeago = createdOn.fromNow(); - var time = createdOn.format('LLL'); - - function activityFromPath(project, from) { - var path = PathHelper.projectActivityPath(project); - - if (from) { - path += '?from=' + from; - } - - return path; - } - - scope.I18n = I18n; - if(scope.showAuthorAsLink === false) { - scope.authorLink = '' + scope.author.name + ''; - } else { - scope.authorLink = '' + scope.author.name + ''; - } - - if (scope.activity) { - scope.timestamp = '' + timeago + ''; - } else { - scope.timestamp = '' + timeago + ''; - } - } - }; -}; diff --git a/frontend/app/ui_components/index.js b/frontend/app/ui_components/index.js index 5b5c38fe3a..bbb946c73f 100644 --- a/frontend/app/ui_components/index.js +++ b/frontend/app/ui_components/index.js @@ -31,8 +31,6 @@ angular.module('openproject.uiComponents') './accessible-by-keyboard-directive')]) .directive('accessibleCheckbox', [require('./accessible-checkbox-directive')]) .directive('accessibleElement', [require('./accessible-element-directive')]) - .directive('authoring', ['I18n', 'PathHelper', 'TimezoneService', require( - './authoring-directive')]) .directive('copyToClipboard', [ 'I18n', '$timeout', @@ -42,7 +40,6 @@ angular.module('openproject.uiComponents') ]) .directive('opDate', ['TimezoneService', require('./date/date-directive')]) .directive('opTime', ['TimezoneService', require('./date/time-directive')]) - .directive('opDateTime', ['$compile', 'TimezoneService', require('./date/date-time-directive')]) .directive('emptyElement', [require('./empty-element-directive')]) .constant('ENTER_KEY', 13) .directive('expandableSearch', ['ENTER_KEY', require('./expandable-search')])