src/app/app.component.ts
| encapsulation | ViewEncapsulation.None |
| selector | my-app |
| styleUrls | ./app.component.scss |
| templateUrl | ./app.component.html |
No results matching.
Properties |
|
Methods |
|
Accessors |
constructor(appService: AppService, licenceService: LicenceService, router: Router, route: ActivatedRoute, location: Location, eventService: EventService, appStoreService: AppStoreService, startupCheckService: StartupCheckService, cdr: ChangeDetectorRef, elementRef: ElementRef)
|
|||||||||||||||||||||||||||||||||
|
Defined in src/app/app.component.ts:81
|
|||||||||||||||||||||||||||||||||
|
Parameters :
|
| Public backToEditor |
backToEditor()
|
|
Defined in src/app/app.component.ts:387
|
|
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.
Returns :
void
|
| Public closeAbortModal | ||||||
closeAbortModal(abortId: number)
|
||||||
|
Defined in src/app/app.component.ts:349
|
||||||
|
Closes abort modal with matching ID (there could be multiple abort modals open)
Parameters :
Returns :
void
|
| Public isMainRoute | ||||||||
isMainRoute(route: string)
|
||||||||
|
Defined in src/app/app.component.ts:365
|
||||||||
|
Whether or not current route includes the route from param
Parameters :
Returns :
boolean
|
| Public licenceProblemDetails | ||||||
licenceProblemDetails(url: string)
|
||||||
|
Defined in src/app/app.component.ts:241
|
||||||
|
Opens licence page with the active licence problem Problem details are encoded in the url
Parameters :
Returns :
void
|
| Public openLicencingPage |
openLicencingPage()
|
|
Defined in src/app/app.component.ts:372
|
|
Opens a page for updating the licence.
Returns :
void
|
| Private parseComputeApi | ||||||||
parseComputeApi(value: string | undefined)
|
||||||||
|
Defined in src/app/app.component.ts:253
|
||||||||
|
Based on string provided we return true, false or null True -> Compute API False -> JES API Null -> JES WEB
Parameters :
Returns :
boolean | null
true, false or null |
| Private parseDcAdapterSettings |
parseDcAdapterSettings()
|
|
Defined in src/app/app.component.ts:194
|
|
Parses adapter settings that are found in the
Returns :
void
|
| Public subscribeToAppActive |
subscribeToAppActive()
|
|
Defined in src/app/app.component.ts:303
|
|
Listens for an event that will activate od deactivate full application. Based on licence key prcoessing result
Returns :
void
|
| Public subscribeToDemoLimitModal |
subscribeToDemoLimitModal()
|
|
Defined in src/app/app.component.ts:267
|
|
Listens for an
Returns :
void
|
| Public subscribeToLicenseEvents |
subscribeToLicenseEvents()
|
|
Defined in src/app/app.component.ts:280
|
|
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()
|
|
Defined in src/app/app.component.ts:340
|
|
Opens requests modal when requested from event service
Returns :
void
|
| Public subscribeToShowAbortModal |
subscribeToShowAbortModal()
|
|
Defined in src/app/app.component.ts:315
|
|
Listnes to an event that is fired when showing abort modal
Then pushes object to array and modal is displayed
Returns :
void
|
| Public subscribeToStartupData |
subscribeToStartupData()
|
|
Defined in src/app/app.component.ts:331
|
|
When startupservice request is finished with valid response, this event will make sure loading screen is gone.
Returns :
void
|
| Public toggleSidebar |
toggleSidebar()
|
|
Defined in src/app/app.component.ts:357
|
|
Toggles sidebar when requested from event service
Returns :
void
|
| Public appActive |
Type : boolean | null
|
Default value : null
|
|
Defined in src/app/app.component.ts:69
|
| Public appOverCapacity |
Type : boolean
|
Default value : false
|
|
Defined in src/app/app.component.ts:68
|
| Public approveActive |
Type : boolean
|
Default value : false
|
|
Defined in src/app/app.component.ts:65
|
| Public commitVer |
Type : string
|
|
Defined in src/app/app.component.ts:57
|
| Private dcAdapterSettings |
Type : DcAdapterSettings | undefined
|
|
Defined in src/app/app.component.ts:56
|
| Public demoLimitNotice |
Type : { open: boolean; featureName: string }
|
Default value : {
open: false,
featureName: ''
}
|
|
Defined in src/app/app.component.ts:74
|
| Public editorActive |
Type : boolean
|
Default value : false
|
|
Defined in src/app/app.component.ts:64
|
| Public embed |
Type : unknown
|
Default value : globals.embed
|
|
Defined in src/app/app.component.ts:81
|
| Public errTop |
Type : boolean | undefined
|
|
Defined in src/app/app.component.ts:60
|
| Public freeTierBanner |
Type : boolean
|
Default value : this.licenceService.isAppFreeTier.value
|
|
Defined in src/app/app.component.ts:66
|
| Public licenceProblem |
Type : unknown
|
Default value : this.licenceService.licenceProblem
|
|
Defined in src/app/app.component.ts:67
|
| Public licenceState |
Type : unknown
|
Default value : this.licenceService.licenceState
|
|
Defined in src/app/app.component.ts:80
|
| Public licenseExpiringDays |
Type : number | null
|
Default value : null
|
|
Defined in src/app/app.component.ts:61
|
| Public requestsModal |
Type : boolean
|
Default value : false
|
|
Defined in src/app/app.component.ts:70
|
| Public router |
Type : Router
|
|
Defined in src/app/app.component.ts:86
|
| Public routeUrl |
Type : any
|
|
Defined in src/app/app.component.ts:59
|
| Public sasjsAborts |
Type : InfoModal[]
|
Default value : []
|
|
Defined in src/app/app.component.ts:62
|
| Public showRegistration |
Type : boolean
|
Default value : true
|
|
Defined in src/app/app.component.ts:71
|
| Public startupDataLoaded |
Type : boolean
|
Default value : false
|
|
Defined in src/app/app.component.ts:72
|
| Public startupSteps |
Type : StartupStep[]
|
Default value : []
|
|
Defined in src/app/app.component.ts:73
|
| Public syssite |
Type : unknown
|
Default value : this.appService.syssite
|
|
Defined in src/app/app.component.ts:79
|
| Public version |
Type : any
|
|
Defined in src/app/app.component.ts:58
|
| vaEditorLibds |
getvaEditorLibds()
|
|
Defined in src/app/app.component.ts:377
|
|
"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);
}