File

src/app/app.component.ts

Description

The main application component.

Extends

BaseApplicationComponent

Implements

OnDestroy OnInit

Metadata

Index

Properties
Methods

Constructor

constructor()

Creates an instance of app component.

Methods

handleClick
handleClick(target: EventTarget | null)

Disables block position change if an input element is clicked

Parameters :
Name Type Optional Description
target EventTarget | null No

The element clicked

Returns : void
registrationToggle
registrationToggle(event: boolean)

Toggles the registration expansion state.

Parameters :
Name Type Optional Description
event boolean No

The new state of registration expansion.

Returns : void
resetBlock
resetBlock()

Resets the block position to the default state.

Returns : void
resetCamera
resetCamera()

Resets the camera to the default view.

Returns : void
resetMetadata
resetMetadata()

Resets the metadata to the default state.

Returns : void
resetStage
resetStage()

Resets the stage to its initial state.

Returns : void
updateSide
updateSide(selection: Side)

Updates the side view.

Parameters :
Name Type Optional Description
selection Side No

The selected side.

Returns : void
updateView
updateView(type: ViewType)

Handles updating of the boolean that keeps track of current view and calling the event emitter.

Parameters :
Name Type Optional
type ViewType No
Returns : void

Properties

Protected Readonly disableOrganAxis
Type : unknown
Default value : toSignal(this.model.disableOrganAxis$, { initialValue: false })

Whether the organ axis is hidden

disablePositionChange
Type : unknown
Default value : false

Disables changes in block position

Protected Readonly embedded
Type : unknown
Default value : toSignal(this.page.useCancelRegistrationCallback$)

Whether to use the embedded app

Readonly homeUrl$
Type : unknown
Default value : this.globalConfig.getOption('homeUrl')

Preset homeurl

Readonly metadata
Type : unknown
Default value : inject(MetadataService)

Metadata service

Readonly model
Type : unknown
Default value : inject(ModelState)

Model state

Readonly page
Type : unknown
Default value : inject(PageState)

Page state

Readonly registration
Type : unknown
Default value : inject(RegistrationState)

Registration state

registrationExpanded
Type : unknown
Default value : false

Indicates whether the registration is expanded.

registrationStarted
Type : unknown
Default value : false

False until the initial registration modal is closed

Readonly snackbar
Type : unknown
Default value : inject(MatSnackBar)

Snackbar service

Readonly view$
Type : unknown
Default value : this.globalConfig.getOption('view')

Preset view setting

Protected Readonly viewSide
Type : unknown
Default value : toSignal(this.model.viewSide$)

The current view side

Readonly viewSide$
Type : unknown
Default value : this.globalConfig.getOption('viewSide')

Preset view side

Protected Readonly viewType
Type : unknown
Default value : toSignal(this.model.viewType$, { initialValue: 'register' })

The current view type, either 'register' or 'preview', default is register

import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnDestroy, OnInit } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { MatSnackBar } from '@angular/material/snack-bar';
import { BaseApplicationComponent } from '@hra-ui/application';
import { GlobalConfigState } from 'ccf-shared';
import { combineLatest, Subscription } from 'rxjs';

import { GlobalConfig } from './core/services/config/config';
import { ModelState, ViewSide, ViewType } from './core/store/model/model.state';
import { PageState } from './core/store/page/page.state';
import { RegistrationState } from './core/store/registration/registration.state';
import { MetadataService } from './modules/metadata/metadata.service';

/** Represents a user with a first and last name. */
export interface User {
  /** First name */
  firstName: string;
  /** Last name */
  lastName: string;
}

/** Configuration options for the application. */
interface AppOptions extends GlobalConfig {
  /** Home url */
  homeUrl?: string;
  /** Logo tooltip */
  logoTooltip?: string;
  /** View type */
  view?: ViewType;
  /** View side */
  viewSide?: ViewSide;
}

/** Valid values for side of the view. */
export type Side = 'left' | 'right' | 'anterior' | 'posterior';

/**
 * The main application component.
 */
@Component({
  selector: 'ccf-root',
  standalone: false,
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss',
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: {
    class: 'hra-app',
    '[class.embedded]': 'embedded()',
    '(document:mousedown)': 'handleClick($event.target)',
  },
})
export class AppComponent extends BaseApplicationComponent implements OnDestroy, OnInit {
  /** Model state */
  readonly model = inject(ModelState);
  /** Page state */
  readonly page = inject(PageState);
  /** Registration state */
  readonly registration = inject(RegistrationState);
  /** Snackbar service */
  readonly snackbar = inject(MatSnackBar);
  /** Metadata service */
  readonly metadata = inject(MetadataService);
  /** Global config */
  private readonly globalConfig = inject<GlobalConfigState<AppOptions>>(GlobalConfigState);

  /** False until the initial registration modal is closed */
  registrationStarted = false;

  /** Disables changes in block position */
  disablePositionChange = false;

  /** Indicates whether the registration is expanded. */
  registrationExpanded = false;

  /** Preset homeurl */
  readonly homeUrl$ = this.globalConfig.getOption('homeUrl');

  /** Preset view setting */
  readonly view$ = this.globalConfig.getOption('view');

  /** Preset view side */
  readonly viewSide$ = this.globalConfig.getOption('viewSide');

  /** Whether to use the embedded app */
  protected readonly embedded = toSignal(this.page.useCancelRegistrationCallback$);

  /** The current view side */
  protected readonly viewSide = toSignal(this.model.viewSide$);

  /** The current view type, either 'register' or 'preview', default is register */
  protected readonly viewType = toSignal(this.model.viewType$, { initialValue: 'register' });

  /** Whether the organ axis is hidden */
  protected readonly disableOrganAxis = toSignal(this.model.disableOrganAxis$, { initialValue: false });

  /** All subscriptions managed by the container. */
  private readonly subscriptions = new Subscription();

  /**
   * Creates an instance of app component.
   */
  constructor() {
    super({ screenSizeNotice: { width: 1280, height: 832 } });

    const cdr = inject(ChangeDetectorRef);

    this.subscriptions.add(
      this.page.registrationStarted$.subscribe((registrationStarted) => {
        this.registrationStarted = registrationStarted;
      }),
    );

    combineLatest([this.view$, this.viewSide$]).subscribe(([view, viewSide]) => {
      this.model.setViewType(view ?? 'register');
      this.model.setViewSide(viewSide ?? 'anterior');
      cdr.markForCheck();
    });
  }

  /**
   * Initializes app: opens snackbar and sets premade options
   */
  ngOnInit(): void {
    const { editRegistration, user, organ } = this.globalConfig.snapshot;
    if (!editRegistration && (!user || !organ)) {
      setTimeout(() => this.metadata.openModal('create'), 20);
    }
  }

  /**
   * Toggles the registration expansion state.
   * @param event The new state of registration expansion.
   */
  registrationToggle(event: boolean): void {
    this.registrationExpanded = event;
    if (!this.registrationExpanded) {
      this.disablePositionChange = false;
    }
  }

  /**
   * Disables block position change if an input element is clicked
   * @param target The element clicked
   */
  handleClick(target: EventTarget | null): void {
    if (!target || !(target instanceof HTMLElement)) {
      return;
    }

    const disableWhenClicked = ['mat-mdc-input-element', 'mat-mdc-form-field', 'form-input-label'];
    for (const className of disableWhenClicked) {
      if (typeof target.className === 'string' && target.className.includes(className)) {
        this.disablePositionChange = true;
        return;
      }
    }
    this.disablePositionChange = false;
  }

  /**
   * Cleans up all subscriptions.
   */
  ngOnDestroy(): void {
    this.subscriptions.unsubscribe();
  }

  /**
   * Updates the side view.
   * @param selection The selected side.
   */
  updateSide(selection: Side): void {
    this.updateView('register');
    this.model.setViewSide(selection);
  }

  /**
   * Handles updating of the boolean that keeps track of current view
   * and calling the event emitter.
   *
   * @param selection 3D (true) or Register (false)
   */
  updateView(type: ViewType): void {
    this.model.setViewType(type);
  }

  /**
   * Resets the stage to its initial state.
   */
  resetStage(): void {
    this.resetMetadata();
    this.resetCamera();
  }

  /**
   * Resets the camera to the default view.
   */
  resetCamera(): void {
    this.model.setViewSide('anterior');
    this.model.setViewType('register');
  }

  /**
   * Resets the metadata to the default state.
   */
  resetMetadata(): void {
    if (this.registration.snapshot.initialRegistration) {
      this.registration.setToInitialRegistration();
    } else {
      this.model.setOrganDefaults();
    }
  }

  /**
   * Resets the block position to the default state.
   */
  resetBlock(): void {
    if (this.registration.snapshot.initialRegistration) {
      this.registration.resetPosition();
    } else {
      this.model.setDefaultPosition();
    }
  }
}
<ng-container hraFeature="rui">
  <hra-back-button-bar hraFeature="back-bar" class="back-bar" (backClick)="page.cancelRegistration()" />
  <hra-nav-header-buttons
    hraFeature="nav-header"
    app="product:rui"
    appTitle="Registration User Interface"
    [appLink]="(homeUrl$ | async) ?? 'https://apps.humanatlas.io/rui/'"
    [brandmark]="true"
  />
  <ccf-left-sidebar hraFeature="left-sidebar" [modalClosed]="registrationStarted" />
  <hra-expansion-panel hraFeature="registration-controls" tagline="Registration" disabled class="registration">
    <hra-expansion-panel-actions>
      <button
        hraFeature="registration"
        hraClickEvent
        mat-icon-button
        hraIconButtonSize="large"
        aria-label="Icon to open nested menu"
        hraPlainTooltip="More"
        [matMenuTriggerFor]="menu"
      >
        <mat-icon>more_vert</mat-icon>
      </button>
      <mat-menu hraFeature="menu" #menu="matMenu">
        <button hraFeature="toggle-organ-axis" hraClickEvent mat-menu-item (click)="model.toggleOrganAxis()">
          <mat-icon>{{ disableOrganAxis() ? 'visibility_on' : 'visibility_off' }}</mat-icon>
          {{ `${disableOrganAxis() ? 'Show' : 'Hide'} 3D axes` }}
        </button>
        <mat-divider class="menu-divider" />
        <button hraFeature="reset-camera" hraClickEvent mat-menu-item (click)="resetCamera()">Reset camera view</button>
        <button hraFeature="reset-block" hraClickEvent mat-menu-item (click)="resetBlock()">
          Reset tissue block position
        </button>
        <mat-divider class="menu-divider" />
        <button hraFeature="reset-metadata" hraClickEvent mat-menu-item (click)="metadata.openModal('edit')">
          Edit metadata
        </button>
      </mat-menu>
    </hra-expansion-panel-actions>
    <hra-expansion-panel-header-content>
      <mat-button-toggle-group
        hraFeature="side"
        hraModelChangeEvent
        name="sideSelect"
        aria-label="Side Select"
        hideSingleSelectionIndicator
        hraButtonToggleSize="medium"
        [ngModel]="viewSide()"
        (ngModelChange)="updateSide($event)"
      >
        <mat-button-toggle value="left">Left</mat-button-toggle>
        <mat-button-toggle value="right">Right</mat-button-toggle>
        <mat-button-toggle value="anterior" checked>Anterior</mat-button-toggle>
        <mat-button-toggle value="posterior">Posterior</mat-button-toggle>
      </mat-button-toggle-group>
      <mat-button-toggle-group
        hraFeature="view-type"
        hraModelChangeEvent
        name="modeSelect"
        aria-label="Mode Select"
        hideSingleSelectionIndicator
        hraButtonToggleSize="medium"
        [ngModel]="viewType()"
        (ngModelChange)="updateView($event)"
      >
        <mat-button-toggle value="register">Register</mat-button-toggle>
        <mat-button-toggle value="3d">Preview</mat-button-toggle>
      </mat-button-toggle-group>
    </hra-expansion-panel-header-content>
  </hra-expansion-panel>
  <ccf-content hraFeature="stage-content" class="stage-content" [disablePositionChange]="disablePositionChange" />
  <ccf-right-sidebar hra-feature="right-sidebar" (registrationExpanded)="registrationToggle($event)" />
</ng-container>
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""