@pega/dx-component-builder-sdk 0.23.13 → 0.23.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/package.json +1 -1
  2. package/src/constants.js +12 -0
  3. package/src/tasks/create/templates/SDK-React/field/Boolean/index.tsx.mustache +23 -21
  4. package/src/tasks/create/templates/SDK-React/field/Date/index.tsx.mustache +24 -26
  5. package/src/tasks/create/templates/SDK-React/field/DateTime/index.tsx.mustache +20 -22
  6. package/src/tasks/create/templates/SDK-React/field/Decimal/index.tsx.mustache +3 -4
  7. package/src/tasks/create/templates/SDK-React/field/Decimal-Currency/index.tsx.mustache +3 -4
  8. package/src/tasks/create/templates/SDK-React/field/Decimal-Percentage/index.tsx.mustache +3 -4
  9. package/src/tasks/create/templates/SDK-React/field/Integer/index.tsx.mustache +3 -4
  10. package/src/tasks/create/templates/SDK-React/field/Picklist/index.tsx.mustache +3 -4
  11. package/src/tasks/create/templates/SDK-React/field/Picklist-Autocomplete/index.tsx.mustache +31 -32
  12. package/src/tasks/create/templates/SDK-React/field/Picklist-RadioButtons/index.tsx.mustache +22 -23
  13. package/src/tasks/create/templates/SDK-React/field/Text/index.tsx.mustache +18 -19
  14. package/src/tasks/create/templates/SDK-React/field/Text-Email/index.tsx.mustache +26 -27
  15. package/src/tasks/create/templates/SDK-React/field/Text-Input/index.tsx.mustache +18 -19
  16. package/src/tasks/create/templates/SDK-React/field/Text-Paragraph/index.tsx.mustache +21 -22
  17. package/src/tasks/create/templates/SDK-React/field/Text-Phone/index.tsx.mustache +39 -40
  18. package/src/tasks/create/templates/SDK-React/field/Text-URL/index.tsx.mustache +19 -20
  19. package/src/tasks/create/templates/SDK-React/field/TimeOfDay/index.tsx.mustache +4 -5
  20. package/src/tasks/create/templates/SDK-React/template/DETAILS/index.tsx.mustache +19 -20
  21. package/src/tasks/create/templates/SDK-React/template/DETAILS-region/index.tsx.mustache +19 -20
  22. package/src/tasks/create/templates/SDK-React/template/FORM/index.tsx.mustache +6 -7
  23. package/src/tasks/create/templates/SDK-React/template/FORM-region/index.tsx.mustache +8 -9
  24. package/src/tasks/create/templates/SDK-React/template/PAGE/index.tsx.mustache +4 -5
  25. package/src/tasks/create/templates/SDK-React/widget/CASE/index.tsx.mustache +10 -11
  26. package/src/tasks/create/templates/SDK-React/widget/PAGE/index.tsx.mustache +23 -24
  27. package/src/tasks/create/templates/SDK-React/widget/PAGE-CASE/index.tsx.mustache +37 -37
  28. package/src/tasks/validator/index.js +52 -47
  29. package/tests/assets/sdk-a/components/create/override-sdk/designSystemExtension/rich-text-editor/rich-text-editor.component.html +33 -0
  30. package/tests/assets/sdk-a/components/create/override-sdk/designSystemExtension/rich-text-editor/rich-text-editor.component.scss +19 -0
  31. package/tests/assets/sdk-a/components/create/override-sdk/designSystemExtension/rich-text-editor/rich-text-editor.component.spec.ts +24 -0
  32. package/tests/assets/sdk-a/components/create/override-sdk/designSystemExtension/rich-text-editor/rich-text-editor.component.ts +89 -0
  33. package/tests/assets/sdk-a/components/create/override-sdk/field/rich-text/rich-text.component.html +20 -0
  34. package/tests/assets/sdk-a/components/create/override-sdk/field/rich-text/rich-text.component.scss +0 -0
  35. package/tests/assets/sdk-a/components/create/override-sdk/field/rich-text/rich-text.component.spec.ts +24 -0
  36. package/tests/assets/sdk-a/components/create/override-sdk/field/rich-text/rich-text.component.ts +131 -0
  37. package/tests/assets/sdk-a/components/create/override-sdk/widget/file-utility/file-utility.component.ts +21 -15
  38. package/tests/assets/sdk-a/components/createAll/override-sdk/designSystemExtension/rich-text-editor/rich-text-editor.component.html +33 -0
  39. package/tests/assets/sdk-a/components/createAll/override-sdk/designSystemExtension/rich-text-editor/rich-text-editor.component.scss +19 -0
  40. package/tests/assets/sdk-a/components/createAll/override-sdk/designSystemExtension/rich-text-editor/rich-text-editor.component.spec.ts +24 -0
  41. package/tests/assets/sdk-a/components/createAll/override-sdk/designSystemExtension/rich-text-editor/rich-text-editor.component.ts +89 -0
  42. package/tests/assets/sdk-a/components/createAll/override-sdk/field/rich-text/rich-text.component.html +20 -0
  43. package/tests/assets/sdk-a/components/createAll/override-sdk/field/rich-text/rich-text.component.scss +0 -0
  44. package/tests/assets/sdk-a/components/createAll/override-sdk/field/rich-text/rich-text.component.spec.ts +24 -0
  45. package/tests/assets/sdk-a/components/createAll/override-sdk/field/rich-text/rich-text.component.ts +131 -0
  46. package/tests/assets/sdk-a/components/createAll/override-sdk/widget/file-utility/file-utility.component.ts +21 -15
@@ -149,24 +149,30 @@ export class FileUtilityComponent implements OnInit, OnDestroy {
149
149
  this.lu_bLoading$ = true;
150
150
  }
151
151
 
152
- // const uploadedFiles = [];
153
-
154
- for (const file of files) {
155
- attachmentUtils
156
- .uploadAttachment(file, this.onUploadProgress, this.errorHandler, this.pConn$.getContextName())
157
- .then((fileResponse: any) => {
158
- if (fileResponse.type === 'File') {
159
- (attachmentUtils.linkAttachmentsToCase(caseID, [fileResponse], 'File', this.pConn$.getContextName()) as Promise<any>)
160
- .then(() => {
161
- this.refreshAttachments();
162
- })
163
- .catch(console.error);
152
+ const arFiles: any = Array.from(files);
153
+
154
+ Promise.allSettled(
155
+ arFiles.map(file => attachmentUtils.uploadAttachment(file, this.onUploadProgress, this.errorHandler, this.pConn$.getContextName()))
156
+ )
157
+ .then((fileResponses: any) => {
158
+ const uploadedFiles: any = [];
159
+ fileResponses.forEach(fileResponse => {
160
+ if (fileResponse.status === 'fulfilled') {
161
+ uploadedFiles.push(fileResponse.value);
164
162
  }
165
- })
166
- .catch(console.error);
167
- }
163
+ });
164
+ if (uploadedFiles.length > 0) {
165
+ (attachmentUtils.linkAttachmentsToCase(caseID, uploadedFiles, 'File', this.pConn$.getContextName()) as Promise<any>)
166
+ .then(() => {
167
+ this.refreshAttachments();
168
+ })
169
+ .catch();
170
+ }
171
+ })
172
+ .catch();
168
173
 
169
174
  this.arFileList$ = [];
175
+ this.lu_bLoading$ = false;
170
176
  }
171
177
 
172
178
  refreshAttachments() {
@@ -0,0 +1,33 @@
1
+ <div [attr.data-test-id]="testId">
2
+ <label [ngClass]="{ 'label-required': required === true }" class="rich-text-label">{{ label }}</label>
3
+ <div class="rich-text-editor" *ngIf="!readonly">
4
+ <editor
5
+ [formControl]="richText"
6
+ [attr.disabled]="disabled"
7
+ [initialValue]="value"
8
+ [init]="{
9
+ base_url: '/tinymce',
10
+ suffix: '.min',
11
+ menubar: false,
12
+ placeholder,
13
+ statusbar: false,
14
+ min_height: 130,
15
+ plugins: ['lists', 'advlist', 'autolink', 'image', 'link', 'autoresize'],
16
+ autoresize_bottom_margin: 0,
17
+ toolbar: disabled ? false : 'blocks | bold italic strikethrough | bullist numlist outdent indent | link image',
18
+ toolbar_location: 'bottom',
19
+ content_style: 'body { font-family:Helvetica, Arial,sans-serif; font-size:14px }',
20
+ branding: false,
21
+ paste_data_images: true,
22
+ file_picker_types: 'image',
23
+ file_picker_callback: filePickerCallback
24
+ }"
25
+ (onBlur)="blur()"
26
+ (onChange)="change($event)"
27
+ ></editor>
28
+ <p *ngIf="richText.invalid" [ngClass]="'text-editor-error'">{{ info }}</p>
29
+ </div>
30
+ <div *ngIf="readonly">
31
+ <div class="readonly-richtext-editor" style="margin: 10px 5px" [innerHTML]="value || '--'"></div>
32
+ </div>
33
+ </div>
@@ -0,0 +1,19 @@
1
+ .rich-text-label {
2
+ margin: 5px;
3
+ font-size: 16px;
4
+ }
5
+
6
+ .rich-text-editor {
7
+ margin: 10px 0px;
8
+ }
9
+
10
+ .label-required::after {
11
+ display: inline;
12
+ content: ' *';
13
+ vertical-align: top;
14
+ color: var(--app-neutral-dark-color);
15
+ }
16
+
17
+ .text-editor-error {
18
+ color: var(--app-error-light-color);
19
+ }
@@ -0,0 +1,24 @@
1
+ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2
+
3
+ import { RichTextEditorComponent } from './rich-text-editor.component';
4
+
5
+ describe('RichTextComponent', () => {
6
+ let component: RichTextEditorComponent;
7
+ let fixture: ComponentFixture<RichTextEditorComponent>;
8
+
9
+ beforeEach(waitForAsync(() => {
10
+ TestBed.configureTestingModule({
11
+ declarations: [RichTextEditorComponent]
12
+ }).compileComponents();
13
+ }));
14
+
15
+ beforeEach(() => {
16
+ fixture = TestBed.createComponent(RichTextEditorComponent);
17
+ component = fixture.componentInstance;
18
+ fixture.detectChanges();
19
+ });
20
+
21
+ it('should create', () => {
22
+ expect(component).toBeTruthy();
23
+ });
24
+ });
@@ -0,0 +1,89 @@
1
+ import { Component, Input, Output, EventEmitter, OnChanges } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
4
+ import { EditorModule, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular';
5
+
6
+ declare let tinymce: any;
7
+
8
+ @Component({
9
+ selector: 'app-rich-text-editor',
10
+ templateUrl: './rich-text-editor.component.html',
11
+ styleUrls: ['./rich-text-editor.component.scss'],
12
+ standalone: true,
13
+ imports: [CommonModule, EditorModule, ReactiveFormsModule],
14
+ providers: [{ provide: TINYMCE_SCRIPT_SRC, useValue: 'tinymce/tinymce.min.js' }]
15
+ })
16
+ export class RichTextEditorComponent implements OnChanges {
17
+ @Input() placeholder;
18
+ @Input() disabled;
19
+ @Input() readonly;
20
+ @Input() value;
21
+ @Input() label;
22
+ @Input() required;
23
+ @Input() info;
24
+ @Input() error;
25
+ @Input() testId;
26
+
27
+ @Output() onBlur: EventEmitter<any> = new EventEmitter();
28
+ @Output() onChange: EventEmitter<any> = new EventEmitter();
29
+
30
+ richText = new FormControl();
31
+
32
+ ngOnChanges() {
33
+ if (this.required) {
34
+ this.richText.addValidators(Validators.required);
35
+ }
36
+
37
+ if (this.disabled) {
38
+ this.richText.disable();
39
+ } else {
40
+ this.richText.enable();
41
+ }
42
+
43
+ if (this.value) {
44
+ this.richText.setValue(this.value);
45
+ }
46
+ }
47
+
48
+ filePickerCallback = cb => {
49
+ const input = document.createElement('input');
50
+ input.setAttribute('type', 'file');
51
+ input.setAttribute('accept', 'image/*');
52
+
53
+ input.addEventListener('change', (e: any) => {
54
+ const file = e.target.files[0];
55
+
56
+ const reader: any = new FileReader();
57
+ reader.addEventListener('load', () => {
58
+ /*
59
+ Note: Now we need to register the blob in TinyMCEs image blob
60
+ registry. In the next release this part hopefully won't be
61
+ necessary, as we are looking to handle it internally.
62
+ */
63
+ const blobId = `blobid${new Date().getTime()}`;
64
+ console.log('editorRef', tinymce.activeEditor);
65
+ const blobCache = tinymce.activeEditor.editorUpload.blobCache;
66
+ const base64 = reader.result.split(',')[1];
67
+ const blobInfo = blobCache.create(blobId, file, base64);
68
+ blobCache.add(blobInfo);
69
+
70
+ /* call the callback and populate the Title field with the file name */
71
+ cb(blobInfo.blobUri(), { title: file.name });
72
+ });
73
+ reader.readAsDataURL(file);
74
+ });
75
+
76
+ input.click();
77
+ };
78
+
79
+ blur() {
80
+ if (tinymce.activeEditor) {
81
+ const editorValue = tinymce.activeEditor.getContent({ format: 'html' });
82
+ this.onBlur.emit(editorValue);
83
+ }
84
+ }
85
+
86
+ change(event) {
87
+ this.onChange.emit(event);
88
+ }
89
+ }
@@ -0,0 +1,20 @@
1
+ <div *ngIf="displayMode$; else noDisplayMode">
2
+ <component-mapper *ngIf="bVisible$ !== false" name="FieldValueList" [props]="{ label$, value$, displayMode$ }"></component-mapper>
3
+ </div>
4
+ <ng-template #noDisplayMode>
5
+ <div *ngIf="!bReadonly$; else noEdit">
6
+ <div *ngIf="bVisible$">
7
+ <component-mapper
8
+ name="RichTextEditor"
9
+ [props]="{ placeholder, required: bRequired$, disabled: bDisabled$, label: label$, readonly: false, error, info, testId, value: value$ }"
10
+ [parent]="this"
11
+ [outputEvents]="{ onBlur: fieldOnBlur, onChange: fieldOnChange }"
12
+ ></component-mapper>
13
+ </div>
14
+ </div>
15
+ </ng-template>
16
+ <ng-template #noEdit>
17
+ <div *ngIf="bVisible$ !== false">
18
+ <component-mapper name="RichTextEditor" [props]="{ label: label$, value: value$, readonly: true, testId }" [parent]="this"></component-mapper>
19
+ </div>
20
+ </ng-template>
@@ -0,0 +1,24 @@
1
+ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2
+
3
+ import { RichTextComponent } from './rich-text.component';
4
+
5
+ describe('RichTextComponent', () => {
6
+ let component: RichTextComponent;
7
+ let fixture: ComponentFixture<RichTextComponent>;
8
+
9
+ beforeEach(waitForAsync(() => {
10
+ TestBed.configureTestingModule({
11
+ declarations: [RichTextComponent]
12
+ }).compileComponents();
13
+ }));
14
+
15
+ beforeEach(() => {
16
+ fixture = TestBed.createComponent(RichTextComponent);
17
+ component = fixture.componentInstance;
18
+ fixture.detectChanges();
19
+ });
20
+
21
+ it('should create', () => {
22
+ expect(component).toBeTruthy();
23
+ });
24
+ });
@@ -0,0 +1,131 @@
1
+ import { Component, OnInit, Input, forwardRef, OnDestroy } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormGroup, ReactiveFormsModule } from '@angular/forms';
4
+ import { AngularPConnectData, AngularPConnectService } from '@pega/angular-sdk-components';
5
+ import { Utils } from '@pega/angular-sdk-components';
6
+ import { ComponentMapperComponent } from '@pega/angular-sdk-components';
7
+ import { handleEvent } from '@pega/angular-sdk-components';
8
+ import { PConnFieldProps } from '@pega/angular-sdk-components';
9
+
10
+ interface RichTextProps extends PConnFieldProps {
11
+ // If any, enter additional props that only exist on RichText here
12
+ }
13
+
14
+ @Component({
15
+ selector: 'app-rich-text',
16
+ templateUrl: './rich-text.component.html',
17
+ styleUrls: ['./rich-text.component.scss'],
18
+ standalone: true,
19
+ imports: [CommonModule, ReactiveFormsModule, forwardRef(() => ComponentMapperComponent)]
20
+ })
21
+ export class RichTextComponent implements OnInit, OnDestroy {
22
+ @Input() pConn$: typeof PConnect;
23
+ @Input() formGroup$: FormGroup;
24
+
25
+ // Used with AngularPConnect
26
+ angularPConnectData: AngularPConnectData = {};
27
+ configProps$: RichTextProps;
28
+
29
+ label$ = '';
30
+ value$ = '';
31
+ bRequired$ = false;
32
+ bReadonly$ = false;
33
+ bDisabled$ = false;
34
+ bVisible$ = true;
35
+ displayMode$?: string = '';
36
+ controlName$: string;
37
+ testId: string;
38
+ helperText: string;
39
+ placeholder: any;
40
+ info: any;
41
+ error: boolean;
42
+ status: any;
43
+
44
+ constructor(
45
+ private angularPConnect: AngularPConnectService,
46
+ private utils: Utils
47
+ ) {}
48
+
49
+ ngOnInit(): void {
50
+ // First thing in initialization is registering and subscribing to the AngularPConnect service
51
+ this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange);
52
+
53
+ // call updateSelf when initializing
54
+ this.checkAndUpdate();
55
+ }
56
+
57
+ ngOnDestroy(): void {
58
+ if (this.angularPConnectData.unsubscribeFn) {
59
+ this.angularPConnectData.unsubscribeFn();
60
+ }
61
+ }
62
+
63
+ // Callback passed when subscribing to store change
64
+ onStateChange() {
65
+ this.checkAndUpdate();
66
+ }
67
+
68
+ checkAndUpdate() {
69
+ // Should always check the bridge to see if the component should
70
+ // update itself (re-render)
71
+ const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this);
72
+
73
+ // ONLY call updateSelf when the component should update
74
+ if (bUpdateSelf) {
75
+ this.updateSelf();
76
+ }
77
+ }
78
+
79
+ // updateSelf
80
+ updateSelf(): void {
81
+ // moved this from ngOnInit() and call this from there instead...
82
+ this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as RichTextProps;
83
+ const stateProps: any = this.pConn$.getStateProps();
84
+ this.status = stateProps?.status;
85
+
86
+ if (this.configProps$.value != undefined) {
87
+ this.value$ = this.configProps$.value;
88
+ }
89
+
90
+ this.testId = this.configProps$.testId;
91
+ this.displayMode$ = this.configProps$.displayMode;
92
+ this.label$ = this.configProps$.label;
93
+ this.placeholder = this.configProps$.placeholder;
94
+ this.info = stateProps?.validatemessage || this.configProps$.helperText;
95
+ this.error = stateProps?.status === 'error';
96
+
97
+ if (this.configProps$.required != null) {
98
+ this.bRequired$ = this.utils.getBooleanValue(this.configProps$.required);
99
+ }
100
+
101
+ if (this.configProps$.visibility != null) {
102
+ this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility);
103
+ }
104
+
105
+ if (this.configProps$.disabled != undefined) {
106
+ this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled);
107
+ }
108
+
109
+ if (this.configProps$.readOnly != null) {
110
+ this.bReadonly$ = this.utils.getBooleanValue(this.configProps$.readOnly);
111
+ }
112
+ }
113
+
114
+ fieldOnChange() {
115
+ if (this.status === 'error') {
116
+ const property = (this.pConn$.getStateProps() as any).value;
117
+ this.pConn$.clearErrorMessages({
118
+ property,
119
+ category: '',
120
+ context: ''
121
+ });
122
+ }
123
+ }
124
+
125
+ fieldOnBlur(editorValue: any) {
126
+ // PConnect wants to use eventHandler for onBlur
127
+ const actionsApi = this.pConn$?.getActionsApi();
128
+ const propName = (this.pConn$?.getStateProps() as any).value;
129
+ handleEvent(actionsApi, 'changeNblur', propName, editorValue);
130
+ }
131
+ }
@@ -149,24 +149,30 @@ export class FileUtilityComponent implements OnInit, OnDestroy {
149
149
  this.lu_bLoading$ = true;
150
150
  }
151
151
 
152
- // const uploadedFiles = [];
153
-
154
- for (const file of files) {
155
- attachmentUtils
156
- .uploadAttachment(file, this.onUploadProgress, this.errorHandler, this.pConn$.getContextName())
157
- .then((fileResponse: any) => {
158
- if (fileResponse.type === 'File') {
159
- (attachmentUtils.linkAttachmentsToCase(caseID, [fileResponse], 'File', this.pConn$.getContextName()) as Promise<any>)
160
- .then(() => {
161
- this.refreshAttachments();
162
- })
163
- .catch(console.error);
152
+ const arFiles: any = Array.from(files);
153
+
154
+ Promise.allSettled(
155
+ arFiles.map(file => attachmentUtils.uploadAttachment(file, this.onUploadProgress, this.errorHandler, this.pConn$.getContextName()))
156
+ )
157
+ .then((fileResponses: any) => {
158
+ const uploadedFiles: any = [];
159
+ fileResponses.forEach(fileResponse => {
160
+ if (fileResponse.status === 'fulfilled') {
161
+ uploadedFiles.push(fileResponse.value);
164
162
  }
165
- })
166
- .catch(console.error);
167
- }
163
+ });
164
+ if (uploadedFiles.length > 0) {
165
+ (attachmentUtils.linkAttachmentsToCase(caseID, uploadedFiles, 'File', this.pConn$.getContextName()) as Promise<any>)
166
+ .then(() => {
167
+ this.refreshAttachments();
168
+ })
169
+ .catch();
170
+ }
171
+ })
172
+ .catch();
168
173
 
169
174
  this.arFileList$ = [];
175
+ this.lu_bLoading$ = false;
170
176
  }
171
177
 
172
178
  refreshAttachments() {