opf/openproject

View on GitHub
frontend/src/app/features/boards/board/add-list-modal/add-list-modal.component.ts

Summary

Maintainability
C
7 hrs
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, Component, ElementRef, Inject, OnInit, ViewChild,
} from '@angular/core';
import { OpModalLocalsMap } from 'core-app/shared/components/modal/modal.types';
import { OpModalComponent } from 'core-app/shared/components/modal/modal.component';
import { OpModalLocalsToken } from 'core-app/shared/components/modal/modal.service';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { Board } from 'core-app/features/boards/board/board';
import { StateService } from '@uirouter/core';
import { BoardService } from 'core-app/features/boards/board/board.service';
import { BoardActionsRegistryService } from 'core-app/features/boards/board/board-actions/board-actions-registry.service';
import { BoardActionService } from 'core-app/features/boards/board/board-actions/board-action.service';
import { trackByHref } from 'core-app/shared/helpers/angular/tracking-functions';
import { HalResourceNotificationService } from 'core-app/features/hal/services/hal-resource-notification.service';
import { tap } from 'rxjs/operators';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
import {
  firstValueFrom,
  Observable,
} from 'rxjs';
import { OpAutocompleterComponent } from 'core-app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';

@Component({
  templateUrl: './add-list-modal.html',
})
export class AddListModalComponent extends OpModalComponent implements OnInit {
  @ViewChild(OpAutocompleterComponent, { static: true }) public ngSelectComponent:OpAutocompleterComponent;

  getAutocompleterData = (searchTerm:string):Observable<HalResource[]> => {
    // Remove prefix # from search
    searchTerm = searchTerm.replace(/^#/, '');
    return this.actionService.loadAvailable(this.active, searchTerm)
      .pipe(tap((values) => (this.warnIfNoOptions(values))));
  };

  public autocompleterOptions = {
    resource: '',
    getOptionsFn: this.getAutocompleterData,
  };

  public showClose:boolean;

  public confirmed = false;

  /** Active board */
  public board:Board;

  /** Current active set of values */
  public active:Set<string>;

  /** Action service used by the board */
  public actionService:BoardActionService;

  /** The selected attribute */
  public selectedAttribute:HalResource|undefined;

  /** avoid double click */
  public inFlight = false;

  public trackByHref = trackByHref;

  public warningText:string|undefined;

  public text:any = {
    title: this.I18n.t('js.boards.add_list'),
    button_add: this.I18n.t('js.button_add'),
    button_create: this.I18n.t('js.button_create'),
    button_cancel: this.I18n.t('js.button_cancel'),
    close_popup: this.I18n.t('js.close_popup_title'),

    free_board: this.I18n.t('js.boards.board_type.free'),
    free_board_text: this.I18n.t('js.boards.board_type.free_text'),

    action_board: this.I18n.t('js.boards.board_type.action'),
    action_board_text: this.I18n.t('js.boards.board_type.action_text'),
    select_attribute: this.I18n.t('js.boards.board_type.select_attribute'),
    placeholder: this.I18n.t('js.placeholders.selection'),
  };

  /** Whether the no results warning is displayed */
  showWarning = false;

  constructor(readonly elementRef:ElementRef,
    @Inject(OpModalLocalsToken) public locals:OpModalLocalsMap,
    readonly cdRef:ChangeDetectorRef,
    readonly boardActions:BoardActionsRegistryService,
    readonly halNotification:HalResourceNotificationService,
    readonly state:StateService,
    readonly boardService:BoardService,
    readonly I18n:I18nService,
    readonly apiV3Service:ApiV3Service,
    readonly currentProject:CurrentProjectService) {
    super(locals, cdRef, elementRef);
  }

  ngOnInit() {
    super.ngOnInit();
    this.board = this.locals.board;
    this.active = new Set(this.locals.active as string[]);
    this.actionService = this.boardActions.get(this.board.actionAttribute!);
    this.autocompleterOptions.resource = this.actionService.resourceName.toLowerCase();
  }

  onModelChange(element:HalResource) {
    this.selectedAttribute = element;
  }

  create() {
    this.inFlight = true;
    this.actionService
      .addColumnWithActionAttribute(this.board, this.selectedAttribute!)
      .then((board) => firstValueFrom(this.boardService.save(board)))
      .then((board) => {
        this.inFlight = false;
        this.closeMe();
        void this.state.go('boards.partitioned.show', { board_id: board.id, isNew: true });
      })
      .catch(() => (this.inFlight = false));
  }

  onNewActionCreated() {
    this
      .apiV3Service
      .versions
      .post(this.getVersionPayload(this.ngSelectComponent.ngSelectInstance.searchTerm))
      .subscribe(
        (version) => {
          this.selectedAttribute = version;
          this.create();
        },
        (error) => {
          this.ngSelectComponent.closeSelect();
          this.halNotification.handleRawError(error);
        },
      );
  }

  private getVersionPayload(name:string) {
    const payload:any = {};
    payload.name = name;
    payload._links = {
      definingProject: {
        href: this.apiV3Service.projects.id(this.currentProject.id!).path,
      },
    };

    return payload;
  }

  private warnIfNoOptions(values:unknown[]) {
    let hasMember = false;
    if (values.length === 0) {
      if (this.ngSelectComponent.ngSelectInstance.searchTerm !== undefined && this.ngSelectComponent.ngSelectInstance.searchTerm !== '') {
        hasMember = true;
      } else {
        hasMember = false;
      }
    } else {
      hasMember = false;
    }
    this.actionService
      .warningTextWhenNoOptionsAvailable(hasMember)
      .then((text) => {
        this.warningText = text;
      })
      .catch(() => {});
    this.showWarning = this.ngSelectComponent.ngSelectInstance.searchTerm !== undefined && (values.length === 0);
    this.cdRef.detectChanges();
  }
}