pull/6274/head
commit
5535490cc2
@ -0,0 +1,61 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- 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. |
||||
#++ |
||||
|
||||
module MetaTagsHelper |
||||
|
||||
## |
||||
# Use meta-tags to output title and site name |
||||
def output_title_and_meta_tags |
||||
display_meta_tags site: Setting.app_title, |
||||
title: html_title_parts, |
||||
separator: ' | ', # Update the TitleService when changing this! |
||||
reverse: true |
||||
end |
||||
|
||||
## |
||||
# Writer of html_title as string |
||||
def html_title(*args) |
||||
title = [] |
||||
|
||||
raise "Don't use html_title getter" if args.empty? |
||||
|
||||
@html_title ||= [] |
||||
@html_title += args |
||||
end |
||||
|
||||
## |
||||
# The html title parts currently defined |
||||
def html_title_parts |
||||
[].tap do |parts| |
||||
parts << h(@project.name) if @project |
||||
parts.concat @html_title.map(&:to_s) if @html_title |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,27 @@ |
||||
import {Title} from "@angular/platform-browser"; |
||||
import {Injectable} from "@angular/core"; |
||||
|
||||
const titlePartsSeparator = ' | '; |
||||
|
||||
@Injectable() |
||||
export class OpTitleService { |
||||
constructor(private titleService:Title) { |
||||
|
||||
} |
||||
|
||||
public get current():string { |
||||
return this.titleService.getTitle(); |
||||
} |
||||
|
||||
public get titleParts():string[] { |
||||
return this.current.split(titlePartsSeparator); |
||||
} |
||||
|
||||
public setFirstPart(value:string) { |
||||
let parts = this.titleParts; |
||||
parts[0] = value; |
||||
|
||||
this.titleService.setTitle(parts.join(titlePartsSeparator)); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,69 @@ |
||||
//-- 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, |
||||
EventEmitter, |
||||
Inject, |
||||
Injector, |
||||
Input, |
||||
OnChanges, |
||||
OnDestroy, |
||||
OnInit, |
||||
Output, |
||||
SimpleChanges |
||||
} from '@angular/core'; |
||||
import {UpgradeComponent} from '@angular/upgrade/static'; |
||||
|
||||
@Directive({selector: 'ng1-wp-field-controls-wrapper'}) |
||||
export class Ng1FieldControlsWrapper extends UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy { |
||||
@Input() public fieldController:any; |
||||
@Input() public cancelTitle:string; |
||||
@Input() public saveTitle:string; |
||||
@Output() public onSave = new EventEmitter<undefined>(); |
||||
@Output() public onCancel = new EventEmitter<undefined>(); |
||||
|
||||
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('wpEditFieldControls', 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(); } |
||||
} |
||||
|
@ -1,26 +0,0 @@ |
||||
// 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 |
||||
} from '@angular/core'; |
||||
import {UpgradeComponent} from '@angular/upgrade/static'; |
||||
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; |
||||
|
||||
@Directive({selector: 'ng1-wp-relations-wrapper'}) |
||||
export class Ng1RelationsDirectiveWrapper extends UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy { |
||||
@Input('workPackage') workPackage:WorkPackageResource; |
||||
|
||||
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('wpRelations', elementRef, injector); |
||||
} |
||||
} |
||||
|
@ -1,95 +1,96 @@ |
||||
<div class="relation-row" |
||||
ng-class="['relation-row-{{ $ctrl.relatedWorkPackage.id }}']" |
||||
<div *ngIf="workPackage && relatedWorkPackage" |
||||
class="relation-row relation-row-{{ relatedWorkPackage.id }}" |
||||
focus-within="'.wp-relations-controls-section accessible-by-keyboard'"> |
||||
<div class="grid-block hierarchy-item"> |
||||
|
||||
<div class="grid-content medium-3 collapse"> |
||||
<span class="relation-row--type" |
||||
ng-click="$ctrl.activateRelationTypeEdit()" |
||||
(accessibleClick)="activateRelationTypeEdit()" |
||||
tabindex="0" |
||||
ng-if="!$ctrl.userInputs.showRelationTypesForm"> |
||||
*ngIf="!userInputs.showRelationTypesForm"> |
||||
|
||||
|
||||
<span ng-if="$ctrl.groupByWorkPackageType" |
||||
ng-bind="$ctrl.normalizedRelationType"></span> |
||||
<span ng-if="!$ctrl.groupByWorkPackageType" |
||||
ng-bind="$ctrl.relatedWorkPackage.type.name"></span> |
||||
<span class="hidden-for-sighted" ng-bind="::$ctrl.text.updateRelation"></span> |
||||
<span *ngIf="groupByWorkPackageType" |
||||
[textContent]="normalizedRelationType"></span> |
||||
<span *ngIf="!groupByWorkPackageType" |
||||
[textContent]="relatedWorkPackage.type.name"></span> |
||||
<span class="hidden-for-sighted" [textContent]="text.updateRelation"></span> |
||||
</span> |
||||
<div class="wp-edit-field inplace-edit" |
||||
ng-if="$ctrl.userInputs.showRelationTypesForm"> |
||||
*ngIf="userInputs.showRelationTypesForm"> |
||||
<select class="wp-inline-edit--field form--select" |
||||
ng-model="$ctrl.selectedRelationType" |
||||
[(ngModel)]="selectedRelationType" |
||||
(change)="saveRelationType()" |
||||
role="listbox" |
||||
focus |
||||
ng-keydown="$ctrl.cancelRelationTypeEditOnEscape($event)" |
||||
ng-options="relationType as relationType.label for relationType in $ctrl.availableRelationTypes track by relationType.name" |
||||
ng-change="$ctrl.saveRelationType()"></select> |
||||
(keydown.escape)="cancelRelationTypeEditOnEscape($event)"> |
||||
<option *ngFor="let relationType of availableRelationTypes" |
||||
[textContent]="relationType.label" |
||||
[ngValue]="relationType"></option> |
||||
</select> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="grid-content medium-5 collapse" wp-single-relation |
||||
ng-if="$ctrl.relatedWorkPackage"> |
||||
<a ui-sref="work-packages.show.relations({ workPackageId: $ctrl.relatedWorkPackage.id})" |
||||
<div class="grid-content medium-5 collapse" |
||||
*ngIf="relatedWorkPackage"> |
||||
<a uiSref="work-packages.show.relations" |
||||
[uiParams]="{ workPackageId: relatedWorkPackage.id }" |
||||
class="wp-relations--subject-field" |
||||
aria-label="{{ $ctrl.normalizedRelationType + ' ' + singleRelationCtrl.getFullIdentifier($ctrl.relatedWorkPackage, true) }}"> |
||||
{{ singleRelationCtrl.getFullIdentifier($ctrl.relatedWorkPackage, true) }} |
||||
[textContent]="relatedWorkPackage.subjectWithType()" |
||||
[attr.aria-label]="normalizedRelationType + ' ' + relatedWorkPackage.subjectWithType()"> |
||||
</a> |
||||
</div> |
||||
|
||||
<div class="grid-content medium-2 collapse wp-relations-status-field"> |
||||
<wp-edit-field-group-ng1 work-package="$ctrl.relatedWorkPackage"> |
||||
<div wp-edit-form="$ctrl.relatedWorkPackage" ng-if="$ctrl.relatedWorkPackage"> |
||||
<wp-edit-field work-package-id="$ctrl.relatedWorkPackage.id" |
||||
field-name="'status'"> |
||||
</wp-edit-field> |
||||
</div> |
||||
</wp-edit-field-group-ng1> |
||||
<wp-edit-field-group *ngIf="relatedWorkPackage" [workPackage]="relatedWorkPackage"> |
||||
<wp-edit-field [workPackageId]="relatedWorkPackage.id" fieldName="status"></wp-edit-field> |
||||
</wp-edit-field-group> |
||||
</div> |
||||
|
||||
<div class="grid-content medium-2 collapse wp-relations-controls-section" |
||||
ng-class="{'-expanded': $ctrl.userInputs.showRelationInfo }"> |
||||
<accessible-by-keyboard link-aria-label="{{ ::$ctrl.text.description_label }}" |
||||
link-title="{{ ::$ctrl.text.description_label }}" |
||||
link-class="wp-relations--description-btn" |
||||
ng-class="{'-visible': $ctrl.showDescriptionInfo }" |
||||
execute="$ctrl.userInputs.showRelationInfo = !$ctrl.userInputs.showRelationInfo"> |
||||
<op-icon icon-classes="icon-info1 wp-relations--icon wp-relations--description-icon" icon-title="{{ ::$ctrl.text.toggleDescription }}"></op-icon> |
||||
ng-class="{'-expanded': userInputs.showRelationInfo }"> |
||||
<accessible-by-keyboard [linkAriaLabel]="text.description_label" |
||||
[linkTitle]="text.description_label" |
||||
linkClass="wp-relations--description-btn" |
||||
[ngClass]="{'-visible': showDescriptionInfo }" |
||||
(execute)="userInputs.showRelationInfo = !userInputs.showRelationInfo"> |
||||
<op-icon icon-classes="icon-info1 wp-relations--icon wp-relations--description-icon" |
||||
icon-title="text.toggleDescription"></op-icon> |
||||
</accessible-by-keyboard> |
||||
<accessible-by-keyboard ng-if="$ctrl.relation.delete" |
||||
execute="$ctrl.removeRelation($ctrl.relation)" |
||||
<accessible-by-keyboard *ngIf="relation.delete" |
||||
(execute)="removeRelation(relation)" |
||||
aria-hidden="false" |
||||
link-aria-label="{{ ::$ctrl.text.remove }}" |
||||
link-title="{{ ::$ctrl.text.remove }}" |
||||
link-class="relation-row--remove-btn"> |
||||
<op-icon icon-classes="icon-remove wp-relations--icon" icon-title="{{ ::$ctrl.text.removeButton }}"></op-icon> |
||||
[linkAriaLabel]="text.remove" |
||||
[linkTitle]="text.remove" |
||||
linkClass="relation-row--remove-btn"> |
||||
<op-icon icon-classes="icon-remove wp-relations--icon" [icon-title]="text.removeButton"></op-icon> |
||||
</accessible-by-keyboard> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="grid-block hierarchy-item description-container" |
||||
ng-show="$ctrl.userInputs.showRelationInfo"> |
||||
*ngIf="userInputs.showRelationInfo"> |
||||
<div class="wp-relation--description-read-value" |
||||
ng-class="{'-placeholder': !$ctrl.relation.description }" |
||||
ng-click="$ctrl.startDescriptionEdit()" |
||||
ng-hide="$ctrl.userInputs.showDescriptionEditForm" |
||||
ng-bind="$ctrl.relation.description || $ctrl.text.placeholder.description"> |
||||
[ngClass]="{'-placeholder': !relation.description }" |
||||
(accessibleClick)="startDescriptionEdit()" |
||||
*ngIf="!userInputs.showDescriptionEditForm" |
||||
[textContent]="relation.description || text.placeholder.description"> |
||||
</div> |
||||
<div class="wp-relation--description-wrapper textarea-wrapper" |
||||
ng-show="$ctrl.userInputs.showDescriptionEditForm"> |
||||
*ngIf="userInputs.showDescriptionEditForm"> |
||||
<textarea |
||||
msd-elastic="\n" |
||||
#relationDescriptionTextarea |
||||
autofocus |
||||
class="wp-relation--description-textarea" |
||||
name="description" |
||||
ng-keyup="$ctrl.handleDescriptionKey($event)" |
||||
ng-model="$ctrl.userInputs.newRelationText"></textarea> |
||||
<wp-edit-field-controls field-controller="$ctrl.fieldController" |
||||
on-save="$ctrl.saveDescription()" |
||||
on-cancel="$ctrl.cancelDescriptionEdit()" |
||||
save-title="{{ vm.field.text.save }}" |
||||
cancel-title="{{ vm.field.text.cancel }}"> |
||||
</wp-edit-field-controls> |
||||
(keyup)="handleDescriptionKey($event)" |
||||
[(ngModel)]="userInputs.newRelationText"></textarea> |
||||
<ng1-wp-field-controls-wrapper [fieldController]="fieldController" |
||||
(onSave)="saveDescription()" |
||||
(onCancel)="cancelDescriptionEdit()" |
||||
[saveTitle]="text.save" |
||||
[cancelTitle]="text.cancel"> |
||||
</ng1-wp-field-controls-wrapper> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
@ -1,27 +0,0 @@ |
||||
<div class="loading-indicator--location" |
||||
data-indicator-name="relationAddFixed"> |
||||
<div class="grid-block v-align collapse wp-relations-create" |
||||
ng-if="$ctrl.showRelationsCreateForm || $ctrl.externalFormToggle"> |
||||
<div class="grid-content medium-10 collapse"> |
||||
<wp-relations-autocomplete |
||||
selected-wp-id="$ctrl.selectedWpId" |
||||
loading-promise-name="relationAddFixed" |
||||
selected-relation-type="$ctrl.selectedRelationType" |
||||
focus> |
||||
</wp-relations-autocomplete> |
||||
</div> |
||||
<div class="grid-content medium-2 collapse wp-relations-controls-section relation-row"> |
||||
<accessible-by-keyboard |
||||
execute="$ctrl.createRelation()" |
||||
is-disabled="$ctrl.isDisabled" |
||||
aria-hidden="false"> |
||||
<op-icon icon-classes="icon-checkmark" icon-title="{{ ::$ctrl.text.save }}"></op-icon> |
||||
</accessible-by-keyboard> |
||||
<accessible-by-keyboard |
||||
execute="$ctrl.toggleRelationsCreateForm()" |
||||
aria-hidden="false"> |
||||
<op-icon icon-classes="icon-remove" icon-title="{{ ::$ctrl.text.abort }}"></op-icon> |
||||
</accessible-by-keyboard> |
||||
</div> |
||||
</div> |
||||
</div> |
@ -1,58 +0,0 @@ |
||||
<div class="wp-relations-create"> |
||||
<div class="wp-relations-create-button hide-when-print" |
||||
ng-if="!$ctrl.showRelationsCreateForm"> |
||||
<div class="grid-block"> |
||||
<div class="grid-content collapse wp-inline-create-button"> |
||||
<a class="wp-inline-create--add-link relation-create -focus-after-save" |
||||
ng-click="$ctrl.toggleRelationsCreateForm()" |
||||
href |
||||
id="relation--add-relation"> |
||||
<op-icon icon-classes="icon icon-add"></op-icon> |
||||
<span>{{ ::$ctrl.text.addNewRelation }}</span> |
||||
</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="loading-indicator--location" |
||||
data-indicator-name="relationAddDynamic"> |
||||
<div class="v-align wp-relations-create--form" ng-if="$ctrl.showRelationsCreateForm"> |
||||
<div class="grid-content collapse medium-3"> |
||||
<label class="hidden-for-sighted" for="relation-type--select">{{ ::$ctrl.text.relationType }}</label> |
||||
<select class="form--select relationTypeSelect" |
||||
id="relation-type--select" |
||||
role="listbox" |
||||
ng-model="$ctrl.selectedRelationType" |
||||
ng-options="type.name as type.label for type in $ctrl.relationTypes" |
||||
focus> |
||||
</select> |
||||
</div> |
||||
<div class="grid-content medium-7"> |
||||
<wp-relations-autocomplete |
||||
work-package="$ctrl.workPackage" |
||||
loading-promise-name="relationAddDynamic" |
||||
selected-wp-id="$ctrl.selectedWpId" |
||||
selected-relation-type="$ctrl.selectedRelationType"> |
||||
</wp-relations-autocomplete> |
||||
</div> |
||||
<div class="grid-content medium-2 collapse wp-relations-controls-section relation-row"> |
||||
<accessible-by-keyboard |
||||
execute="$ctrl.createRelation()" |
||||
is-disabled="$ctrl.isDisabled || !$ctrl.selectedWpId" |
||||
link-class="wp-create-relation--save" |
||||
aria-hidden="false"> |
||||
<op-icon icon-classes="icon-checkmark" icon-title="{{ ::$ctrl.text.save }}"></op-icon> |
||||
</accessible-by-keyboard> |
||||
<accessible-by-keyboard |
||||
execute="$ctrl.toggleRelationsCreateForm()" |
||||
link-class="wp-create-relation--cancel" |
||||
aria-hidden="false"> |
||||
<op-icon icon-classes="icon-remove" icon-title="{{ ::$ctrl.text.abort }}"></op-icon> |
||||
</accessible-by-keyboard> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
|
||||
|
||||
|
@ -1,28 +0,0 @@ |
||||
// 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 |
||||
} from '@angular/core'; |
||||
import {UpgradeComponent} from '@angular/upgrade/static'; |
||||
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; |
||||
|
||||
@Directive({selector: 'ng1-wp-relations-create'}) |
||||
export class Ng1RelationsCreateWrapper extends UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy { |
||||
@Input('workPackage') workPackage:WorkPackageResource; |
||||
@Input('fixedRelationType') fixedRelationType:string; |
||||
@Input('externalFormToggle') externalFormToggle:boolean; |
||||
|
||||
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('wpRelationsCreate', elementRef, injector); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,64 @@ |
||||
<div class="wp-relations-create"> |
||||
<div class="wp-relations-create-button hide-when-print" |
||||
*ngIf="!showRelationsCreateForm"> |
||||
<div class="grid-block"> |
||||
<div class="grid-content collapse wp-inline-create-button"> |
||||
<a class="wp-inline-create--add-link relation-create" |
||||
#focusAfterSave |
||||
(accessibleClick)="toggleRelationsCreateForm()" |
||||
href |
||||
id="relation--add-relation"> |
||||
<op-icon icon-classes="icon icon-add"></op-icon> |
||||
<span [textContent]="text.addNewRelation"></span> |
||||
</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="loading-indicator--location" |
||||
data-indicator-name="relationAddDynamic"> |
||||
<div class="v-align wp-relations-create--form" |
||||
*ngIf="showRelationsCreateForm"> |
||||
<div class="grid-content collapse medium-3"> |
||||
<label class="hidden-for-sighted" |
||||
for="relation-type--select" |
||||
[textContent]="text.relationType"></label> |
||||
<select class="form--select relationTypeSelect" |
||||
id="relation-type--select" |
||||
role="listbox" |
||||
[(ngModel)]="selectedRelationType" |
||||
focus> |
||||
<option *ngFor="let type of relationTypes" |
||||
[textContent]="type.label" |
||||
[value]="type.name"></option> |
||||
</select> |
||||
</div> |
||||
<div class="grid-content medium-7"> |
||||
<wp-relations-autocomplete-upgraded |
||||
[workPackage]="workPackage" |
||||
(onWorkPackageIdSelected)="updateSelectedId($event)" |
||||
[selectedRelationType]="parent" |
||||
loadingPromiseName="relationAddDynamic"> |
||||
</wp-relations-autocomplete-upgraded> |
||||
</div> |
||||
<div class="grid-content medium-2 collapse wp-relations-controls-section relation-row"> |
||||
<accessible-by-keyboard |
||||
(execute)="createRelation()" |
||||
[isDisabled]="isDisabled || !selectedWpId" |
||||
linkClass="wp-create-relation--save" |
||||
aria-hidden="false"> |
||||
<op-icon icon-classes="icon-checkmark" [icon-title]="text.save"></op-icon> |
||||
</accessible-by-keyboard> |
||||
<accessible-by-keyboard |
||||
(execute)="toggleRelationsCreateForm()" |
||||
linkClass="wp-create-relation--cancel" |
||||
aria-hidden="false"> |
||||
<op-icon icon-classes="icon-remove" [icon-title]="text.abort"></op-icon> |
||||
</accessible-by-keyboard> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
|
||||
|
||||
|
@ -1,113 +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 {wpDirectivesModule} from '../../../../angular-modules'; |
||||
import {CollectionResource} from 'core-app/modules/hal/resources/collection-resource'; |
||||
import {LoadingIndicatorService} from '../../../common/loading-indicator/loading-indicator.service'; |
||||
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; |
||||
|
||||
function wpRelationsAutocompleteDirective( |
||||
$q:ng.IQService, |
||||
PathHelper:any, |
||||
$http:ng.IHttpService, |
||||
loadingIndicator:LoadingIndicatorService, |
||||
I18n:op.I18n) { |
||||
return { |
||||
restrict: 'E', |
||||
templateUrl: '/components/wp-relations/wp-relations-create/wp-relations-autocomplete/wp-relations-autocomplete.template.html', |
||||
scope: { |
||||
selectedWpId: '=', |
||||
loadingPromiseName: '@', |
||||
selectedRelationType: '=', |
||||
filterCandidatesFor: '@', |
||||
workPackage: '=' |
||||
}, |
||||
link: function (scope:any, element:ng.IAugmentedJQuery, attrs:ng.IAttributes) { |
||||
scope.text = { |
||||
placeholder: I18n.t('js.relations_autocomplete.placeholder') |
||||
}; |
||||
scope.options = []; |
||||
scope.relatedWps = []; |
||||
|
||||
let input = jQuery('.wp-relations--autocomplete'); |
||||
let selected = false; |
||||
|
||||
input.autocomplete({ |
||||
delay: 250, |
||||
autoFocus: false, // Accessibility!
|
||||
appendTo: '.detail-panel--autocomplete-target', |
||||
source: (request:{ term:string }, response:Function) => { |
||||
autocompleteWorkPackages(request.term).then((values) => { |
||||
selected = false; |
||||
response(values.map(wp => { |
||||
return { workPackage: wp, value: getIdentifier(wp) }; |
||||
})); |
||||
}); |
||||
}, |
||||
select: (evt, ui:any) => { |
||||
scope.$evalAsync(() => { |
||||
selected = true; |
||||
scope.selectedWpId = ui.item.workPackage.id; |
||||
}); |
||||
}, |
||||
minLength: 0 |
||||
}).focus(() => !selected && input.autocomplete('search', input.val())); |
||||
|
||||
function getIdentifier(workPackage:WorkPackageResource):string { |
||||
if (workPackage) { |
||||
return `#${workPackage.id} - ${workPackage.subject}`; |
||||
} else { |
||||
return ''; |
||||
} |
||||
} |
||||
|
||||
async function autocompleteWorkPackages(query:string):Promise<WorkPackageResource[]> { |
||||
element.find('.ui-autocomplete--loading').show(); |
||||
return scope.workPackage.available_relation_candidates.$link.$fetch({ |
||||
query: query, |
||||
type: scope.filterCandidatesFor || scope.selectedRelationType |
||||
}).then((collection:CollectionResource) => { |
||||
scope.noResults = collection.count === 0; |
||||
element.find('.ui-autocomplete--loading').hide(); |
||||
return collection.elements || []; |
||||
}).catch(() => { |
||||
element.find('.ui-autocomplete--loading').hide(); |
||||
return []; |
||||
}); |
||||
}; |
||||
|
||||
scope.$watch('noResults', (noResults:boolean) => { |
||||
if (noResults) { |
||||
scope.selectedWpId = null; |
||||
} |
||||
}); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
wpDirectivesModule.directive('wpRelationsAutocomplete', wpRelationsAutocompleteDirective); |
@ -1,15 +0,0 @@ |
||||
<form name="add_relation_form" class="form"> |
||||
<input type="text" |
||||
class="wp-relations--autocomplete ui-autocomplete--input" |
||||
ng-class="{ '-error': noResults }" |
||||
placeholder="{{ ::text.placeholder }}"> |
||||
<div class="ui-autocomplete--loading" style="display: none"> |
||||
<div class="loading-indicator -small"> |
||||
<div class="block-1"></div> |
||||
<div class="block-2"></div> |
||||
<div class="block-3"></div> |
||||
<div class="block-4"></div> |
||||
<div class="block-5"></div> |
||||
</div> |
||||
</div> |
||||
</form> |
@ -1,86 +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 {wpDirectivesModule} from '../../../angular-modules'; |
||||
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; |
||||
import {WorkPackageRelationsController} from '../wp-relations.directive'; |
||||
|
||||
|
||||
export class WorkPackageRelationsGroupController { |
||||
public relatedWorkPackages:Array<WorkPackageResource>; |
||||
public workPackage:WorkPackageResource; |
||||
public header:string; |
||||
public firstGroup:boolean; |
||||
public groupByWorkPackageType:boolean; |
||||
public text:Object; |
||||
public relationsCtrl: WorkPackageRelationsController; |
||||
|
||||
constructor(public $element:ng.IAugmentedJQuery, |
||||
public $timeout:ng.ITimeoutService, |
||||
public I18n:op.I18n) { |
||||
this.text = { |
||||
groupByType: I18n.t('js.relation_buttons.group_by_wp_type'), |
||||
groupByRelation: I18n.t('js.relation_buttons.group_by_relation_type') |
||||
}; |
||||
} |
||||
|
||||
public toggleButton() { |
||||
this.relationsCtrl.toggleGroupBy(); |
||||
this.$timeout(() => { |
||||
this.$element.find('#wp-relation-group-by-toggle').focus(); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
function wpRelationsGroupDirective():any { |
||||
return { |
||||
restrict: 'E', |
||||
templateUrl: '/components/wp-relations/wp-relations-group/wp-relations-group.template.html', |
||||
|
||||
scope: { |
||||
header: '=', |
||||
firstGroup: '=', |
||||
workPackage: '=', |
||||
groupByWorkPackageType: '=', |
||||
relatedWorkPackages: '=' |
||||
}, |
||||
|
||||
link: (scope:any, |
||||
element:ng.IAugmentedJQuery, |
||||
attrs:any, |
||||
controllers: [WorkPackageRelationsController]) => { |
||||
scope.$ctrl.relationsCtrl = controllers[0]; |
||||
}, |
||||
controller: WorkPackageRelationsGroupController, |
||||
controllerAs: '$ctrl', |
||||
require: ['^wpRelations'], |
||||
bindToController: true, |
||||
}; |
||||
} |
||||
|
||||
wpDirectivesModule.directive('wpRelationsGroup', wpRelationsGroupDirective); |
@ -1,28 +1,30 @@ |
||||
<div class="attributes-group"> |
||||
<div class="attributes-group--header"> |
||||
<div class="attributes-group--header-container"> |
||||
<h3 class="attributes-group--header-text relation-group--header"> |
||||
{{ $ctrl.header }} |
||||
<h3 class="attributes-group--header-text relation-group--header" |
||||
[textContent]="header"> |
||||
</h3> |
||||
</div> |
||||
<div class="attributes-group--header-toggle" ng-if="$ctrl.firstGroup"> |
||||
<div class="attributes-group--header-toggle" |
||||
*ngIf="firstGroup"> |
||||
<div id="wp-relation-group-by-toggle" |
||||
#wpRelationGroupByToggler |
||||
class="panel-toggler ng-scope ng-isolate-scope hide-when-print"> |
||||
<accessible-by-keyboard link-class="icon-context icon-small icon-group-by button -transparent" |
||||
execute="$ctrl.toggleButton()"> |
||||
<span ng-if="!$ctrl.groupByWorkPackageType" ng-bind="::$ctrl.text.groupByType"></span> |
||||
<span ng-if="$ctrl.groupByWorkPackageType" ng-bind="::$ctrl.text.groupByRelation"></span> |
||||
<accessible-by-keyboard linkClass="icon-context icon-small icon-group-by button -transparent" |
||||
(execute)="toggleButton()"> |
||||
<span [textContent]="togglerText"></span> |
||||
</accessible-by-keyboard> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="content" ng-if="$ctrl.relatedWorkPackages"> |
||||
<div class="content" |
||||
*ngIf="relatedWorkPackages"> |
||||
<wp-relation-row |
||||
work-package="$ctrl.workPackage" |
||||
group-by-work-package-type="$ctrl.groupByWorkPackageType" |
||||
related-work-package="relatedWorkPackage" |
||||
ng-repeat="relatedWorkPackage in $ctrl.relatedWorkPackages"></wp-relation-row> |
||||
*ngFor="let relatedWorkPackage of relatedWorkPackages" |
||||
[workPackage]="workPackage" |
||||
[groupByWorkPackageType]="groupByWorkPackageType" |
||||
[relatedWorkPackage]="relatedWorkPackage"></wp-relation-row> |
||||
</div> |
||||
</div> |
||||
|
||||
|
@ -1,112 +0,0 @@ |
||||
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; |
||||
import {wpDirectivesModule} from '../../../angular-modules'; |
||||
import {WorkPackageRelationsHierarchyService} from '../wp-relations-hierarchy/wp-relations-hierarchy.service'; |
||||
import {WorkPackageCacheService} from '../../work-packages/work-package-cache.service'; |
||||
import {WorkPackageNotificationService} from '../../wp-edit/wp-notification.service'; |
||||
import {scopedObservable} from '../../../helpers/angular-rx-utils'; |
||||
import {PathHelperService} from 'core-components/common/path-helper/path-helper.service'; |
||||
|
||||
class WpRelationsHierarchyRowDirectiveController { |
||||
public workPackage:WorkPackageResource; |
||||
public relatedWorkPackage:WorkPackageResource; |
||||
public relationType:any; |
||||
public showEditForm:boolean = false; |
||||
public workPackagePath = this.PathHelper.workPackagePath.bind(this.PathHelper); |
||||
public canModifyHierarchy:boolean = false; |
||||
|
||||
constructor(protected $scope:ng.IScope, |
||||
protected $timeout:ng.ITimeoutService, |
||||
protected wpRelationsHierarchyService:WorkPackageRelationsHierarchyService, |
||||
protected wpCacheService:WorkPackageCacheService, |
||||
protected wpNotificationsService:WorkPackageNotificationService, |
||||
protected PathHelper:PathHelperService, |
||||
protected I18n:op.I18n, |
||||
protected $q:ng.IQService) { |
||||
|
||||
this.canModifyHierarchy = !!this.workPackage.changeParent; |
||||
|
||||
if (this.relatedWorkPackage) { |
||||
scopedObservable($scope, this.wpCacheService.state(this.relatedWorkPackage.id).values$()) |
||||
.subscribe((wp) => this.relatedWorkPackage = wp); |
||||
} |
||||
} |
||||
|
||||
public text = { |
||||
change_parent:this.I18n.t('js.relation_buttons.change_parent'), |
||||
remove_parent:this.I18n.t('js.relation_buttons.remove_parent'), |
||||
remove_child:this.I18n.t('js.relation_buttons.remove_child'), |
||||
remove:this.I18n.t('js.relation_buttons.remove'), |
||||
parent:this.I18n.t('js.relation_labels.parent'), |
||||
children:this.I18n.t('js.relation_labels.children') |
||||
}; |
||||
|
||||
public get relationReady() { |
||||
return this.relatedWorkPackage && this.relatedWorkPackage.$loaded; |
||||
} |
||||
|
||||
public get relationClassName() { |
||||
if (this.isCurrentElement()) { |
||||
return 'self'; |
||||
} |
||||
|
||||
return this.relationType; |
||||
} |
||||
|
||||
public removeRelation() { |
||||
if (this.relationType === 'child') { |
||||
this.removeChild(); |
||||
|
||||
} else if (this.relationType === 'parent') { |
||||
this.removeParent(); |
||||
} |
||||
} |
||||
|
||||
public isCurrentElement():boolean { |
||||
return (this.relationType !== 'child' && this.relationType !== 'parent'); |
||||
} |
||||
|
||||
public isParent() { |
||||
return this.relationType === 'parent'; |
||||
} |
||||
|
||||
protected removeChild() { |
||||
this.wpRelationsHierarchyService |
||||
.removeChild(this.relatedWorkPackage) |
||||
.then(() => { |
||||
this.wpCacheService.loadWorkPackage(this.workPackage.id, true); |
||||
this.wpNotificationsService.showSave(this.workPackage); |
||||
this.$timeout(() => { |
||||
angular.element('#hierarchy--add-exisiting-child').focus(); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
protected removeParent() { |
||||
this.wpRelationsHierarchyService |
||||
.removeParent(this.workPackage) |
||||
.then(() => { |
||||
this.wpNotificationsService.showSave(this.workPackage); |
||||
this.$timeout(() => { |
||||
angular.element('#hierarchy--add-parent').focus(); |
||||
}); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
function WpRelationsHierarchyRowDirective():any { |
||||
return { |
||||
restrict:'E', |
||||
templateUrl:'/components/wp-relations/wp-relations-hierarchy-row/wp-relations-hierarchy-row.template.html', |
||||
scope:{ |
||||
indentBy:'@?', |
||||
workPackage:'=', |
||||
relatedWorkPackage:'=?', |
||||
relationType:'@' |
||||
}, |
||||
controller:WpRelationsHierarchyRowDirectiveController, |
||||
controllerAs:'$ctrl', |
||||
bindToController:true |
||||
}; |
||||
} |
||||
|
||||
wpDirectivesModule.directive('wpRelationsHierarchyRow', WpRelationsHierarchyRowDirective); |
@ -1,6 +1,6 @@ |
||||
<div class="detail-panel-description detail-panel--relations detail-panel--autocomplete-target" |
||||
*ngIf="workPackage"> |
||||
<div class="detail-panel-description-content"> |
||||
<ng1-wp-relations-wrapper [workPackage]="workPackage"></ng1-wp-relations-wrapper> |
||||
<wp-relations [workPackage]="workPackage"></wp-relations> |
||||
</div> |
||||
</div> |
||||
|
Loading…
Reference in new issue