File

src/app/shared/widgets/worklist-select/worklist-select.component.ts

Extends

BaseConfiguration

Index

Properties

Properties

addElementConfig
addElementConfig: any
Type : any
allowDrag
allowDrag: boolean
Type : boolean
cellClass
cellClass: string
Type : string
Optional
cellClassModifier
cellClassModifier: string
Type : string
Optional
descriptionField
descriptionField: string
Type : string
disableDeselect
disableDeselect: boolean
Type : boolean
dynamicHeight
dynamicHeight: boolean
Type : boolean
Optional
dynamicHeightAdditionalHeight
dynamicHeightAdditionalHeight: string
Type : string
Optional
dynamicTreeHeightInDialog
dynamicTreeHeightInDialog: boolean
Type : boolean
Optional
elementsDataType
elementsDataType: string
Type : string
emitElementMoved
emitElementMoved: boolean
Type : boolean
emitOnFolderSelect
emitOnFolderSelect: boolean
Type : boolean
focusSearchInput
focusSearchInput: boolean
Type : boolean
Optional
footer
footer: any
Type : any
height
height: string
Type : string
Optional
identifierField
identifierField: string
Type : string
infoHeight
infoHeight: string
Type : string
infoWidth
infoWidth: string
Type : string
isFolderHierarchy
isFolderHierarchy: boolean
Type : boolean
listFolderType
listFolderType: string
Type : string
loadElements
loadElements: boolean
Type : boolean
loadElementsUrl
loadElementsUrl: string
Type : string
loadFoldersUrl
loadFoldersUrl: string
Type : string
localStateRecovery
localStateRecovery: boolean
Type : boolean
localStorageSearch
localStorageSearch: string
Type : string
localStorageState
localStorageState: string
Type : string
multi
multi: boolean
Type : boolean
refreshElementsActions
refreshElementsActions: any[]
Type : any[]
selectors
selectors: literal type
Type : literal type
showActions
showActions: boolean
Type : boolean
showElementIcon
showElementIcon: boolean
Type : boolean
showFolderActions
showFolderActions: boolean
Type : boolean
showStaticFoldersOnly
showStaticFoldersOnly: boolean
Type : boolean
staticFolders
staticFolders: any[]
Type : any[]
import {
  WidgetComponent,
  WidgetConfiguration,
  WidgetConfigure,
  WidgetId,
  WidgetInput,
  WidgetOutput,
} from "../widget.metadata";
import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  Input,
  OnDestroy,
  ViewChild,
} from "@angular/core";
import { getOrDefault, WidgetConfig } from "../widget.configuration";
import {
  concatAll,
  concatMap,
  filter,
  map,
  mergeAll,
  mergeMap,
  take,
  takeUntil,
  throttleTime,
} from "rxjs/operators";
import { AppdataStore } from "../../components/appdata/appdata.store";
import { WidgetframeService } from "../widgetframe/widgetframe.service";
import { CustomNotificationService } from "../../components/notification/customnotification.service";
import { HalService } from "../../components/hal/hal.service";

import { MatDialog } from "@angular/material/dialog";
import { MatMenuTrigger } from "@angular/material/menu";
import { CreateFolderEntryDialogComponent } from "./worklist-create-dialog/create-folder-entry-dialog.component";
import {
  Subject,
  Observable,
  ReplaySubject,
  from,
  of,
  combineLatest,
} from "rxjs";
import { NgUnsubscribe } from "../../ng-unsubscribe";
import { AppContext } from "../../components/app-context/app.context";
import { BaseConfiguration } from "../widgetframe/widgetframe.component";
import { Content, Selectors } from "../../components/app-context/api";

import * as uriTemplates_ from "uri-templates";
import { TranslateService } from "@ngx-translate/core";
import { TreeSelectorComponent } from "../../components/tree-selector/tree-selector.component";
import { Action } from "../../components/hal/action";
import { ActionEvent } from "../../components/hal/hal";
import { CurrentLocaleService } from "../../components/i18n/currentLocale.service";
import { NodeLoader } from "./node-loader";
import {
  OnDragEvent,
  OnDropEvent,
  OnDroppedEvent,
} from "../../components/tree-selector/tree-selector.api";
import { HttpResponse } from "@angular/common/http";
import { DialogService } from "../../components/dialog";

const uriTemplates = uriTemplates_;

export interface WorklistSelectConfiguration extends BaseConfiguration {
  allowDrag: boolean;
  multi: boolean;
  emitElementMoved: boolean;
  localStorageState: string;
  localStorageSearch: string;
  listFolderType: string;
  elementsDataType: string;
  staticFolders: any[];
  refreshElementsActions: any[];
  addElementConfig: any;
  showFolderActions: boolean;
  showElementIcon: boolean;
  emitOnFolderSelect: boolean;
  disableDeselect: boolean;
  footer: any;
  loadElements: boolean;
  loadElementsUrl: string;
  showActions: boolean;
  isFolderHierarchy: boolean;
  loadFoldersUrl: string;
  identifierField: string;
  descriptionField: string;
  localStateRecovery: boolean;
  showStaticFoldersOnly: boolean;
  height?: string;
  dynamicHeight?: boolean;
  dynamicTreeHeightInDialog?: boolean;
  dynamicHeightAdditionalHeight?: string;
  selectors: {
    folderMenu?: Selectors;
    folderIcons?: Selectors;
    nodeMenu?: Selectors;
    nodeIcons?: Selectors;

    // fallback, used, when the more specific selectors are not defined
    menu: Selectors;
    icons: Selectors;
  };
  cellClass?: string;
  cellClassModifier?: string;
  infoWidth: string;
  infoHeight: string;
  focusSearchInput?: boolean;
}

const DEFAULT_ADD_ELEMENT_CONFIG = {
  showAddElementIcon: true,
  labelTooltip: "infotext.create.worklist.or.folder",
  availableOptions: [
    {
      actionsHref: "/api/core/worklists",
      description: "worklist",
      listFolderType: "WORKLIST",
    },
    {
      actionsHref: "/api/core/listfolders/WORKLIST",
      description: "folder",
      listFolderType: "WORKLIST",
    },
  ],
};

@WidgetComponent("nm-worklist-select")
@Component({
  selector: "nm-worklist-select",
  templateUrl: "./worklist-select.component.html",
  styleUrls: ["./worklist-select.component.scss"],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class WorklistSelectComponentWidget implements OnDestroy {
  private unsubscribe = NgUnsubscribe.create();

  @ViewChild(TreeSelectorComponent, { static: true })
  private treeSelector: TreeSelectorComponent;

  @WidgetInput()
  public disableElementAction = new Subject<{
    node: string;
    action: string;
    value: boolean;
  }>();

  @WidgetId()
  public widgetId: string;

  @WidgetInput()
  public removeNodeSelection = new Subject<boolean>();

  @WidgetInput()
  public updateElements = new Subject<any>();

  @WidgetInput()
  public updateFolders = new Subject<any>();

  @WidgetInput()
  public reloadElements = new Subject<boolean>();

  @WidgetInput("disableAddNewElementButton")
  public disableAddNewElementButtonChannel = new Subject<boolean>();

  @WidgetOutput()
  public listSelected = new Subject<number>();

  @WidgetOutput("elementSelected")
  public elementSelected = new ReplaySubject<any>(1);

  @WidgetOutput()
  public reset = new Subject();

  @WidgetOutput()
  public addElementAction = new Subject();

  @WidgetOutput()
  public changeElementStateAction = new Subject<any>();

  @WidgetInput()
  public selectItem = new Subject<any>();

  @WidgetOutput()
  public elementMoved = new Subject<any>();

  @WidgetOutput()
  public editClicked = new Subject<any>();

  /** Add new element configuration */
  public addElementConfig: any;

  /** Element selector configuration */
  public multi: boolean;
  public emitElementMoved: boolean;
  public selectInitialized = true;
  public localStorageState: string;
  public localStorageSearch: string;
  public listFolderType: string;
  public elementsDataType: string;
  public height: string;
  public staticFolders: any[];
  public refreshElementsActions: any[];
  public availableActions: any[] = [];
  public showFolderActions: boolean;
  public showElementIcon: boolean;
  public forcedElementSelection;
  public emitOnFolderSelect: boolean;
  public disableDeselect: boolean;
  public loadElements: boolean;
  public isFolderHierarchy: boolean;
  public identifierField: string;
  public descriptionField: string;
  public localStateRecovery: boolean;
  public showStaticFoldersOnly: boolean;
  public dynamicHeight: boolean = false;
  public dynamicTreeHeightInDialog: boolean = false;
  public dynamicHeightAdditionalHeight: string;
  public focusSearchInput: boolean;

  public folderMenuSelectors: Selectors;
  public folderIconsSelectors: Selectors;
  public nodeMenuSelectors: Selectors;
  public nodeIconsSelectors: Selectors;

  /** Footer configuration */
  public footer: any;
  public withHeader: boolean;
  public showActions: boolean;

  public actions: any[];
  public nodes: any[];
  public disableAddNewElementButton: boolean = false;

  private nodeLoader: NodeLoader;

  private originalName: String;

  constructor(
    private dialogService: DialogService,
    private halService: HalService,
    private notificationService: CustomNotificationService,
    private appstore: AppdataStore,
    private widgetFrameService: WidgetframeService,
    private cd: ChangeDetectorRef,
    private appContext: AppContext,
    private translateService: TranslateService,
    private currentLocaleService: CurrentLocaleService
  ) {
    this.nodeLoader = new NodeLoader(
      translateService,
      appstore,
      widgetFrameService
    );
    this.addActions();
  }

  createAction(name, icon, label, labelFromAction, isDisableable) {
    return {
      name: name,
      icon: icon,
      label: label,
      labelFromAction: labelFromAction,
      isDisableable: isDisableable,
    };
  }

  addActions() {
    this.actions = [];
    this.actions.push(
      this.createAction("refresh", "refresh", "label.refresh", false, false)
    );
    this.actions.push(
      this.createAction(
        "duplicate",
        "content-duplicate",
        "label.duplicate",
        false,
        false
      )
    );
    this.actions.push(
      this.createAction(
        "checkLogging",
        "read",
        "label.checkLogging",
        false,
        false
      )
    );
    this.actions.push(
      this.createAction("import", "file-import", null, true, false)
    );
    this.actions.push(
      this.createAction("export", "file-export", null, true, false)
    );
    this.actions.push(
      this.createAction("translate", "translate", null, true, false)
    );
    this.actions.push(
      this.createAction(
        "add-to-dashboard",
        "view-dashboard-outline",
        null,
        true,
        false
      )
    );
    this.actions.push(
      this.createAction(
        "remove-from-dashboard",
        "view-dashboard",
        null,
        true,
        false
      )
    );
  }

  @WidgetConfigure()
  widgetConfigure() {
    this.multi = getOrDefault(this.configuration.configuration.multi, false);
    this.emitElementMoved = getOrDefault(
      this.configuration.configuration.emitElementMoved,
      false
    );
    this.localStorageState = getOrDefault(
      this.configuration.configuration.localStorageState,
      "worklist-state-storage"
    );
    this.localStorageSearch = getOrDefault(
      this.configuration.configuration.localStorageSearch,
      "worklist-search-string"
    );
    this.listFolderType = getOrDefault(
      this.configuration.configuration.listFolderType,
      "WORKLIST"
    );
    this.elementsDataType = getOrDefault(
      this.configuration.configuration.elementsDataType,
      "worklists"
    );
    this.showFolderActions = getOrDefault(
      this.configuration.configuration.showFolderActions,
      true
    );
    this.showElementIcon = getOrDefault(
      this.configuration.configuration.showElementIcon,
      true
    );
    this.emitOnFolderSelect = getOrDefault(
      this.configuration.configuration.emitOnFolderSelect,
      true
    );
    this.disableDeselect = getOrDefault(
      this.configuration.configuration.disableDeselect,
      false
    );
    this.staticFolders = getOrDefault(
      this.configuration.configuration.staticFolders,
      NodeLoader.defaultStaticFolders()
    );
    this.refreshElementsActions = getOrDefault(
      this.configuration.configuration.refreshElementsActions,
      this.getDefaultRefreshActions()
    );
    this.addElementConfig = Object.assign(
      {},
      DEFAULT_ADD_ELEMENT_CONFIG,
      this.configuration.configuration.addElementConfig || {}
    );
    this.footer = getOrDefault(this.configuration.configuration.footer, null);
    this.showStaticFoldersOnly = getOrDefault(
      this.configuration.configuration.showStaticFoldersOnly,
      false
    );

    this.selectItem
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((data) => (this.forcedElementSelection = data));

    this.withHeader = getOrDefault(
      this.configuration.configuration.withHeader,
      true
    );
    this.showActions = getOrDefault(
      this.configuration.configuration.showActions,
      true
    );
    this.loadElements = getOrDefault(
      this.configuration.configuration.loadElements,
      false
    );
    this.isFolderHierarchy = getOrDefault(
      this.configuration.configuration.isFolderHierarchy,
      false
    );
    this.identifierField = getOrDefault(
      this.configuration.configuration.identifierField,
      "identifier"
    );
    this.descriptionField = getOrDefault(
      this.configuration.configuration.descriptionField,
      "description"
    );
    this.localStateRecovery = getOrDefault(
      this.configuration.configuration.localStateRecovery,
      true
    );

    this.dynamicHeight = getOrDefault(
      this.configuration.configuration.dynamicHeight,
      false
    );

    this.dynamicTreeHeightInDialog = getOrDefault(
      this.configuration.configuration.dynamicTreeHeightInDialog,
      false
    );

    this.dynamicHeightAdditionalHeight = getOrDefault(
      this.configuration.configuration.dynamicHeightAdditionalHeight,
      ""
    );

    this.height = getOrDefault(
      this.configuration.configuration.height,
      "650px"
    );

    const selectors: any = this.configuration.configuration.selectors || {};
    this.folderMenuSelectors = getOrDefault(
      selectors.folderMenu || selectors.menu,
      {
        target: ["nm-worklist-select", "nm-worklist-select-folder"],
        type: "menu",
      }
    );

    this.folderIconsSelectors = getOrDefault(
      selectors.folderIcons || selectors.icons,
      {
        target: ["nm-worklist-select", "nm-worklist-select-folder"],
        type: "icons",
      }
    );

    this.nodeMenuSelectors = getOrDefault(
      selectors.nodeMenu || selectors.menu,
      {
        target: ["nm-worklist-select", "nm-worklist-select-node"],
        type: "menu",
      }
    );

    this.nodeIconsSelectors = getOrDefault(
      selectors.nodeIcons || selectors.icons,
      {
        target: ["nm-worklist-select", "nm-worklist-select-node"],
        type: "icons",
      }
    );

    this.focusSearchInput = getOrDefault(
      this.configuration.configuration.focusSearchInput,
      false
    );

    this.removeNodeSelection
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((event) => this.treeSelector.clearSelection());

    this.updateElements.pipe(takeUntil(this.unsubscribe)).subscribe((node) => {
      const mapped = this.nodeLoader.mapEntry(
        node.value,
        this.identifierField,
        this.descriptionField
      );
      this.treeSelector.updateNode(mapped.id, mapped);
    });

    this.updateFolders.pipe(takeUntil(this.unsubscribe)).subscribe((node) => {
      const mapped = this.nodeLoader.mapFolder(node.value);
      this.treeSelector.updateNode(mapped.id, mapped);
    });

    let sources = [
      //
      of(),
      this.currentLocaleService.getCurrentLocale(),
      this.reloadElements,
      this.halService
        .getActionEvents()
        .pipe(filter((event) => event.name === "duplicate")),
    ];

    // trigger re-load, if the configured selectors
    // are triggered via the userContext
    // (used to reload after adding a folder or worklist)
    if (
      this.refreshElementsActions &&
      this.appContext &&
      this.appContext.userContext
    ) {
      this.refreshElementsActions.forEach((selector) => {
        sources.push(this.appContext.userContext.subscribe(selector));
      });
    }

    from(sources)
      .pipe(mergeAll(), takeUntil(this.unsubscribe), throttleTime(500))
      .subscribe(() => this.loadNodes());

    this.disableAddNewElementButtonChannel
      .asObservable()
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((disabled) => {
        this.disableAddNewElementButton = disabled;
        this.cd.markForCheck();
      });
  }

  private loadNodes() {
    if (this.showStaticFoldersOnly) {
      combineLatest([
        this.nodeLoader.staticFolders(this.staticFolders),
        this.nodeLoader.elements(
          this.configuration.configuration.loadElementsUrl,
          this.elementsDataType,
          this.identifierField,
          this.descriptionField
        ),
      ])
        .pipe(
          map(([folders, elements]) => [...folders.nodes, ...elements.nodes])
        )
        .subscribe((nodes) => {
          this.nodes = nodes;
          this.cd.markForCheck();
        });
      return;
    }

    this.nodeLoader
      .nodes(
        this.staticFolders,
        this.isFolderHierarchy
          ? this.configuration.configuration.loadFoldersUrl
          : null,
        this.listFolderType,
        this.configuration.configuration.loadElementsUrl,
        this.elementsDataType,
        this.identifierField,
        this.descriptionField
      )
      .subscribe((nodes) => {
        this.nodes = nodes;
        this.cd.markForCheck();
      });
  }

  public isNode(index, row: any): boolean {
    return row.isNode === true;
  }

  public isStatic(index, row: any): boolean {
    return row.isStatic === true;
  }

  public isStaticChild(index, row: any): boolean {
    return row.isNode === true && row.parent === "dynamic-worklists";
  }

  public isFolder(index, row: any): boolean {
    return row.isFolder === true;
  }

  public onEdit(data: any) {
    // save the original name to prevent executing actions, if nothing changed
    this.originalName = data.name;

    // use the inline edit mode unless explicitly set in the node
    if (data.editMode && data.editMode !== "inline") {
      this.editClicked.next(data.id);
    } else {
      this.treeSelector.edit(data.id);
    }
  }

  public onEdited(data: any) {
    let trimmedName = data.name.trim();

    if (!trimmedName) {
      let errorTitle = this.translateService.instant(
        "worklist-select.edit.error.title"
      );
      let errorMsg = this.translateService.instant(
        "worklist-select.edit.error.message"
      );
      this.notificationService.error(errorTitle, errorMsg);
      data.name = this.originalName;
      this.reload(data);
      return;
    }

    if (this.originalName === trimmedName) {
      return;
    }

    if (data.isFolder) {
      const action = data.actions.update;
      action.payload = {
        name: trimmedName,
      };
      this.halService.executeAndShowMessage("rename", action).subscribe();
      return;
    }

    const action = data.actions.update;
    action.payload.description = trimmedName;
    this.halService.executeAndShowMessage("rename", action).subscribe();
  }

  public onDrag(event: OnDragEvent) {
    event.cancel =
      this.isStatic(null, event.dragged) ||
      this.isStaticChild(null, event.dragged);
  }

  public onDrop(event: OnDropEvent) {
    if (
      this.isStatic(null, event.target) ||
      this.isStaticChild(null, event.target)
    ) {
      event.cancel = true;
    } else if (event.target.isNode) {
      event.dropAsChild = false;
    }
  }

  public onDropped(node, event: OnDroppedEvent) {
    if (this.emitElementMoved) {
      this.elementMoved.next(event);
      return;
    }
    const action = event.node.actions.update;
    if (event.node.isFolder) {
      const parent = event.to.parent?.folder ? event.to.parent.id : "-1";
      action.payload = {
        parent: parent,
      };
      this.halService.executeAndShowMessage("move", action).subscribe();
    } else {
      if (event.to.parent?.folder) {
        action.payload.listFolder = event.to.parent.id;
        event.node.root = false;
      } else {
        action.payload.listFolder = null;
        event.node.root = true;
      }
      this.halService.executeAndShowMessage("move", action).subscribe();
    }
  }

  onDeleteClick(node, action) {
    if (action.confirmed) {
      // trigger delete action, _without_ reloading...
      this.executeAction("delete", action, node, false).subscribe((event) => {
        this.notificationService.fromResponse(event.response);
        this.treeSelector.removeNode(node.id);
      });
    } else {
      action.confirmed = true;
      setTimeout(() => {
        action.confirmed = false;
        this.cd.markForCheck();
      }, 3000);
    }
  }

  public executeActionAndShowMessage(
    name: string,
    action: Action,
    node: any,
    reload: boolean = true
  ) {
    const observable = this.halService.executeAndShowMessage(name, action);

    if (reload) {
      observable.subscribe((event) => this.reload(node, event.response));
    }

    return observable;
  }

  private executeAction(
    actionName: string,
    action: Action,
    node: any,
    reload: boolean = true
  ): Observable<ActionEvent> {
    const observable = this.halService.execute(actionName, action);

    if (reload) {
      observable.subscribe((event) => this.reload(node, event.response));
    }

    return observable;
  }

  private reload(node: any, actionResponse?: HttpResponse<any>) {
    if (!node) {
      return;
    }

    // if the calling action already contained an updated node object
    // no need to reload
    const updatedNode = actionResponse?.body?.data;
    if (updatedNode && updatedNode[this.identifierField] === node.pimRef) {
      const mapped = this.nodeLoader.mapEntry(
        updatedNode,
        this.identifierField,
        this.descriptionField
      );
      this.treeSelector.updateNode(node.id, mapped);
      return;
    }

    if (node.actions.reload) {
      this.halService
        .execute("reload", node.actions.reload)
        .subscribe((event) => {
          if (event.response.body) {
            const mapped = this.nodeLoader.mapEntry(
              event.response.body,
              this.identifierField,
              this.descriptionField
            );
            this.treeSelector.updateNode(node.id, mapped);
          }
        });
    }
  }

  onClickAction(data, actionName) {
    switch (actionName) {
      case "add-to-dashboard":
      case "remove-from-dashboard":
      case "refresh":
      case "duplicate":
        this.executeActionAndShowMessage(
          actionName,
          data.actions[actionName],
          data
        );
        break;
      default:
        this.executeAction(actionName, data.actions[actionName], data);
        break;
    }
  }

  filteredActions(data): any[] {
    return this.actions.filter((action) => data.actions?.[action.name]);
  }

  @WidgetConfiguration()
  public configuration: WidgetConfig<WorklistSelectConfiguration>;

  ngOnDestroy(): void {
    this.unsubscribe.destroy();
  }

  onSelect(data) {
    if (data && Array.isArray(data)) {
      data = data.length > 0 ? data[0] : null;
    }

    if (data) {
      this.elementSelected.next(data);

      if (data.isNode || this.emitOnFolderSelect) {
        this.listSelected.next(data);
      }
    } else {
      this.elementSelected.next(null);
      this.reset.next();
    }
  }

  public onAddElement() {
    this.addElementAction.next();

    if (
      this.addElementConfig &&
      this.addElementConfig.availableOptions &&
      this.addElementConfig.availableOptions.length > 0
    ) {
      this.availableActions = this.getAvailableActions();

      const dialogRef = this.dialogService.open(
        CreateFolderEntryDialogComponent,
        {
          autoFocus: true,
          width: "706px",
          height: "889px",
        }
      );

      dialogRef.componentInstance.title = "infotext.create.element";
      dialogRef.componentInstance.availableActions = this.availableActions;
      dialogRef.componentInstance.listFolderType = this.listFolderType;
    }
  }

  private getAvailableActions(): any[] {
    var availableActions = [];

    if (this.addElementConfig && this.addElementConfig.availableOptions) {
      from(this.addElementConfig.availableOptions)
        .pipe(
          concatMap((availableOption) => {
            let option: any = availableOption as any;
            return this.widgetFrameService.getData(option.actionsHref).pipe(
              map((response) => {
                return {
                  description: option.description,
                  actions: response._actions,
                  listFolderType: option.listFolderType,
                };
              })
            );
          })
        )
        .subscribe((option) => availableActions.push(option));
    }

    return availableActions;
  }

  public onClickElementIcon(event, data) {
    event.stopPropagation();
    event.preventDefault();
    this.changeElementStateAction.next(data);
  }

  public onElementMove(event) {
    this.elementMoved.next(event);
  }

  public onEditButtonClick(event) {
    this.editClicked.next(event);
  }

  private getDefaultRefreshActions(): any[] {
    return [
      {
        target: "worklists",
      },
      {
        target: "listfolders",
        action: "create",
      },
      {
        target: "listfolder",
        action: "delete",
      },
    ];
  }

  public getWorkListRowContent(data: any): string {
    return data.appendEntriesCount && data.entries !== null
      ? "[" + data.entries + "] " + data.name
      : data.name;
  }
}

results matching ""

    No results matching ""