opf/openproject

View on GitHub
frontend/src/app/features/work-packages/components/wp-new/wp-create.component.ts

Summary

Maintainability
C
1 day
Test Coverage
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2024 the OpenProject GmbH
//
// 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 COPYRIGHT and LICENSE files for more details.
//++

import {
  ChangeDetectorRef,
  Directive,
  Injector,
  OnInit,
  ViewChild,
} from '@angular/core';
import {
  StateService,
  Transition,
} from '@uirouter/core';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { States } from 'core-app/core/states/states.service';
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
import { RootResource } from 'core-app/features/hal/resources/root-resource';
import { takeWhile } from 'rxjs/operators';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { WorkPackageViewFiltersService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-filters.service';
import { WorkPackageChangeset } from 'core-app/features/work-packages/components/wp-edit/work-package-changeset';
import { WorkPackageViewFocusService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-focus.service';
import { EditFormComponent } from 'core-app/shared/components/fields/edit/edit-form/edit-form.component';
import { WorkPackageNotificationService } from 'core-app/features/work-packages/services/notifications/work-package-notification.service';
import * as URI from 'urijs';
import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin';
import { splitViewRoute } from 'core-app/features/work-packages/routing/split-view-routes.helper';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { HalResource, HalSource } from 'core-app/features/hal/resources/hal-resource';
import { OpTitleService } from 'core-app/core/html/op-title.service';
import { WorkPackageCreateService } from './wp-create.service';
import { HalError } from 'core-app/features/hal/services/hal-error';
import idFromLink from 'core-app/features/hal/helpers/id-from-link';

@Directive()
export class WorkPackageCreateComponent extends UntilDestroyedMixin implements OnInit {
  public successState:string = splitViewRoute(this.$state);

  public cancelState:string = this.$state.current.data.baseRoute;

  public newWorkPackage:WorkPackageResource;

  public parentWorkPackage:WorkPackageResource;

  public change:WorkPackageChangeset;

  /** Are we in the copying substates ? */
  public copying = false;

  public stateParams = this.$transition.params('to');

  public text = {
    button_settings: this.I18n.t('js.button_settings'),
  };

  @ViewChild(EditFormComponent, { static: false }) protected editForm:EditFormComponent;

  /** Explicitly remember destroy state in this abstract base */
  protected destroyed = false;

  constructor(
    public readonly injector:Injector,
    protected readonly $transition:Transition,
    protected readonly $state:StateService,
    protected readonly I18n:I18nService,
    protected readonly titleService:OpTitleService,
    protected readonly notificationService:WorkPackageNotificationService,
    protected readonly states:States,
    protected readonly wpCreate:WorkPackageCreateService,
    protected readonly wpViewFocus:WorkPackageViewFocusService,
    protected readonly wpTableFilters:WorkPackageViewFiltersService,
    protected readonly pathHelper:PathHelperService,
    protected readonly apiV3Service:ApiV3Service,
    protected readonly cdRef:ChangeDetectorRef) {
    super();
  }

  public ngOnInit() {
    this.closeEditFormWhenNewWorkPackageSaved();

    this.showForm();
  }

  public ngOnDestroy() {
    super.ngOnDestroy();
  }

  public switchToFullscreen() {
    const type = idFromLink(this.change.value<HalResource>('type')?.href);
    void this.$state.go('work-packages.new', { ...this.$state.params, type });
  }

  public onSaved(params:{ savedResource:WorkPackageResource, isInitial:boolean }) {
    const { savedResource, isInitial } = params;

    this.editForm?.cancel(false);

    if (this.successState) {
      this.$state.go(this.successState, { workPackageId: savedResource.id })
        .then(() => {
          this.wpViewFocus.updateFocus(savedResource.id!);
          this.notificationService.showSave(savedResource, isInitial);
        });
    }
  }

  protected showForm() {
    this
      .createdWorkPackage()
      .then((changeset:WorkPackageChangeset) => {
        this.change = changeset;
        this.newWorkPackage = changeset.pristineResource;
        this.cdRef.detectChanges();

        this.setTitle();

        if (this.stateParams.parent_id) {
          changeset.setValue(
            'parent',
            { href: this.apiV3Service.work_packages.id(this.stateParams.parent_id).path },
          );
        }

        // Load the parent simply to display the type name :-/
        if (this.stateParams.parent_id) {
          this
            .apiV3Service
            .work_packages
            .id(this.stateParams.parent_id)
            .get()
            .pipe(
              this.untilDestroyed(),
            )
            .subscribe((parent) => {
              this.parentWorkPackage = parent;
              this.cdRef.detectChanges();
            });
        }
      })
      .catch((error:unknown) => {
        if (error instanceof HalError && error.errorIdentifier === 'urn:openproject-org:api:v3:errors:MissingPermission') {
          this.apiV3Service.root.get().subscribe((root:RootResource) => {
            if (!root.user) {
              // Not logged in
              const url = URI(this.pathHelper.loginPath());
              url.search({ back_url: url });
              window.location.href = url.toString();
            }
          });
          this.notificationService.handleRawError(error);
        }
      });
  }

  protected setTitle() {
    this.titleService.setFirstPart(this.I18n.t('js.work_packages.create.title'));
  }

  public cancelAndBackToList() {
    this.wpCreate.cancelCreation();
    this.$state.go(this.cancelState, this.$state.params);
  }

  protected createdWorkPackage() {
    const defaults:HalSource = (this.stateParams.defaults as HalSource) || {};
    defaults._links = defaults._links || {};

    const type = this.stateParams.type ? parseInt(this.stateParams.type) : undefined;
    const parent = this.stateParams.parent_id ? parseInt(this.stateParams.parent_id) : undefined;
    const project = this.stateParams.projectPath;

    if (type) {
      defaults._links.type = { href: this.apiV3Service.types.id(type).path };
    }
    if (parent) {
      defaults._links.parent = { href: this.apiV3Service.work_packages.id(parent).path };
    }

    return this.wpCreate.createOrContinueWorkPackage(project, type, defaults);
  }

  private closeEditFormWhenNewWorkPackageSaved() {
    this.wpCreate
      .onNewWorkPackage()
      .pipe(
        takeWhile(() => !this.componentDestroyed),
      )
      .subscribe((wp:WorkPackageResource) => {
        this.onSaved({ savedResource: wp, isInitial: true });
      });
  }
}