src/app/app.component.ts

Metadata

Relationships

Used by

Depends on

No results matching.

Index

Properties
Methods
Accessors

Constructor

constructor(appService: AppService, licenceService: LicenceService, router: Router, route: ActivatedRoute, location: Location, eventService: EventService, appStoreService: AppStoreService, startupCheckService: StartupCheckService, cdr: ChangeDetectorRef, elementRef: ElementRef)
Parameters :
Name Type Optional
appService AppService No
licenceService LicenceService No
router Router No
route ActivatedRoute No
location Location No
eventService EventService No
appStoreService AppStoreService No
startupCheckService StartupCheckService No
cdr ChangeDetectorRef No
elementRef ElementRef No

Methods

Public backToEditor
backToEditor()

VA embed only: returns to the editor for the last-opened table. Lets users who land on the stage/approve/review pages get back without knowing DC's page paths. globals.embed (set at app init) keeps the editor in VA mode.

Returns : void
Public closeAbortModal
closeAbortModal(abortId: number)

Closes abort modal with matching ID (there could be multiple abort modals open)

Parameters :
Name Type Optional
abortId number No
Returns : void
Public isMainRoute
isMainRoute(route: string)

Whether or not current route includes the route from param

Parameters :
Name Type Optional Description
route string No

route to check

Returns : boolean
Public licenceProblemDetails
licenceProblemDetails(url: string)

Opens licence page with the active licence problem Problem details are encoded in the url

Parameters :
Name Type Optional
url string No
Returns : void
Public openLicencingPage
openLicencingPage()

Opens a page for updating the licence.

Returns : void
Private parseComputeApi
parseComputeApi(value: string | undefined)

Based on string provided we return true, false or null True -> Compute API False -> JES API Null -> JES WEB

Parameters :
Name Type Optional Description
value string | undefined No

provided in the html tag

Returns : boolean | null

true, false or null

Private parseDcAdapterSettings
parseDcAdapterSettings()

Parses adapter settings that are found in the tag inside index.html

Returns : void
Public subscribeToAppActive
subscribeToAppActive()

Listens for an event that will activate od deactivate full application. Based on licence key prcoessing result

Returns : void
Public subscribeToDemoLimitModal
subscribeToDemoLimitModal()

Listens for an demo limit event that will show the Feature locked modal For exmaple when in editor upload feature is not enabled When user tries to upload the excel, editor component will trgger this event And stop the execution of file upload code.

Returns : void
Public subscribeToLicenseEvents
subscribeToLicenseEvents()

Listens for licence events so banner can be displayed. App is free tier, licence will expire, is expired or is invalid

Returns : void
Public subscribeToRequestsModal
subscribeToRequestsModal()

Opens requests modal when requested from event service

Returns : void
Public subscribeToShowAbortModal
subscribeToShowAbortModal()

Listnes to an event that is fired when showing abort modal Then pushes object to array and modal is displayed abortId is calculated and assigned so that modal can be removed and closed it's an incrementing number

Returns : void
Public subscribeToStartupData
subscribeToStartupData()

When startupservice request is finished with valid response, this event will make sure loading screen is gone.

Returns : void
Public toggleSidebar
toggleSidebar()

Toggles sidebar when requested from event service

Returns : void

Properties

Public appActive
Type : boolean | null
Default value : null
Public appOverCapacity
Type : boolean
Default value : false
Public approveActive
Type : boolean
Default value : false
Public commitVer
Type : string
Private dcAdapterSettings
Type : DcAdapterSettings | undefined
Public demoLimitNotice
Type : { open: boolean; featureName: string }
Default value : { open: false, featureName: '' }
Public editorActive
Type : boolean
Default value : false
Public embed
Type : unknown
Default value : globals.embed
Public errTop
Type : boolean | undefined
Public freeTierBanner
Type : boolean
Default value : this.licenceService.isAppFreeTier.value
Public licenceProblem
Type : unknown
Default value : this.licenceService.licenceProblem
Public licenceState
Type : unknown
Default value : this.licenceService.licenceState
Public licenseExpiringDays
Type : number | null
Default value : null
Public requestsModal
Type : boolean
Default value : false
Public router
Type : Router
Public routeUrl
Type : any
Public sasjsAborts
Type : InfoModal[]
Default value : []
Public showRegistration
Type : boolean
Default value : true
Public startupDataLoaded
Type : boolean
Default value : false
Public startupSteps
Type : StartupStep[]
Default value : []
Public syssite
Type : unknown
Default value : this.appService.syssite
Public version
Type : any

Accessors

vaEditorLibds
getvaEditorLibds()

"lib.table" of the last-opened editor table (for the VA embed back button).

Returns : string
import {
  ChangeDetectorRef,
  Component,
  ElementRef,
  ViewEncapsulation
} from '@angular/core'
import { Router } from '@angular/router'
import { VERSION } from '../environments/version'
import { ActivatedRoute } from '@angular/router'
import { Location } from '@angular/common'
import '@clr/icons'
import '@clr/icons/shapes/all-shapes'
import { globals } from './_globals'
import { parseEmbedParam } from './shared/utils/parse-embed-param'
import moment from 'moment'
import { EventService } from './services/event.service'
import { AppService } from './services/app.service'
import { InfoModal } from './models/InfoModal'
import { DcAdapterSettings } from './models/DcAdapterSettings'
import { AppStoreService } from './services/app-store.service'
import { LicenceService } from './services/licence.service'
import {
  getSasjsTagDetails,
  SASJS_TAG_STEP,
  StartupCheckService,
  StartupStep
} from './services/startup-check.service'
import '@cds/core/icon/register.js'
import {
  ClarityIcons,
  exclamationTriangleIcon,
  moonIcon,
  processOnVmIcon,
  sunIcon,
  tableIcon,
  trashIcon
} from '@cds/core/icon'

ClarityIcons.addIcons(
  moonIcon,
  sunIcon,
  exclamationTriangleIcon,
  tableIcon,
  trashIcon,
  processOnVmIcon
)

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  encapsulation: ViewEncapsulation.None,
  standalone: false
})
export class AppComponent {
  private dcAdapterSettings: DcAdapterSettings | undefined
  public commitVer: string
  public version: any
  public routeUrl: any
  public errTop: boolean | undefined
  public licenseExpiringDays: number | null = null
  public sasjsAborts: InfoModal[] = []

  public editorActive: boolean = false
  public approveActive: boolean = false
  public freeTierBanner: boolean = this.licenceService.isAppFreeTier.value
  public licenceProblem = this.licenceService.licenceProblem
  public appOverCapacity: boolean = false
  public appActive: boolean | null = null
  public requestsModal: boolean = false
  public showRegistration: boolean = true
  public startupDataLoaded: boolean = false
  public startupSteps: StartupStep[] = []
  public demoLimitNotice: { open: boolean; featureName: string } = {
    open: false,
    featureName: ''
  }

  public syssite = this.appService.syssite
  public licenceState = this.licenceService.licenceState
  public embed = globals.embed

  constructor(
    private appService: AppService,
    private licenceService: LicenceService,
    public router: Router,
    private route: ActivatedRoute,
    private location: Location,
    private eventService: EventService,
    private appStoreService: AppStoreService,
    private startupCheckService: StartupCheckService,
    private cdr: ChangeDetectorRef,
    private elementRef: ElementRef
  ) {
    this.parseDcAdapterSettings()

    this.startupCheckService.steps$.subscribe((steps) => {
      this.startupSteps = steps
    })

    /**
     * Prints app info in the console such as:
     * - Adapter versions
     * - App version
     * - Build timestamp
     *
     */
    ;(window as any).appinfo = () => {
      console.table({
        'Adapter version': VERSION.adapterVersion || 'n/a',
        'App version': (VERSION.tag || '').replace('v', ''),
        'Build timestamp': moment(parseInt(VERSION.timestamp)).format(
          'DD-MMM-YYYY HH:MM'
        ),
        '...': '...'
      })
    }

    this.subscribeToLicenseEvents()

    /**
     * Fetches git tag ang git hash from `version.ts` file
     * It's placed in the user drop down.
     */
    this.commitVer = (VERSION.tag || '').replace('v', '') + '.' + VERSION.hash

    router.events.subscribe((val) => {
      this.routeUrl = this.router.url

      if (typeof this.routeUrl !== 'undefined' && this.routeUrl.length > 4) {
        let rootParam = this.routeUrl.split('/')[1]

        if (rootParam === 'editor') {
          this.errTop = true
          this.editorActive = true
          this.approveActive = false
        } else if (rootParam === 'home') {
          this.errTop = false
          this.editorActive = true
          this.approveActive = false
        } else {
          this.errTop = true
          this.editorActive = false
        }

        globals.rootParam = rootParam
      }

      if (typeof this.routeUrl !== 'undefined' && this.routeUrl.length > 6) {
        if (this.routeUrl.includes('approveDet')) {
          this.approveActive = true
        } else if (this.routeUrl.includes('toapprove')) {
          this.approveActive = true
        } else {
          this.approveActive = false
        }
      }
    })

    const hashQuery = window.location.hash.split('?')[1]
    if (hashQuery && new URLSearchParams(hashQuery).get('embed') !== null) {
      const embed = parseEmbedParam(hashQuery)
      globals.embed = embed
      this.embed = embed
    }

    this.subscribeToShowAbortModal()
    this.subscribeToRequestsModal()
    this.subscribeToStartupData()
    this.subscribeToAppActive()
    this.subscribeToDemoLimitModal()

    /**
     * In Viya streaming apps, content is served within an iframe.  This code
     * makes that iframe "full screen" so it looks like a regular window.
     */
    if (window.frameElement) {
      window.frameElement.setAttribute(
        'style',
        'height:100%;width:100%;position:absolute'
      )
      window.frameElement.setAttribute('allowfullscreen', '')
      window.frameElement.setAttribute('frameborder', '0')
      window.frameElement.setAttribute('marginheight', '0')
      window.frameElement.setAttribute('marginwidth', '0')
      window.frameElement.setAttribute('scrolling', 'auto')
      window.focus()
    }
  }

  /**
   * Parses adapter settings that are found in the <sasjs> tag inside index.html
   */
  private parseDcAdapterSettings() {
    /* The <sasjs> tag is a mandatory part of index.html - it carries serverUrl,
      serverType and the rest of the adapter config - so there is no config to
      fall back on when it is absent. A missing one is reported on the boot
      screen below rather than handled with an abort modal. */
    const sasjsElement = document.querySelector('sasjs')

    const getAppAttribute = (attribute: string) =>
      sasjsElement?.getAttribute(attribute) || undefined

    const dcAdapterSettings = {
      serverUrl: getAppAttribute('serverUrl') || '',
      appLoc: getAppAttribute('appLoc') || '',
      serverType: getAppAttribute('serverType'),
      loginMechanism: getAppAttribute('loginMechanism') || '',
      dcPath: getAppAttribute('dcPath') || '',
      debug: getAppAttribute('debug') === 'true' || false,
      useComputeApi: this.parseComputeApi(getAppAttribute('useComputeApi')),
      runAsTask: getAppAttribute('runAsTask') === 'true' || false,
      contextName: getAppAttribute('contextName') || ''
    }

    this.dcAdapterSettings = dcAdapterSettings as any
    this.appStoreService.setDcAdapterSettings(dcAdapterSettings as any)

    /* Show the <sasjs> tag contents on the boot screen, so the effective
      configuration (serverType, debug, useComputeApi, ...) is visible without
      having to read the page source. The tag is mandatory - without it the
      adapter has no serverUrl or serverType - so a missing one is called out
      plainly rather than silently leaving the step pending. */
    if (sasjsElement) {
      this.startupCheckService.setTagDetails(getSasjsTagDetails(sasjsElement))
    } else {
      this.startupCheckService.updateStep(
        SASJS_TAG_STEP,
        'error',
        'SASJS TAG MISSING!!'
      )
    }

    this.appService.sasServiceInit()
  }

  /**
   * Opens licence page with the active licence problem
   * Problem details are encoded in the url
   */
  public licenceProblemDetails(url: string) {
    this.router.navigateByUrl(url)
  }

  /**
   * Based on string provided we return true, false or null
   * True -> Compute API
   * False -> JES API
   * Null -> JES WEB
   * @param value provided in the html <sasjs> tag
   * @returns true, false or null
   */
  private parseComputeApi(value: string | undefined): boolean | null {
    if (value === undefined) return null

    if (value === 'undefined' || value === 'null') return null

    return value === 'true' || false
  }

  /**
   * Listens for an `demo limit` event that will show the `Feature locked modal`
   * For exmaple when in editor upload feature is not enabled
   * When user tries to upload the excel, editor component will trgger this event
   * And stop the execution of file upload code.
   */
  public subscribeToDemoLimitModal() {
    this.eventService.onDemoLimitModalShow.subscribe((featureName: string) => {
      this.demoLimitNotice = {
        open: true,
        featureName
      }
    })
  }

  /**
   * Listens for licence events so banner can be displayed.
   * App is free tier, licence will expire, is expired or is invalid
   */
  public subscribeToLicenseEvents() {
    this.licenceService.isAppFreeTier.subscribe((isAppFreeTier: boolean) => {
      this.freeTierBanner = isAppFreeTier
    })

    this.licenceService.licenseExpiresInDays.subscribe(
      (licenseExpiringDays: number | null) => {
        if (licenseExpiringDays && licenseExpiringDays <= 14)
          this.licenseExpiringDays = licenseExpiringDays
      }
    )

    this.licenceService.isAppOverCapacity.subscribe(
      (isAppOverAppCapacity: boolean) => {
        this.appOverCapacity = isAppOverAppCapacity
      }
    )
  }

  /**
   * Listens for an event that will activate od deactivate full application.
   * Based on licence key prcoessing result
   */
  public subscribeToAppActive() {
    this.licenceService.isAppActivated.subscribe((value: any) => {
      this.appActive = value
    })
  }

  /**
   * Listnes to an event that is fired when showing abort modal
   * Then pushes object to array and modal is displayed
   * `abortId` is calculated and assigned so that modal can be removed and closed
   * it's an incrementing number
   */
  public subscribeToShowAbortModal() {
    this.eventService.onShowAbortModal.subscribe((sasjsAbort: InfoModal) => {
      /* One message at a time.  Pushing to the array stacked a second modal on
         top of the one already open - two dismissals for one failure, and the
         lower one was often a duplicate of the upper.  Replacing keeps the
         newest message, which is the one the user has to act on. */
      sasjsAbort.id = Date.now()
      this.sasjsAborts = [sasjsAbort]
      this.cdr.detectChanges() //Changes were not triggered while hot is focused
    })
  }

  /**
   * When startupservice request is finished with valid response, this event will
   * make sure loading screen is gone.
   */
  public subscribeToStartupData() {
    this.eventService.onStartupDataLoaded.subscribe(() => {
      this.startupDataLoaded = true
    })
  }

  /**
   * Opens requests modal when requested from event service
   */
  public subscribeToRequestsModal() {
    this.eventService.onRequestsModalOpen.subscribe((value: boolean) => {
      this.requestsModal = true
    })
  }

  /**
   * Closes abort modal with matching ID (there could be multiple abort modals open)
   */
  public closeAbortModal(abortId: number) {
    let abortIndex = this.sasjsAborts.findIndex((abort) => abort.id === abortId)
    this.sasjsAborts.splice(abortIndex, 1)
  }

  /**
   * Toggles sidebar when requested from event service
   */
  public toggleSidebar() {
    this.eventService.toggleSidebar()
  }

  /**
   * Whether or not current route includes the route from param
   * @param route route to check
   */
  public isMainRoute(route: string): boolean {
    return this.router.url.includes(route)
  }

  /**
   * Opens a page for updating the licence.
   */
  public openLicencingPage() {
    this.router.navigateByUrl('/licensing/update')
  }

  /** "lib.table" of the last-opened editor table (for the VA embed back button). */
  get vaEditorLibds(): string {
    const e = globals.editor
    return e?.library && e?.table ? `${e.library}.${e.table}` : ''
  }

  /**
   * VA embed only: returns to the editor for the last-opened table. Lets users
   * who land on the stage/approve/review pages get back without knowing DC's
   * page paths. `globals.embed` (set at app init) keeps the editor in VA mode.
   */
  public backToEditor(): void {
    if (this.vaEditorLibds) {
      this.router.navigate(['/editor/' + this.vaEditorLibds], {
        queryParams: { embed: 'va' }
      })
    }
  }
}
<div class="main-container">
  @if (!router.url.includes('licensing')) {
    @if (freeTierBanner && (!licenseExpiringDays || licenseExpiringDays < 0)) {
      <div
        class="alert alert-app-level alert-warning"
        id="demo-banner"
        role="alert"
      >
        @if (licenceProblem.value === null) {
          <div class="alert-items">
            <div class="alert-item static">
              <div class="alert-icon-wrapper">
                <cds-icon
                  class="alert-icon"
                  shape="warning-standard"
                ></cds-icon>
              </div>
              <div class="alert-text">
                Data Controller (FREE Tier) - to upgrade contact
                <contact-link classes="color-white" />
              </div>
            </div>
          </div>
          <a routerLink="/licensing/update" class="update-key"
            >Update Licence Key</a
          >
        }
        @if (licenceProblem.value !== null) {
          <div class="alert-items">
            <div class="alert-item static">
              <div class="alert-icon-wrapper">
                <cds-icon
                  class="alert-icon"
                  shape="warning-standard"
                ></cds-icon>
              </div>
              <div class="alert-text">
                Data Controller (FREE Tier) - Problem with licence
              </div>
            </div>
          </div>
          <a
            (click)="licenceProblemDetails(licenceProblem.value)"
            class="update-key cursor-pointer"
            >More details</a
          >
        }
      </div>
    }
    @if (licenseExpiringDays && !freeTierBanner) {
      <div
        class="alert alert-app-level alert-danger"
        id="demo-banner"
        role="alert"
      >
        <div class="alert-items">
          <div class="alert-item static">
            <div class="alert-icon-wrapper">
              <cds-icon class="alert-icon" shape="warning-standard"></cds-icon>
            </div>
            <div class="alert-text">
              This license key will expire in {{ licenseExpiringDays }}
              {{ licenseExpiringDays === 1 ? 'day' : 'days' }}. Please contact
              <contact-link classes="color-white" />
              or your reseller to arrange additional licence for site id
              {{ syssite.getValue() }}.
            </div>
          </div>
        </div>
        @if (!freeTierBanner) {
          <a routerLink="/licensing/update" class="update-key"
            >Update Licence Key</a
          >
        }
      </div>
    }
    @if (appOverCapacity) {
      <div
        class="alert alert-app-level alert-danger"
        id="demo-banner"
        role="alert"
      >
        <div class="alert-items">
          <div class="alert-item static">
            <div class="alert-icon-wrapper">
              <cds-icon class="alert-icon" shape="warning-standard"></cds-icon>
            </div>
            <div class="alert-text">
              The registered number of users exceeds the limit specified for
              your license. Please contact
              <contact-link classes="color-white" />
              or your reseller to arrange additional licence for site id
              {{ syssite.getValue() }}.
            </div>
          </div>
        </div>
        @if (!licenseExpiringDays && !freeTierBanner) {
          <a routerLink="/licensing/update" class="update-key"
            >Update Licence Key</a
          >
        }
      </div>
    }
  }

  @if (!embed) {
    <header class="app-header">
      <!-- <button
      *ngIf="
        isMainRoute('view') ||
        (isMainRoute('home') && !router.url.includes('licensing'))
      "
              class="header-hamburger-trigger"
              (click)="toggleSidebar()"
              type="button"
              >
              <span></span>
            </button> -->
      @if (
        isMainRoute('view') ||
        (isMainRoute('home') && !router.url.includes('licensing'))
      ) {
        <div
          (click)="toggleSidebar()"
          type="button"
          aria-label="Toggle sidebar"
          class="cursor-pointer select-none ml-10 d-flex clr-justify-content-center clr-align-items-center"
        >
          <clr-icon size="24" shape="tree-view" aria-hidden="true"></clr-icon>
        </div>
      }
      <div class="logo d-flex clr-align-items-center">
        <a href="#" [routerLink]="['/']" class="nav-link">
          <img
            class="without-text d-block d-md-none"
            src="images/dc-logo.svg"
            alt="datacontroller logo without text"
          />
          <img
            class="with-text d-none d-md-block"
            src="images/datacontroller.svg"
            alt="datacontroller logo"
          />
        </a>
      </div>
      @if (
        !router.url.includes('deploy') && !router.url.includes('licensing')
      ) {
        <div class="header-nav d-flex d-sm-none">
          <clr-dropdown>
            <button
              class="nav-icon color-white-i"
              clrDropdownTrigger
              aria-label="toggle settings menu"
            >
              Menu
              <!-- <clr-icon size="20" shape="bars"></clr-icon> -->
            </button>
            <clr-dropdown-menu *clrIfOpen clrPosition="bottom-left">
              <a [routerLink]="['/view']" clrDropdownItem>VIEW</a>
              <a [routerLink]="['/home']" clrDropdownItem>LOAD</a>
              <a [routerLink]="['/review/submitted']" clrDropdownItem>REVIEW</a>
            </clr-dropdown-menu>
          </clr-dropdown>
        </div>
        <div class="header-nav d-none d-sm-flex">
          <a
            [routerLink]="['/view']"
            class="nav-link nav-text"
            routerLinkActive="active"
            >VIEW</a
          >
          <a
            [routerLink]="['/home']"
            class="nav-link nav-text"
            [class.active]="
              router.url.includes('editor') ||
              router.url.includes('edit-record') ||
              router.url.includes('home')
            "
            >LOAD</a
          >
          <a
            [routerLink]="['/review/submitted']"
            [class.active]="
              router.url.includes('submitted') ||
              router.url.includes('approve') ||
              router.url.includes('history')
            "
            class="nav-link nav-text cursor-pointer"
            >REVIEW</a
          >
        </div>
      }
      <app-header-actions></app-header-actions>
    </header>
  }
  @if (
    !embed &&
    (router.url.includes('submitted') ||
      router.url.includes('approve') ||
      router.url.includes('history'))
  ) {
    <nav class="subnav">
      <ul class="nav">
        <li class="nav-item">
          <a
            [routerLink]="['/review/submitted']"
            class="nav-link nav-text"
            routerLinkActive="active"
            >SUBMIT</a
          >
        </li>
        <li class="nav-item">
          <a
            [routerLink]="['/review/approve']"
            class="nav-link nav-text"
            [class.active]="router.url.includes('approve')"
            routerLinkActive="active"
            >APPROVE</a
          >
        </li>
        <li class="nav-item">
          <a
            [routerLink]="['/review/history']"
            class="nav-link nav-text"
            routerLinkActive="active"
            >HISTORY</a
          >
        </li>
      </ul>
    </nav>
  }

  @if (!errTop) {
    <app-alerts></app-alerts>
  }
  <app-requests-modal [(opened)]="requestsModal"></app-requests-modal>
  <app-excel-password-modal></app-excel-password-modal>

  <!-- <app-terms *ngIf="showRegistration"></app-terms> -->

  <!-- VA embed, back to Editor button -->
  @if (embed === 'va' && vaEditorLibds && !isMainRoute('/editor')) {
    <div class="va-back-bar">
      <button
        type="button"
        class="btn btn-sm btn-primary"
        (click)="backToEditor()"
      >
        <clr-icon
          aria-hidden="true"
          shape="caret"
          dir="left"
          size="16"
        ></clr-icon>
        Back to Edit table
      </button>
    </div>
  }

  @if (startupDataLoaded) {
    <router-outlet></router-outlet>
  }

  <app-login></app-login>
  @if (errTop) {
    <app-alerts></app-alerts>
  }
  @for (abort of sasjsAborts; track abort) {
    <app-info-modal
      [data]="abort"
      [forceReload]="!startupDataLoaded && sasjsAborts.length === 1"
      (onConfirmModalClick)="closeAbortModal(abort.id!)"
    >
    </app-info-modal>
  }

  <clr-modal
    appDragNdrop
    [(clrModalOpen)]="demoLimitNotice.open"
    [clrModalClosable]="true"
    [clrModalSize]="'lg'"
    class="position-relative"
  >
    <h3 class="modal-title">
      Locked Feature ({{ demoLimitNotice.featureName }})
      <clr-icon size="20" shape="lock"></clr-icon>
    </h3>

    <div class="modal-body">
      Contact
      <contact-link />
      with your site id ({{ syssite.value }}) to activate!
    </div>
  </clr-modal>
</div>

<!-- App Loading Page -->
@if (!startupDataLoaded) {
  <div class="app-loading">
    <img
      class="loading-logo"
      src="images/datacontroller.svg"
      alt="datacontroller logo"
    />
    @if (appActive === null) {
      <div class="slider">
        <div class="line"></div>
        <div class="subline inc"></div>
        <div class="subline dec"></div>
      </div>
    }
    @if (startupSteps.length > 0) {
      <div class="startup-checks">
        @for (step of startupSteps; track step.label) {
          <div
            class="startup-check"
            [class.startup-check--error]="step.status === 'error'"
            [class.startup-check--done]="step.status === 'done'"
            [class.startup-check--grid]="step.details?.length"
          >
            @switch (step.status) {
              @case ('pending') {
                <clr-icon
                  shape="circle"
                  class="is-solid startup-check__icon--pending"
                ></clr-icon>
              }
              @case ('in_progress') {
                <clr-spinner clrSmall></clr-spinner>
              }
              @case ('done') {
                <clr-icon
                  shape="check-circle"
                  class="is-solid startup-check__icon--done"
                ></clr-icon>
              }
              @case ('error') {
                <clr-icon
                  shape="exclamation-circle"
                  class="is-solid startup-check__icon--error"
                ></clr-icon>
              }
            }
            <span class="startup-check__label">{{ step.label }}</span>
            @if (step.detail) {
              <span class="startup-check__detail">{{ step.detail }}</span>
            }
            @if (step.details?.length) {
              <div class="startup-check__grid">
                @for (detail of step.details; track detail.name) {
                  <span class="startup-check__grid-name">{{
                    detail.name
                  }}</span>
                  <span class="startup-check__grid-value">{{
                    detail.value
                  }}</span>
                }
              </div>
            }
          </div>
        }
      </div>
    }
  </div>
}
<!-- /App Loading Page -->

./app.component.scss

.va-back-bar {
  position: sticky;
  top: 0;
  z-index: 1000;
  display: flex;
  justify-content: flex-start;
  padding: 0.35rem 0.6rem;
  background: #fafafa;
  border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""