@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.
- package/package.json +1 -1
- package/src/constants.js +12 -0
- package/src/tasks/create/templates/SDK-React/field/Boolean/index.tsx.mustache +23 -21
- package/src/tasks/create/templates/SDK-React/field/Date/index.tsx.mustache +24 -26
- package/src/tasks/create/templates/SDK-React/field/DateTime/index.tsx.mustache +20 -22
- package/src/tasks/create/templates/SDK-React/field/Decimal/index.tsx.mustache +3 -4
- package/src/tasks/create/templates/SDK-React/field/Decimal-Currency/index.tsx.mustache +3 -4
- package/src/tasks/create/templates/SDK-React/field/Decimal-Percentage/index.tsx.mustache +3 -4
- package/src/tasks/create/templates/SDK-React/field/Integer/index.tsx.mustache +3 -4
- package/src/tasks/create/templates/SDK-React/field/Picklist/index.tsx.mustache +3 -4
- package/src/tasks/create/templates/SDK-React/field/Picklist-Autocomplete/index.tsx.mustache +31 -32
- package/src/tasks/create/templates/SDK-React/field/Picklist-RadioButtons/index.tsx.mustache +22 -23
- package/src/tasks/create/templates/SDK-React/field/Text/index.tsx.mustache +18 -19
- package/src/tasks/create/templates/SDK-React/field/Text-Email/index.tsx.mustache +26 -27
- package/src/tasks/create/templates/SDK-React/field/Text-Input/index.tsx.mustache +18 -19
- package/src/tasks/create/templates/SDK-React/field/Text-Paragraph/index.tsx.mustache +21 -22
- package/src/tasks/create/templates/SDK-React/field/Text-Phone/index.tsx.mustache +39 -40
- package/src/tasks/create/templates/SDK-React/field/Text-URL/index.tsx.mustache +19 -20
- package/src/tasks/create/templates/SDK-React/field/TimeOfDay/index.tsx.mustache +4 -5
- package/src/tasks/create/templates/SDK-React/template/DETAILS/index.tsx.mustache +19 -20
- package/src/tasks/create/templates/SDK-React/template/DETAILS-region/index.tsx.mustache +19 -20
- package/src/tasks/create/templates/SDK-React/template/FORM/index.tsx.mustache +6 -7
- package/src/tasks/create/templates/SDK-React/template/FORM-region/index.tsx.mustache +8 -9
- package/src/tasks/create/templates/SDK-React/template/PAGE/index.tsx.mustache +4 -5
- package/src/tasks/create/templates/SDK-React/widget/CASE/index.tsx.mustache +10 -11
- package/src/tasks/create/templates/SDK-React/widget/PAGE/index.tsx.mustache +23 -24
- package/src/tasks/create/templates/SDK-React/widget/PAGE-CASE/index.tsx.mustache +37 -37
- package/src/tasks/validator/index.js +52 -47
- package/tests/assets/sdk-a/components/create/override-sdk/designSystemExtension/rich-text-editor/rich-text-editor.component.html +33 -0
- package/tests/assets/sdk-a/components/create/override-sdk/designSystemExtension/rich-text-editor/rich-text-editor.component.scss +19 -0
- package/tests/assets/sdk-a/components/create/override-sdk/designSystemExtension/rich-text-editor/rich-text-editor.component.spec.ts +24 -0
- package/tests/assets/sdk-a/components/create/override-sdk/designSystemExtension/rich-text-editor/rich-text-editor.component.ts +89 -0
- package/tests/assets/sdk-a/components/create/override-sdk/field/rich-text/rich-text.component.html +20 -0
- package/tests/assets/sdk-a/components/create/override-sdk/field/rich-text/rich-text.component.scss +0 -0
- package/tests/assets/sdk-a/components/create/override-sdk/field/rich-text/rich-text.component.spec.ts +24 -0
- package/tests/assets/sdk-a/components/create/override-sdk/field/rich-text/rich-text.component.ts +131 -0
- package/tests/assets/sdk-a/components/create/override-sdk/widget/file-utility/file-utility.component.ts +21 -15
- package/tests/assets/sdk-a/components/createAll/override-sdk/designSystemExtension/rich-text-editor/rich-text-editor.component.html +33 -0
- package/tests/assets/sdk-a/components/createAll/override-sdk/designSystemExtension/rich-text-editor/rich-text-editor.component.scss +19 -0
- package/tests/assets/sdk-a/components/createAll/override-sdk/designSystemExtension/rich-text-editor/rich-text-editor.component.spec.ts +24 -0
- package/tests/assets/sdk-a/components/createAll/override-sdk/designSystemExtension/rich-text-editor/rich-text-editor.component.ts +89 -0
- package/tests/assets/sdk-a/components/createAll/override-sdk/field/rich-text/rich-text.component.html +20 -0
- package/tests/assets/sdk-a/components/createAll/override-sdk/field/rich-text/rich-text.component.scss +0 -0
- package/tests/assets/sdk-a/components/createAll/override-sdk/field/rich-text/rich-text.component.spec.ts +24 -0
- package/tests/assets/sdk-a/components/createAll/override-sdk/field/rich-text/rich-text.component.ts +131 -0
- package/tests/assets/sdk-a/components/createAll/override-sdk/widget/file-utility/file-utility.component.ts +21 -15
|
@@ -13,13 +13,13 @@ import { PConnProps } from '@pega/react-sdk-components/lib/types/PConnProps';
|
|
|
13
13
|
|
|
14
14
|
interface {{COMPONENT_CLASS_NAME}}Props extends PConnProps {
|
|
15
15
|
// If any, enter additional props that only exist on this component
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
label: string;
|
|
17
|
+
createDateTime: string;
|
|
18
|
+
createLabel: string;
|
|
19
|
+
createOperator: { userName: string; userId: string };
|
|
20
|
+
updateDateTime: string;
|
|
21
|
+
updateLabel: string;
|
|
22
|
+
updateOperator: { userName: string; userId: string };
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const useStyles = makeStyles(theme => ({
|
|
@@ -38,7 +38,6 @@ const useStyles = makeStyles(theme => ({
|
|
|
38
38
|
// Page Case Widget example
|
|
39
39
|
|
|
40
40
|
// props passed in combination of props from property panel (config.json) and run time props from Constellation
|
|
41
|
-
// any default values in config.pros should be set in defaultProps at bottom of this file
|
|
42
41
|
export default function {{COMPONENT_CLASS_NAME}}(props: {{COMPONENT_CLASS_NAME}}Props) {
|
|
43
42
|
// const componentName = "Operator";
|
|
44
43
|
const classes = useStyles();
|
|
@@ -192,37 +191,38 @@ export default function {{COMPONENT_CLASS_NAME}}(props: {{COMPONENT_CLASS_NAME}}
|
|
|
192
191
|
// End of popover-related
|
|
193
192
|
|
|
194
193
|
return (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
{{=<% %>=}}
|
|
198
|
-
<TextField
|
|
199
|
-
defaultValue={caseOpName}
|
|
200
|
-
label={caseOpLabel}
|
|
201
|
-
onClick={showOperatorDetails}
|
|
202
|
-
InputProps={{
|
|
203
|
-
readOnly: true,
|
|
204
|
-
disableUnderline: true,
|
|
205
|
-
inputProps: { style: { cursor: 'pointer' } }
|
|
206
|
-
}}
|
|
207
|
-
/>
|
|
208
|
-
<br />
|
|
209
|
-
{Utils.generateDateTime(caseTime, 'DateTime-Since')}
|
|
210
|
-
<%={{ }}=%> {{! revert delimiters for mustache }}
|
|
211
|
-
<Popover
|
|
212
|
-
id={popoverId}
|
|
213
|
-
open={popoverOpen}
|
|
214
|
-
anchorEl={popoverAnchorEl}
|
|
215
|
-
onClose={handlePopoverClose}
|
|
194
|
+
<Styled{{COMPONENT_CLASS_NAME}}Wrapper>
|
|
195
|
+
<>
|
|
216
196
|
{{! pick new delimiters for mustache }}
|
|
217
197
|
{{=<% %>=}}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
198
|
+
<TextField
|
|
199
|
+
defaultValue={caseOpName}
|
|
200
|
+
label={caseOpLabel}
|
|
201
|
+
onClick={showOperatorDetails}
|
|
202
|
+
InputProps={{
|
|
203
|
+
readOnly: true,
|
|
204
|
+
disableUnderline: true,
|
|
205
|
+
inputProps: { style: { cursor: 'pointer' } }
|
|
206
|
+
}}
|
|
207
|
+
/>
|
|
208
|
+
<br />
|
|
209
|
+
{Utils.generateDateTime(caseTime, 'DateTime-Since')}
|
|
221
210
|
<%={{ }}=%> {{! revert delimiters for mustache }}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
211
|
+
<Popover
|
|
212
|
+
id={popoverId}
|
|
213
|
+
open={popoverOpen}
|
|
214
|
+
anchorEl={popoverAnchorEl}
|
|
215
|
+
onClose={handlePopoverClose}
|
|
216
|
+
{{! pick new delimiters for mustache }}
|
|
217
|
+
{{=<% %>=}}
|
|
218
|
+
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
|
219
|
+
transformOrigin={{ vertical: 'top', horizontal: 'center' }}
|
|
220
|
+
PaperProps={{ style: { maxWidth: '45ch' } }}
|
|
221
|
+
<%={{ }}=%> {{! revert delimiters for mustache }}
|
|
222
|
+
>
|
|
223
|
+
{getPopoverGrid()}
|
|
224
|
+
</Popover>
|
|
225
|
+
</>
|
|
226
226
|
</Styled{{COMPONENT_CLASS_NAME}}Wrapper>
|
|
227
227
|
);
|
|
228
|
-
}
|
|
228
|
+
}
|
|
@@ -5,22 +5,19 @@ import chalk from 'chalk';
|
|
|
5
5
|
import inquirer from 'inquirer';
|
|
6
6
|
import Ajv from 'ajv';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
COMPONENT_SCHEMA,
|
|
10
|
-
CATEGORY_CUSTOM,
|
|
11
|
-
CATEGORY_OVERRIDE } from '../../constants.js';
|
|
12
|
-
|
|
8
|
+
import { COMPONENT_SCHEMA, CATEGORY_CUSTOM, CATEGORY_OVERRIDE } from '../../constants.js';
|
|
13
9
|
import {
|
|
14
10
|
getComponentDirectoryPath,
|
|
15
11
|
getComponents,
|
|
16
12
|
getOOTBComponents,
|
|
17
13
|
removeComponentPrefix,
|
|
18
|
-
getFramework,
|
|
19
|
-
getLocalComponentKeyFromConfig
|
|
14
|
+
getFramework,
|
|
15
|
+
getLocalComponentKeyFromConfig
|
|
16
|
+
} from '../../util.js';
|
|
20
17
|
|
|
21
18
|
const ajv = new Ajv({ allErrors: true });
|
|
22
19
|
|
|
23
|
-
const combinationTypes = ['PAGE', 'CASE'];
|
|
20
|
+
// const combinationTypes = ['PAGE', 'CASE'];
|
|
24
21
|
|
|
25
22
|
const allowedSubtypes = [
|
|
26
23
|
...new Set([
|
|
@@ -30,13 +27,25 @@ const allowedSubtypes = [
|
|
|
30
27
|
])
|
|
31
28
|
];
|
|
32
29
|
|
|
30
|
+
const allowedOverrideTypes = [
|
|
31
|
+
...new Set([...COMPONENT_SCHEMA.type.map(({ value }) => value)]),
|
|
32
|
+
'Group'
|
|
33
|
+
];
|
|
34
|
+
|
|
33
35
|
// additional subtypes are manually added below
|
|
34
36
|
const allowedOverrideSubtypes = [
|
|
35
37
|
...new Set([
|
|
36
38
|
...COMPONENT_SCHEMA.subtype.field.map(({ value }) => value),
|
|
37
39
|
...COMPONENT_SCHEMA.subtype.template.map(({ value }) => value),
|
|
38
40
|
...COMPONENT_SCHEMA.subtype.widget.map(({ value }) => value)
|
|
39
|
-
]),
|
|
41
|
+
]),
|
|
42
|
+
'ACTION',
|
|
43
|
+
'DATA_CAPTURE',
|
|
44
|
+
'SUMMARY',
|
|
45
|
+
'CASEVIEW',
|
|
46
|
+
'DATAVIEW',
|
|
47
|
+
'LIST',
|
|
48
|
+
'TAB'
|
|
40
49
|
];
|
|
41
50
|
|
|
42
51
|
const schema = {
|
|
@@ -93,16 +102,16 @@ const schema = {
|
|
|
93
102
|
{
|
|
94
103
|
type: 'object',
|
|
95
104
|
properties: {
|
|
96
|
-
key: { type: 'string'},
|
|
105
|
+
key: { type: 'string' },
|
|
97
106
|
name: { type: 'string' },
|
|
98
107
|
label: { type: 'string' },
|
|
99
108
|
format: { type: 'string' }
|
|
100
109
|
},
|
|
101
|
-
required: ['key','name', 'label', 'format']
|
|
110
|
+
required: ['key', 'name', 'label', 'format']
|
|
102
111
|
}
|
|
103
112
|
]
|
|
104
113
|
}
|
|
105
|
-
}
|
|
114
|
+
}
|
|
106
115
|
},
|
|
107
116
|
required: ['format']
|
|
108
117
|
},
|
|
@@ -159,13 +168,12 @@ const schema = {
|
|
|
159
168
|
}
|
|
160
169
|
},
|
|
161
170
|
required: ['format', 'elements']
|
|
162
|
-
}
|
|
171
|
+
}
|
|
163
172
|
]
|
|
164
173
|
}
|
|
165
174
|
}
|
|
166
|
-
|
|
167
175
|
},
|
|
168
|
-
required: ['label', 'format']
|
|
176
|
+
required: ['label', 'format']
|
|
169
177
|
}
|
|
170
178
|
]
|
|
171
179
|
}
|
|
@@ -188,7 +196,7 @@ const overrideSchema = {
|
|
|
188
196
|
version: { type: 'string' },
|
|
189
197
|
library: { type: 'string' },
|
|
190
198
|
allowedApplications: { type: 'array' },
|
|
191
|
-
type: { type: 'string', enum:
|
|
199
|
+
type: { type: 'string', enum: allowedOverrideTypes },
|
|
192
200
|
subtype: {
|
|
193
201
|
anyOf: [{ type: 'string' }, { type: 'array' }],
|
|
194
202
|
|
|
@@ -230,16 +238,16 @@ const overrideSchema = {
|
|
|
230
238
|
{
|
|
231
239
|
type: 'object',
|
|
232
240
|
properties: {
|
|
233
|
-
key: { type: 'string'},
|
|
241
|
+
key: { type: 'string' },
|
|
234
242
|
name: { type: 'string' },
|
|
235
243
|
label: { type: 'string' },
|
|
236
244
|
format: { type: 'string' }
|
|
237
245
|
},
|
|
238
|
-
required: ['key','name', 'label', 'format']
|
|
246
|
+
required: ['key', 'name', 'label', 'format']
|
|
239
247
|
}
|
|
240
248
|
]
|
|
241
249
|
}
|
|
242
|
-
}
|
|
250
|
+
}
|
|
243
251
|
},
|
|
244
252
|
required: ['format']
|
|
245
253
|
},
|
|
@@ -296,13 +304,12 @@ const overrideSchema = {
|
|
|
296
304
|
}
|
|
297
305
|
},
|
|
298
306
|
required: ['format', 'elements']
|
|
299
|
-
}
|
|
307
|
+
}
|
|
300
308
|
]
|
|
301
309
|
}
|
|
302
310
|
}
|
|
303
|
-
|
|
304
311
|
},
|
|
305
|
-
required: ['label', 'format']
|
|
312
|
+
required: ['label', 'format']
|
|
306
313
|
}
|
|
307
314
|
]
|
|
308
315
|
}
|
|
@@ -316,8 +323,6 @@ const overrideSchema = {
|
|
|
316
323
|
const validateSchema = ajv.compile(schema);
|
|
317
324
|
const validateOverrideSchema = ajv.compile(overrideSchema);
|
|
318
325
|
|
|
319
|
-
|
|
320
|
-
|
|
321
326
|
export default async (option, category = CATEGORY_CUSTOM) => {
|
|
322
327
|
let componentToValidate = null;
|
|
323
328
|
const framework = await getFramework();
|
|
@@ -341,55 +346,57 @@ export default async (option, category = CATEGORY_CUSTOM) => {
|
|
|
341
346
|
}
|
|
342
347
|
|
|
343
348
|
const componentDirectory = await getComponentDirectoryPath(componentToValidate, category);
|
|
344
|
-
let configFileName =
|
|
349
|
+
let configFileName = 'config.json';
|
|
345
350
|
if (category === CATEGORY_OVERRIDE) {
|
|
346
|
-
configFileName =
|
|
351
|
+
configFileName = 'config-ext.json';
|
|
347
352
|
}
|
|
348
353
|
const configFile = path.join(componentDirectory, configFileName);
|
|
349
354
|
let configData;
|
|
350
355
|
try {
|
|
351
356
|
configData = fs.readFileSync(path.resolve(configFile), 'utf8');
|
|
352
|
-
}
|
|
353
|
-
catch (err) {
|
|
357
|
+
} catch (err) {
|
|
354
358
|
if (category === CATEGORY_CUSTOM) {
|
|
355
359
|
throw new Error(`Missing config file for: ${componentToValidate}`);
|
|
356
|
-
}
|
|
357
|
-
else if (category === CATEGORY_OVERRIDE) {
|
|
360
|
+
} else if (category === CATEGORY_OVERRIDE) {
|
|
358
361
|
// for now, if now file just end
|
|
359
|
-
console.log(`${chalk.bold.yellow(
|
|
362
|
+
console.log(`${chalk.bold.yellow(`Config file not available for: ${componentToValidate}`)}`);
|
|
360
363
|
return;
|
|
361
364
|
}
|
|
362
365
|
}
|
|
363
366
|
|
|
364
|
-
//const data = JSON.parse(fs.readFileSync(path.resolve(configFile), 'utf8'));
|
|
367
|
+
// const data = JSON.parse(fs.readFileSync(path.resolve(configFile), 'utf8'));
|
|
365
368
|
const data = JSON.parse(configData);
|
|
366
369
|
|
|
367
|
-
// for now a special case for ScalarList,
|
|
370
|
+
// for now a special case for ScalarList & Group, those doesn't have a "subtype", so for now manualy
|
|
368
371
|
// add one in here, just to pass validation
|
|
369
|
-
if (data
|
|
370
|
-
data
|
|
372
|
+
if (data.name === 'ScalarList') {
|
|
373
|
+
data.subtype = 'LIST';
|
|
374
|
+
}
|
|
375
|
+
if (data.name === 'Group') {
|
|
376
|
+
data.subtype = 'DATA_CAPTURE';
|
|
371
377
|
}
|
|
372
378
|
|
|
373
|
-
const valid =
|
|
379
|
+
const valid =
|
|
380
|
+
category === CATEGORY_OVERRIDE ? validateOverrideSchema(data) : validateSchema(data);
|
|
374
381
|
|
|
375
382
|
if (valid) {
|
|
376
|
-
|
|
377
383
|
// need to remove path
|
|
378
384
|
const originalComponentToValidate = componentToValidate;
|
|
379
385
|
componentToValidate = removeComponentPrefix(componentToValidate);
|
|
380
386
|
|
|
381
387
|
// don't check for out the box if override
|
|
382
|
-
if (category
|
|
383
|
-
|
|
388
|
+
if (category !== CATEGORY_OVERRIDE) {
|
|
384
389
|
// we are validating the component name, not the actual component
|
|
385
390
|
// so need to get the component name (or key) from config.json
|
|
386
|
-
switch(framework) {
|
|
387
|
-
case
|
|
391
|
+
switch (framework) {
|
|
392
|
+
case 'SDK-Angular':
|
|
388
393
|
componentToValidate = await getLocalComponentKeyFromConfig(originalComponentToValidate);
|
|
389
394
|
break;
|
|
390
|
-
case
|
|
395
|
+
case 'SDK-React':
|
|
396
|
+
break;
|
|
397
|
+
case 'SDK-WebComponents':
|
|
391
398
|
break;
|
|
392
|
-
|
|
399
|
+
default:
|
|
393
400
|
break;
|
|
394
401
|
}
|
|
395
402
|
|
|
@@ -404,15 +411,13 @@ export default async (option, category = CATEGORY_CUSTOM) => {
|
|
|
404
411
|
}
|
|
405
412
|
}
|
|
406
413
|
|
|
407
|
-
|
|
408
414
|
console.log(`${chalk.bold.green(componentToValidate)} schema is valid`);
|
|
409
415
|
} else {
|
|
416
|
+
// eslint-disable-next-line no-lonely-if
|
|
410
417
|
if (category === CATEGORY_OVERRIDE) {
|
|
411
418
|
throw new Error(`Invalid: ${ajv.errorsText(validateOverrideSchema.errors)}`);
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
419
|
+
} else {
|
|
414
420
|
throw new Error(`Invalid: ${ajv.errorsText(validateSchema.errors)}`);
|
|
415
421
|
}
|
|
416
|
-
|
|
417
422
|
}
|
|
418
423
|
};
|
|
@@ -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
|
+
}
|
package/tests/assets/sdk-a/components/create/override-sdk/field/rich-text/rich-text.component.html
ADDED
|
@@ -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>
|
package/tests/assets/sdk-a/components/create/override-sdk/field/rich-text/rich-text.component.scss
ADDED
|
File without changes
|
|
@@ -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
|
+
});
|
package/tests/assets/sdk-a/components/create/override-sdk/field/rich-text/rich-text.component.ts
ADDED
|
@@ -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
|
+
}
|