@pega/dx-component-builder-sdk
Version:
Utility for building custom UI components
766 lines (641 loc) • 20.7 kB
text/typescript
import { Component, OnInit, Input, NgZone, forwardRef, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import download from 'downloadjs';
import { AngularPConnectData, AngularPConnectService } from '@pega/angular-sdk-components';
import { Utils } from '@pega/angular-sdk-components';
import { ComponentMapperComponent } from '@pega/angular-sdk-components';
interface FileUtilityProps {
// If any, enter additional props that only exist on this component
label?: string;
}
export class FileUtilityComponent implements OnInit, OnDestroy {
pConn$: typeof PConnect;
// For interaction with AngularPConnect
angularPConnectData: AngularPConnectData = {};
arFullListAttachments: any[] = [];
lu_name$ = '';
lu_icon$ = '';
lu_bLoading$ = false;
lu_count$ = 0;
lu_arActions$: any[] = [];
lu_arItems$: any = [];
va_arItems$: any[] = [];
lu_onViewAllFunction: any;
bShowFileModal$ = false;
bShowLinkModal$ = false;
bShowViewAllModal$ = false;
arFileMainButtons$: any[] = [];
arFileSecondaryButtons$: any[] = [];
arLinkMainButtons$: any[] = [];
arLinkSecondaryButtons$: any[] = [];
arFiles$: any[] = [];
arFileList$: any[] = [];
removeFileFromList$: any;
arLinks$: any[] = [];
arLinksList$: any[] = [];
removeLinksFromList$: any;
link_title$ = '';
link_url$ = '';
closeSvgIcon$ = '';
currentCaseID = '';
addAttachmentsActions: any;
constructor(
private angularPConnect: AngularPConnectService,
private utils: Utils,
private ngZone: NgZone
) {}
ngOnInit(): void {
// // First thing in initialization is registering and subscribing to the AngularPConnect service
this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange);
const configProps: FileUtilityProps = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps());
this.lu_name$ = configProps.label ?? '';
this.lu_icon$ = 'paper-clip';
this.closeSvgIcon$ = this.utils.getImageSrc('times', this.utils.getSDKStaticContentUrl());
this.addAttachmentsActions = [
{
text: this.pConn$.getLocalizedValue('Add files', '', ''),
id: 'addNewFiles',
onClick: () => this.createModal('addLocalFile')
},
{
text: this.pConn$.getLocalizedValue('Add links', '', ''),
id: 'addNewLinks',
onClick: () => this.createModal('addLocalLink')
}
];
// const onViewAllCallback = () => this.onViewAll(this.arFullListAttachments);
this.lu_onViewAllFunction = { onClick: this.onViewAll.bind(this) };
this.removeFileFromList$ = { onClick: this.removeFileFromList.bind(this) };
this.removeLinksFromList$ = { onClick: this.removeLinksFromList.bind(this) };
this.updateSelf();
this.createModalButtons();
PCore.getPubSubUtils().subscribe(
PCore.getEvents().getCaseEvent().CASE_ATTACHMENTS_UPDATED_FROM_CASEVIEW,
this.updateSelf.bind(this),
'caseAttachmentsUpdateFromCaseview'
);
}
ngOnDestroy(): void {
if (this.angularPConnectData.unsubscribeFn) {
this.angularPConnectData.unsubscribeFn();
}
PCore.getPubSubUtils().unsubscribe(PCore.getEvents().getCaseEvent().CASE_ATTACHMENTS_UPDATED_FROM_CASEVIEW, 'caseAttachmentsUpdateFromCaseview');
}
// Callback passed when subscribing to store change
onStateChange() {
// adding a property to track in configProps, when ever the attachment file changes
// need to trigger a redraw
this.pConn$.registerAdditionalProps({
lastRefreshTime: ` ${PCore.getConstants().SUMMARY_OF_ATTACHMENTS_LAST_REFRESH_TIME}`
});
// Should always check the bridge to see if the component should update itself (re-render)
const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this);
// ONLY call updateSelf when the component should update
if (bUpdateSelf || this.caseHasChanged()) {
this.updateSelf();
}
}
onAttachFiles(files) {
const attachmentUtils = PCore.getAttachmentUtils();
const caseID = this.pConn$.getValue(PCore.getConstants().CASE_INFO.CASE_INFO_ID);
if (files.length > 0) {
this.lu_bLoading$ = true;
}
const arFiles: any = Array.from(files);
Promise.allSettled(
arFiles.map(file => attachmentUtils.uploadAttachment(file, this.onUploadProgress, this.errorHandler, this.pConn$.getContextName()))
)
.then((fileResponses: any) => {
const uploadedFiles: any = [];
fileResponses.forEach(fileResponse => {
if (fileResponse.status === 'fulfilled') {
uploadedFiles.push(fileResponse.value);
}
});
if (uploadedFiles.length > 0) {
(attachmentUtils.linkAttachmentsToCase(caseID, uploadedFiles, 'File', this.pConn$.getContextName()) as Promise<any>)
.then(() => {
this.refreshAttachments();
})
.catch();
}
})
.catch();
this.arFileList$ = [];
this.lu_bLoading$ = false;
}
refreshAttachments() {
this.updateSelf();
}
onUploadProgress() {}
errorHandler() {}
onAttachLinks(links) {
const attachmentUtils = PCore.getAttachmentUtils();
const caseID = this.pConn$.getValue(PCore.getConstants().CASE_INFO.CASE_INFO_ID);
if (links.length > 0) {
this.lu_bLoading$ = true;
}
const linksToAttach = links.map(link => ({
type: 'URL',
category: 'URL',
url: link.url,
name: link.linkTitle
}));
(attachmentUtils.linkAttachmentsToCase(caseID, linksToAttach, 'URL', this.pConn$.getContextName()) as Promise<any>)
.then(() => {
this.refreshAttachments();
})
.catch(console.log);
}
addAttachments(attsFromResp: any[] = []) {
this.lu_bLoading$ = false;
attsFromResp = attsFromResp.map(respAtt => {
const updatedAtt = {
...respAtt,
meta: `${respAtt.category} . ${this.utils.generateDateTime(respAtt.createTime, 'DateTime-Since')}, ${respAtt.createdBy}`
};
if (updatedAtt.type === 'FILE') {
updatedAtt.nameWithExt = updatedAtt.fileName;
}
return updatedAtt;
});
return attsFromResp;
}
onViewAll(): void {
this.bShowViewAllModal$ = true;
// add clickAway listener
// window.addEventListener('mouseup', this._clickAway.bind(this));
}
// Below method is not needed, still having it commented in case we want in future
// _clickAway(event: any) {
// let bInPopUp = false;
// // run through list of elements in path, if menu not in th path, then want to
// // hide (toggle) the menu
// for (const i in event.path) {
// if (event.path[i].className == 'psdk-modal-file-top' || event.path[i].tagName == 'BUTTON') {
// bInPopUp = true;
// break;
// }
// }
// if (!bInPopUp) {
// this.bShowViewAllModal$ = false;
// window.removeEventListener('mouseup', this._clickAway.bind(this));
// }
// }
_closeViewAll() {
this.bShowViewAllModal$ = false;
// window.removeEventListener('mouseup', this._clickAway.bind(this));
}
removeFileFromList(item: any) {
if (item != null) {
const arFileList = this.arFileList$;
const itemId = item.id;
for (let fileIndex = 0; fileIndex < arFileList.length; fileIndex++) {
if (arFileList[fileIndex].id == itemId) {
// remove the file from the list and redraw
this.ngZone.run(() => {
arFileList.splice(fileIndex, 1);
});
break;
}
}
}
}
removeLinksFromList(item: any) {
const localLinksList = this.arLinksList$.slice();
if (item != null) {
const itemId = item.id;
for (let linkIndex = 0; linkIndex < localLinksList.length; linkIndex++) {
if (localLinksList[linkIndex].id == itemId) {
// remove the file from the list and redraw
localLinksList.splice(linkIndex, 1);
this.ngZone.run(() => {
this.arLinksList$ = localLinksList.slice();
});
break;
}
}
}
}
getNewListUtilityItemProps = ({ att, cancelFile, downloadFile, deleteFile, removeFile }) => {
let actions;
let isDownloadable = false;
if (att.progress && att.progress !== 100) {
actions = [
{
id: `Cancel-${att.ID}`,
text: 'Cancel',
icon: 'times',
onClick: cancelFile
}
];
} else if (att.links) {
const isFile = att.type === 'FILE';
const ID = att.ID.replace(/\s/gi, '');
const actionsMap = new Map([
[
'download',
{
id: `download-${ID}`,
text: isFile ? 'Download' : 'Open',
icon: isFile ? 'download' : 'open',
onClick: downloadFile
}
],
[
'delete',
{
id: `Delete-${ID}`,
text: 'Delete',
icon: 'trash',
onClick: deleteFile
}
]
]);
actions = [];
actionsMap.forEach((action, actionKey) => {
if (att.links[actionKey]) {
actions.push(action);
}
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
isDownloadable = att.links.download;
} else if (att.error) {
actions = [
{
id: `Remove-${att.ID}`,
text: 'Remove',
icon: 'trash',
onClick: removeFile
}
];
}
return {
id: att.ID,
visual: {
icon: this.utils.getIconForAttachment(att),
progress: att.progress == 100 ? undefined : att.progress
},
primary: {
type: att.type,
name: att.name,
icon: 'trash',
click: removeFile
},
secondary: {
text: att.meta
},
actions
};
};
getListUtilityItemProps = ({ att, cancelFile, downloadFile, deleteFile, removeFile }) => {
let actions;
let isDownloadable = false;
if (att.progress && att.progress !== 100) {
actions = [
{
id: `Cancel-${att.ID}`,
text: this.pConn$.getLocalizedValue('Cancel', '', ''),
icon: 'times',
onClick: cancelFile
}
];
} else if (att.links) {
const isFile = att.type === 'FILE';
const ID = att.ID.replace(/\s/gi, '');
const actionsMap = new Map([
[
'download',
{
id: `download-${ID}`,
text: isFile ? this.pConn$.getLocalizedValue('Download', '', '') : this.pConn$.getLocalizedValue('Open', '', ''),
icon: isFile ? 'download' : 'open',
onClick: downloadFile
}
],
[
'delete',
{
id: `Delete-${ID}`,
text: this.pConn$.getLocalizedValue('Delete', '', ''),
icon: 'trash',
onClick: deleteFile
}
]
]);
actions = [];
actionsMap.forEach((action, actionKey) => {
if (att.links[actionKey]) {
actions.push(action);
}
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
isDownloadable = att.links.download;
} else if (att.error) {
actions = [
{
id: `Remove-${att.ID}`,
text: this.pConn$.getLocalizedValue('Remove', '', ''),
icon: 'trash',
onClick: removeFile
}
];
}
return {
id: att.ID,
visual: {
icon: this.utils.getIconForAttachment(att),
progress: att.progress == 100 ? undefined : att.progress
},
primary: {
type: att.type,
name: att.name,
icon: 'open',
click: downloadFile
},
secondary: {
text: att.meta
},
actions
};
};
_addLink() {
// copy list locally
const localList = this.arLinksList$.slice();
const url = this.link_url$;
if (!/^(http|https):\/\//.test(this.link_url$)) {
this.link_url$ = `http://${this.link_url$}`;
}
// list for display
let oLink: any = {};
oLink.icon = 'chain';
oLink.ID = `${new Date().getTime()}`;
oLink = this.getNewListUtilityItemProps({
att: oLink,
downloadFile: null,
cancelFile: null,
deleteFile: null,
removeFile: null
});
oLink.type = 'URL';
oLink.primary.type = oLink.type;
oLink.visual.icon = 'chain';
oLink.primary.name = this.link_title$;
oLink.primary.icon = 'open';
oLink.secondary.text = url;
this.ngZone.run(() => {
// need to create a new array or summary list won't detect changes
this.arLinksList$ = localList.slice();
this.arLinksList$.push(oLink);
// list for actually attachments
const link: any = {};
link.id = oLink.id;
link.linkTitle = this.link_title$;
link.type = oLink.type;
link.url = url;
this.arLinks$.push(link);
// clear values
this.link_title$ = '';
this.link_url$ = '';
});
}
_changeTitle(event: any) {
this.link_title$ = event.srcElement.value;
}
_changeUrl(event: any) {
this.link_url$ = event.srcElement.value;
}
downloadFile(att: any) {
const attachUtils = PCore.getAttachmentUtils();
const { ID, name, extension, type } = att;
const context = this.pConn$.getContextName();
attachUtils
// @ts-ignore - 3rd parameter "responseEncoding" is optional
.downloadAttachment(ID, context)
.then((content: any) => {
if (type === 'FILE') {
this.fileDownload(content.data, name, extension);
} else if (type === 'URL') {
let { data } = content;
if (!/^(http|https):\/\//.test(data)) {
data = `//${data}`;
}
window.open(content.data, '_blank');
}
})
.catch(console.error);
}
fileDownload = (data, fileName, ext) => {
const file = ext ? `${fileName}.${ext}` : fileName;
download(atob(data), file);
};
cancelFile() {
alert('cancel');
}
deleteFile(att: any) {
setTimeout(() => {
const attachUtils = PCore.getAttachmentUtils();
const { ID } = att;
const context = this.pConn$.getContextName();
attachUtils
.deleteAttachment(ID, context)
.then(() => {
this.updateSelf();
// let newAttachments;
// setAttachments((current) => {
// newAttachments = current.filter((file) => file.ID !== ID);
// return newAttachments;
// });
// if (callbackFn) {
// callbackFn(newAttachments);
// }
})
.catch(console.error);
});
}
removeFile() {
alert('remove');
}
removeNewFile() {
alert('remove');
}
createModal(modalType: string) {
switch (modalType) {
case 'addLocalFile':
this.ngZone.run(() => {
this.bShowFileModal$ = true;
});
break;
case 'addLocalLink':
this.ngZone.run(() => {
this.bShowLinkModal$ = true;
});
break;
default:
break;
}
}
createModalButtons() {
this.arFileMainButtons$.push({ actionID: 'attach', jsAction: 'attachFiles', name: this.pConn$.getLocalizedValue('Attach files', '', '') });
this.arFileSecondaryButtons$.push({ actionID: 'cancel', jsAction: 'cancel', name: this.pConn$.getLocalizedValue('Cancel', '', '') });
this.arLinkMainButtons$.push({ actionID: 'attach', jsAction: 'attachLinks', name: this.pConn$.getLocalizedValue('Attach links', '', '') });
this.arLinkSecondaryButtons$.push({ actionID: 'cancel', jsAction: 'cancel', name: this.pConn$.getLocalizedValue('Cancel', '', '') });
}
uploadMyFiles($event) {
// alert($event.target.files[0]); // outputs the first file
this.arFiles$ = this.getFiles($event.target.files);
// convert FileList to an array
const myFiles = Array.from(this.arFiles$);
this.arFileList$ = myFiles.map(att => {
return this.getNewListUtilityItemProps({
att,
downloadFile: !att.progress ? () => this.downloadFile(att) : null,
cancelFile: att.progress ? () => this.cancelFile() : null,
deleteFile: !att.progress ? () => this.deleteFile(att) : null,
removeFile: att.error ? () => this.removeNewFile() : null
});
});
}
getFiles(arFiles: any[]): any[] {
return this.setNewFiles(arFiles);
}
setNewFiles(arFiles) {
let index = 0;
for (const file of arFiles) {
if (!this.validateMaxSize(file, 5)) {
file.error = true;
file.meta = 'File is too big. Max allowed size is 5MB.';
}
file.mimeType = file.type;
file.icon = this.utils.getIconFromFileType(file.type);
file.ID = `${new Date().getTime()}I${index}`;
index++;
}
return arFiles;
}
validateMaxSize(fileObj, maxSizeInMB): boolean {
const fileSize = (fileObj.size / 1048576).toFixed(2);
return fileSize < maxSizeInMB;
}
onFileActionButtonClick(event: any) {
// modal buttons
switch (event.action) {
case 'cancel':
this.bShowFileModal$ = false;
this.clearOutFiles();
break;
case 'attachFiles':
this.bShowFileModal$ = false;
this.onAttachFiles(this.arFiles$);
this.clearOutFiles();
break;
default:
break;
}
}
onLinkActionButtonClick(event: any) {
// modal buttons
switch (event.action) {
case 'cancel':
this.bShowLinkModal$ = false;
this.clearOutLinks();
break;
case 'attachLinks':
this.bShowLinkModal$ = false;
this.onAttachLinks(this.arLinks$);
this.clearOutLinks();
break;
default:
break;
}
}
clearOutFiles() {
this.arFileList$ = [];
this.arFiles$ = [];
}
clearOutLinks() {
this.arLinksList$ = [];
this.arLinks$ = [];
this.link_title$ = '';
this.link_url$ = '';
}
addALink() {}
_fieldOnChangeLink(event: any) {
this.link_title$ = event.target.value;
}
_fieldOnChangeURL(event: any) {
this.link_url$ = event.target.value;
}
updateSelf() {
const caseID = this.getCaseID();
if (caseID) {
this.lu_bLoading$ = true;
this.fetchCaseAttachments(caseID)
.then(resp => {
this.handleAttachmentsResponse(resp);
})
.catch(err => {
console.log(err);
})
.finally(() => {
this.lu_bLoading$ = false;
});
}
}
getCaseID() {
return this.pConn$.getValue(PCore.getConstants().CASE_INFO.CASE_INFO_ID) || '';
}
fetchCaseAttachments(caseID) {
const attachmentUtils = PCore.getAttachmentUtils();
return attachmentUtils.getCaseAttachments(caseID, this.pConn$.getContextName());
}
handleAttachmentsResponse(resp) {
this.arFullListAttachments = this.addAttachments(resp);
this.lu_count$ = this.arFullListAttachments.length;
this.lu_arActions$ = this.addAttachmentsActions;
this.lu_arItems$ = this.getUtilityItems(this.arFullListAttachments.slice(0, 3));
this.va_arItems$ = this.getUtilityItems(this.arFullListAttachments);
}
getUtilityItems(attachments) {
return attachments.map(this.mapAttachmentToUtilityItem.bind(this));
}
mapAttachmentToUtilityItem(att) {
return this.getListUtilityItemProps({
att,
downloadFile: this.getDownloadFunction(att),
cancelFile: this.getCancelFunction(att),
deleteFile: this.getDeleteFunction(att),
removeFile: this.getRemoveFunction(att)
});
}
getDownloadFunction(att) {
return !att.progress ? () => this.downloadFile(att) : null;
}
getCancelFunction(att) {
return att.progress ? () => this.cancelFile() : null;
}
getDeleteFunction(att) {
return !att.progress ? () => this.deleteFile(att) : null;
}
getRemoveFunction(att) {
return att.error ? () => this.removeFile() : null;
}
caseHasChanged(): boolean {
const caseID = this.pConn$.getValue(PCore.getConstants().CASE_INFO.CASE_INFO_ID);
if (this.currentCaseID !== caseID) {
this.currentCaseID = caseID;
return true;
}
return false;
}
}