|
| 1 | +import { Component, OnInit, Input, NgZone, forwardRef, OnDestroy } from '@angular/core'; |
| 2 | +import { CommonModule } from '@angular/common'; |
| 3 | +import { MatSnackBarModule, MatSnackBar } from '@angular/material/snack-bar'; |
| 4 | +import { Subscription } from 'rxjs'; |
| 5 | +import { FooterComponent } from '../footer/footer.component'; |
| 6 | +import { AngularPConnectData, AngularPConnectService, Utils } from '@pega/angular-sdk-components'; |
| 7 | +import { ErrorMessagesService } from '@pega/angular-sdk-components'; |
| 8 | +import { ComponentMapperComponent } from '@pega/angular-sdk-components'; |
| 9 | + |
| 10 | +interface IPage { |
| 11 | + classID: string; |
| 12 | + pxPageViewIcon: string; |
| 13 | + pyClassName: string; |
| 14 | + pyLabel: string; |
| 15 | + pyRuleName: string; |
| 16 | + pyURLContent: string; |
| 17 | +} |
| 18 | + |
| 19 | +interface AppShellProps { |
| 20 | + // If any, enter additional props that only exist on this component |
| 21 | + pages: IPage[]; |
| 22 | + caseTypes?: object[]; |
| 23 | + portalLogo: string; |
| 24 | + portalName: string; |
| 25 | + portalTemplate: string; |
| 26 | + readOnly?: boolean; |
| 27 | + showAppHeaderBar: boolean; |
| 28 | + showAppName: boolean; |
| 29 | +} |
| 30 | + |
| 31 | +@Component({ |
| 32 | + selector: 'app-app-shell', |
| 33 | + templateUrl: './app-shell.component.html', |
| 34 | + styleUrls: ['./app-shell.component.scss'], |
| 35 | + imports: [CommonModule, MatSnackBarModule, FooterComponent, forwardRef(() => ComponentMapperComponent)] |
| 36 | +}) |
| 37 | +export class AppShellComponent implements OnInit, OnDestroy { |
| 38 | + @Input() pConn$: typeof PConnect; |
| 39 | + |
| 40 | + // For interaction with AngularPConnect |
| 41 | + angularPConnectData: AngularPConnectData = {}; |
| 42 | + configProps$: AppShellProps; |
| 43 | + |
| 44 | + pages$: IPage[]; |
| 45 | + caseTypes$?: object[]; |
| 46 | + arChildren$: any[]; |
| 47 | + bShowAppShell$ = false; |
| 48 | + appName$ = ''; |
| 49 | + errorMessagesSubscription: Subscription; |
| 50 | + sErrorMessages = ''; |
| 51 | + snackBarRef: any; |
| 52 | + bOkDisplayError = false; |
| 53 | + portalTemplate: string; |
| 54 | + links: any = []; |
| 55 | + imageURL: string | Blob; |
| 56 | + localizedVal = PCore.getLocaleUtils().getLocaleValue; |
| 57 | + |
| 58 | + constructor( |
| 59 | + private angularPConnect: AngularPConnectService, |
| 60 | + private erService: ErrorMessagesService, |
| 61 | + private snackBar: MatSnackBar, |
| 62 | + private ngZone: NgZone, |
| 63 | + private utils: Utils |
| 64 | + ) {} |
| 65 | + |
| 66 | + ngOnInit() { |
| 67 | + // First thing in initialization is registering and subscribing to the AngularPConnect service |
| 68 | + this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); |
| 69 | + |
| 70 | + // Then, continue on with other initialization |
| 71 | + |
| 72 | + this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as AppShellProps; |
| 73 | + |
| 74 | + this.portalTemplate = this.configProps$.portalTemplate; |
| 75 | + |
| 76 | + // making a copy, so can add info |
| 77 | + this.pages$ = this.configProps$.pages; |
| 78 | + |
| 79 | + this.links = this.pages$.filter((page, index) => { |
| 80 | + return index !== 0; |
| 81 | + }); |
| 82 | + |
| 83 | + if (this.pages$) { |
| 84 | + this.bShowAppShell$ = true; |
| 85 | + } |
| 86 | + |
| 87 | + /* TODO: We're setting the `pyPortalTemplate` for now, this would be handled by the CoreJS in the future releases */ |
| 88 | + if (this.portalTemplate === 'wss') { |
| 89 | + PCore.getEnvironmentInfo().setEnvironmentInfo({ ...PCore.getEnvironmentInfo().environmentInfoObject, pyPortalTemplate: 'wss' } as any); |
| 90 | + } |
| 91 | + |
| 92 | + // @ts-ignore - Property 'pyCaseTypesAvailableToCreateDP' does not exist on type pxApplication |
| 93 | + const caseTypesAvailableToCreateDP = PCore.getEnvironmentInfo().environmentInfoObject?.pxApplication?.pyCaseTypesAvailableToCreateDP; |
| 94 | + if (caseTypesAvailableToCreateDP) { |
| 95 | + const portalID = this.pConn$.getValue('.pyOwner'); |
| 96 | + PCore.getDataPageUtils() |
| 97 | + .getPageDataAsync(caseTypesAvailableToCreateDP, this.pConn$.getContextName(), { |
| 98 | + PortalName: portalID |
| 99 | + }) |
| 100 | + .then((response: any) => { |
| 101 | + if (response?.pyCaseTypesAvailableToCreate) { |
| 102 | + this.pConn$.replaceState('.pyCaseTypesAvailableToCreate', response.pyCaseTypesAvailableToCreate, { |
| 103 | + skipDirtyValidation: true |
| 104 | + }); |
| 105 | + } |
| 106 | + }); |
| 107 | + } |
| 108 | + |
| 109 | + this.caseTypes$ = this.configProps$.caseTypes; |
| 110 | + |
| 111 | + this.arChildren$ = this.pConn$.getChildren(); |
| 112 | + |
| 113 | + // handle showing and hiding the progress spinner |
| 114 | + this.errorMessagesSubscription = this.erService.getMessage().subscribe(message => { |
| 115 | + this.showDismissErrorMessages(message); |
| 116 | + }); |
| 117 | + |
| 118 | + // cannot call checkAndUpdate becasue first time through, will call updateSelf and that is incorrect (causes issues). |
| 119 | + // however, need angularPConnect to be initialized with currentProps for future updates, so calling shouldComponentUpdate directly |
| 120 | + // without checking to update here in init, will initialize and this is correct |
| 121 | + this.angularPConnect.shouldComponentUpdate(this); |
| 122 | + } |
| 123 | + |
| 124 | + ngOnDestroy(): void { |
| 125 | + if (this.angularPConnectData.unsubscribeFn) { |
| 126 | + this.angularPConnectData.unsubscribeFn(); |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + // Callback passed when subscribing to store change |
| 131 | + onStateChange() { |
| 132 | + this.checkAndUpdate(); |
| 133 | + } |
| 134 | + |
| 135 | + checkAndUpdate() { |
| 136 | + // Should always check the bridge to see if the component should |
| 137 | + // update itself (re-render) |
| 138 | + const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); |
| 139 | + |
| 140 | + // ONLY call updateSelf when the component should update |
| 141 | + if (bUpdateSelf) { |
| 142 | + this.updateSelf(); |
| 143 | + } |
| 144 | + } |
| 145 | + |
| 146 | + updateSelf() { |
| 147 | + this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as AppShellProps; |
| 148 | + |
| 149 | + const showAppName = this.configProps$.showAppName; |
| 150 | + const envInfo = PCore.getEnvironmentInfo(); |
| 151 | + const appNameToDisplay = showAppName ? envInfo.getApplicationLabel() : ''; |
| 152 | + const portalClass = this.pConn$.getValue('.classID', ''); // 2nd arg empty string until typedef marked correctly |
| 153 | + const envPortalName = envInfo.getPortalName(); |
| 154 | + |
| 155 | + this.ngZone.run(() => { |
| 156 | + // making a copy, so can add info |
| 157 | + this.pages$ = this.configProps$.pages; |
| 158 | + |
| 159 | + if (this.pages$) { |
| 160 | + this.bShowAppShell$ = true; |
| 161 | + } |
| 162 | + |
| 163 | + this.caseTypes$ = this.configProps$.caseTypes; |
| 164 | + this.arChildren$ = this.pConn$.getChildren(); |
| 165 | + }); |
| 166 | + |
| 167 | + const portalLogo = this.configProps$.portalLogo; |
| 168 | + // using the default icon then fetch it from the static folder (not auth involved) |
| 169 | + if ( |
| 170 | + !portalLogo || |
| 171 | + portalLogo.toLowerCase().includes('pzpega-logo-mark') || |
| 172 | + portalLogo.toLowerCase().includes('py-logo') || |
| 173 | + portalLogo.toLowerCase().includes('py-full-logo') |
| 174 | + ) { |
| 175 | + const portalLogoImage = this.utils.getIconPath(this.utils.getSDKStaticContentUrl()).concat('pzpega-logo-mark.svg'); |
| 176 | + this.imageURL = portalLogoImage; |
| 177 | + } |
| 178 | + // not using default icon to fetch it using the way which uses authentication |
| 179 | + else { |
| 180 | + PCore.getAssetLoader() |
| 181 | + .getSvcImageUrl(portalLogo) |
| 182 | + .then(data => { |
| 183 | + this.imageURL = data; |
| 184 | + }) |
| 185 | + .catch(() => { |
| 186 | + console.error(`${this.localizedVal('Unable to load the image for the portal logo/icon with the insName', 'AppShell')}:${portalLogo}`); |
| 187 | + }); |
| 188 | + } |
| 189 | + this.appName$ = this.localizedVal(appNameToDisplay || '', '', `${portalClass}!PORTAL!${envPortalName}`.toUpperCase()); |
| 190 | + } |
| 191 | + |
| 192 | + // fpr show/hiding error messages in the SnackBar component |
| 193 | + showDismissErrorMessages(errorMessages: any) { |
| 194 | + switch (errorMessages.action) { |
| 195 | + case 'update': |
| 196 | + // won't show unless publish is turned on |
| 197 | + |
| 198 | + if (this.sErrorMessages.indexOf(errorMessages.actionMessage) < 0) { |
| 199 | + this.sErrorMessages = this.sErrorMessages.concat(errorMessages.actionMessage).concat('\n'); |
| 200 | + |
| 201 | + if (this.bOkDisplayError) { |
| 202 | + const config = { panelClass: ['snackbar-newline'], duration: 5000 }; |
| 203 | + this.snackBarRef = this.snackBar.open(this.sErrorMessages, 'Ok', config); |
| 204 | + } |
| 205 | + } |
| 206 | + break; |
| 207 | + case 'show': |
| 208 | + // add error message if not in the list |
| 209 | + // won't show unless publish is turned on |
| 210 | + |
| 211 | + if (this.sErrorMessages.indexOf(errorMessages.actionMessage) < 0) { |
| 212 | + this.sErrorMessages = this.sErrorMessages.concat(errorMessages.actionMessage).concat('\n'); |
| 213 | + } |
| 214 | + |
| 215 | + this.bOkDisplayError = true; |
| 216 | + |
| 217 | + if (this.bOkDisplayError) { |
| 218 | + const config = { panelClass: ['snackbar-newline'], duration: 5000 }; |
| 219 | + this.snackBarRef = this.snackBar.open(this.sErrorMessages, 'Ok', config); |
| 220 | + } |
| 221 | + // this.snackBarRef.afterDismissed().subscribe( info => { |
| 222 | + // this.sErrorMessages = ""; |
| 223 | + // } |
| 224 | + // ) |
| 225 | + break; |
| 226 | + case 'dismiss': |
| 227 | + // closes snack bar |
| 228 | + // turns publish off |
| 229 | + // clears out errors |
| 230 | + // should be called to dimiss and at "cancel" |
| 231 | + if (this.snackBarRef != null) { |
| 232 | + this.snackBarRef.dismiss(); |
| 233 | + this.sErrorMessages = ''; |
| 234 | + this.bOkDisplayError = false; |
| 235 | + } |
| 236 | + break; |
| 237 | + case 'publish': |
| 238 | + // allows errors to be shown, clears out existing ones |
| 239 | + // should be turned on at "submit" (finishAssignment, nextAssignment, etc.) |
| 240 | + |
| 241 | + this.bOkDisplayError = true; |
| 242 | + this.sErrorMessages = ''; |
| 243 | + break; |
| 244 | + default: |
| 245 | + break; |
| 246 | + } |
| 247 | + } |
| 248 | +} |
0 commit comments