@evoke-platform/ui-components 1.0.0-dev.217 → 1.0.0-dev.218

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 (58) hide show
  1. package/dist/published/components/custom/Form/Common/Form.d.ts +38 -0
  2. package/dist/published/components/custom/Form/Common/Form.js +413 -0
  3. package/dist/published/components/custom/Form/Common/FormComponentWrapper.d.ts +26 -0
  4. package/dist/published/components/custom/Form/Common/FormComponentWrapper.js +79 -0
  5. package/dist/published/components/custom/Form/Common/index.d.ts +2 -0
  6. package/dist/published/components/custom/Form/Common/index.js +2 -0
  7. package/dist/published/components/custom/Form/FormComponents/ButtonComponent.d.ts +37 -0
  8. package/dist/published/components/custom/Form/FormComponents/ButtonComponent.js +150 -0
  9. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.d.ts +17 -0
  10. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +80 -0
  11. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentComponent.d.ts +23 -0
  12. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentComponent.js +154 -0
  13. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentList.d.ts +15 -0
  14. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentList.js +172 -0
  15. package/dist/published/components/custom/Form/FormComponents/FormFieldComponent.d.ts +41 -0
  16. package/dist/published/components/custom/Form/FormComponents/FormFieldComponent.js +409 -0
  17. package/dist/published/components/custom/Form/FormComponents/ImageComponent/Image.d.ts +15 -0
  18. package/dist/published/components/custom/Form/FormComponents/ImageComponent/Image.js +111 -0
  19. package/dist/published/components/custom/Form/FormComponents/ImageComponent/ImageComponent.d.ts +23 -0
  20. package/dist/published/components/custom/Form/FormComponents/ImageComponent/ImageComponent.js +112 -0
  21. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/InstanceLookup.d.ts +20 -0
  22. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/InstanceLookup.js +229 -0
  23. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectComponent.d.ts +34 -0
  24. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectComponent.js +150 -0
  25. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectPropertyInput.d.ts +3 -0
  26. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectPropertyInput.js +306 -0
  27. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/RelatedObjectInstance.d.ts +24 -0
  28. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/RelatedObjectInstance.js +126 -0
  29. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ActionDialog.d.ts +21 -0
  30. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ActionDialog.js +96 -0
  31. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ManyToMany/DropdownRepeatableField.d.ts +15 -0
  32. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ManyToMany/DropdownRepeatableField.js +158 -0
  33. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ManyToMany/DropdownRepeatableFieldInput.d.ts +39 -0
  34. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ManyToMany/DropdownRepeatableFieldInput.js +89 -0
  35. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.d.ts +12 -0
  36. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +369 -0
  37. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableFieldComponent.d.ts +20 -0
  38. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableFieldComponent.js +57 -0
  39. package/dist/published/components/custom/Form/FormComponents/UserComponent/UserComponent.d.ts +26 -0
  40. package/dist/published/components/custom/Form/FormComponents/UserComponent/UserComponent.js +99 -0
  41. package/dist/published/components/custom/Form/FormComponents/UserComponent/UserProperty.d.ts +23 -0
  42. package/dist/published/components/custom/Form/FormComponents/UserComponent/UserProperty.js +115 -0
  43. package/dist/published/components/custom/Form/FormComponents/ViewOnlyComponent.d.ts +20 -0
  44. package/dist/published/components/custom/Form/FormComponents/ViewOnlyComponent.js +83 -0
  45. package/dist/published/components/custom/Form/FormComponents/index.d.ts +8 -0
  46. package/dist/published/components/custom/Form/FormComponents/index.js +8 -0
  47. package/dist/published/components/custom/Form/index.d.ts +3 -0
  48. package/dist/published/components/custom/Form/index.js +3 -0
  49. package/dist/published/components/custom/Form/types.d.ts +109 -0
  50. package/dist/published/components/custom/Form/types.js +1 -0
  51. package/dist/published/components/custom/Form/utils.d.ts +45 -0
  52. package/dist/published/components/custom/Form/utils.js +1036 -0
  53. package/dist/published/components/custom/index.d.ts +1 -0
  54. package/dist/published/components/custom/index.js +1 -0
  55. package/dist/published/index.d.ts +1 -1
  56. package/dist/published/index.js +1 -1
  57. package/dist/published/styles/form-component.css +152 -0
  58. package/package.json +18 -5
@@ -0,0 +1,150 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ /* eslint-disable @typescript-eslint/no-explicit-any */
11
+ import { ReactComponent } from '@formio/react';
12
+ import { groupBy, isEmpty } from 'lodash';
13
+ import { customAlphabet } from 'nanoid';
14
+ import { lowercase, uppercase } from 'nanoid-dictionary';
15
+ import React, { useState } from 'react';
16
+ import { createRoot } from 'react-dom/client';
17
+ import { LoadingButton } from '../../../core';
18
+ import { Box } from '../../../layout';
19
+ const nanoid = customAlphabet(uppercase + lowercase);
20
+ export class ButtonComponent extends ReactComponent {
21
+ constructor(component, options, data) {
22
+ super(component, options, data);
23
+ this.getButton = (buttonAction) => {
24
+ var _a;
25
+ return (_a = this.component.components) === null || _a === void 0 ? void 0 : _a.find((button) => button && button.action == buttonAction);
26
+ };
27
+ this.handleCancel = (event) => {
28
+ const cancelButton = this.getButton('cancel');
29
+ cancelButton === null || cancelButton === void 0 ? void 0 : cancelButton.onClick(event);
30
+ };
31
+ this.handleSaveDraft = (setSubmitting) => {
32
+ setSubmitting(true);
33
+ const submission = this.root.getValue();
34
+ const saveDraftButton = this.getButton('save-draft');
35
+ if (!saveDraftButton) {
36
+ console.error('Save draft button not on form');
37
+ return;
38
+ }
39
+ this.emit('saveDraftButton', submission);
40
+ this.root.errors.forEach((error) => {
41
+ // set non-required empty fields to null if it has a mask
42
+ const maskMessage = error.messages.find((message) => message.context.validator === 'mask');
43
+ const noMaskError = !!maskMessage &&
44
+ !error.component.validate.required &&
45
+ submission.data[error.component.key] === maskMessage.context.value;
46
+ if (noMaskError) {
47
+ submission.data[error.component.key] = null;
48
+ }
49
+ });
50
+ saveDraftButton.onClick(submission, this.setError, setSubmitting);
51
+ };
52
+ this.handleSubmit = (setSubmitting) => {
53
+ setSubmitting(true);
54
+ const submission = this.root.getValue();
55
+ const submitButton = this.getButton('submit');
56
+ if (!submitButton) {
57
+ console.error('Submit button not on form');
58
+ return;
59
+ }
60
+ this.emit('submitButton', submission);
61
+ const errors = [
62
+ ...this.root.errors.filter((error) => {
63
+ // remove mask errors when field is empty and not required
64
+ const maskMessage = error.messages.find((message) => message.context.validator === 'mask');
65
+ const noMaskError = !!maskMessage &&
66
+ !error.component.validate.required &&
67
+ submission.data[error.component.key] === maskMessage.context.value;
68
+ if (noMaskError) {
69
+ submission.data[error.component.key] = null;
70
+ }
71
+ return !noMaskError;
72
+ }),
73
+ ...this.root.customErrors.filter((err) => err.code !== 'errorMessage'),
74
+ ];
75
+ if (isEmpty(errors)) {
76
+ submitButton.onClick(submission, this.setError, setSubmitting);
77
+ }
78
+ else {
79
+ setTimeout(() => {
80
+ const element = document.getElementById(`error-list-${this.root.id}`);
81
+ submitButton.isModal
82
+ ? element === null || element === void 0 ? void 0 : element.scrollIntoView({ block: 'nearest' })
83
+ : this.scrollIntoViewWithOffset(element, 170);
84
+ }, 0);
85
+ setSubmitting(false);
86
+ }
87
+ };
88
+ this.scrollIntoViewWithOffset = (el, offset) => {
89
+ var _a, _b, _c;
90
+ window.scrollTo({
91
+ behavior: 'smooth',
92
+ top: ((_a = el === null || el === void 0 ? void 0 : el.getBoundingClientRect()) === null || _a === void 0 ? void 0 : _a.top) - ((_c = (_b = document === null || document === void 0 ? void 0 : document.body) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect()) === null || _c === void 0 ? void 0 : _c.top) - offset,
93
+ });
94
+ };
95
+ this.setButtonKey = (value) => {
96
+ this.buttonKey = value;
97
+ };
98
+ this.getButton = this.getButton.bind(this);
99
+ this.handleCancel = this.handleCancel.bind(this);
100
+ this.handleSubmit = this.handleSubmit.bind(this);
101
+ this.handleSaveDraft = this.handleSaveDraft.bind(this);
102
+ this.setError = this.setError.bind(this);
103
+ this.buttonKey = nanoid();
104
+ }
105
+ setError(error) {
106
+ if ((error === null || error === void 0 ? void 0 : error.statusCode) === 422) {
107
+ for (const [key, details] of Object.entries(groupBy(error.details, 'path'))) {
108
+ this.emit(`error-${key.replace('/', '')}`, details);
109
+ }
110
+ setTimeout(() => {
111
+ const submitButton = this.getButton('submit');
112
+ const element = document.getElementById(`error-list-${this.root.id}`);
113
+ (submitButton === null || submitButton === void 0 ? void 0 : submitButton.isModal)
114
+ ? element === null || element === void 0 ? void 0 : element.scrollIntoView({ block: 'nearest' })
115
+ : this.scrollIntoViewWithOffset(element, 170);
116
+ }, 100);
117
+ }
118
+ }
119
+ attachReact(element) {
120
+ if (!this.componentRoot) {
121
+ this.componentRoot = createRoot(element);
122
+ }
123
+ return this.componentRoot.render(React.createElement(Box, { sx: { width: '100%', display: 'flex', justifyContent: 'flex-end', paddingTop: '10px' } }, this.component.components.map((button) => {
124
+ if (button) {
125
+ return (React.createElement(Button, { key: this.buttonKey, button: button, handleSubmit: this.handleSubmit, handleCancel: this.handleCancel, handleSaveDraft: this.handleSaveDraft, setButtonKey: this.setButtonKey }));
126
+ }
127
+ return null;
128
+ })));
129
+ }
130
+ }
131
+ const Button = (props) => {
132
+ const { button, handleSubmit, handleCancel, handleSaveDraft, setButtonKey } = props;
133
+ const [submitting, setSubmitting] = useState(false);
134
+ return (React.createElement(LoadingButton, { sx: button.style, variant: button.variant, onClick: (event) => __awaiter(void 0, void 0, void 0, function* () {
135
+ switch (button.action) {
136
+ case 'submit':
137
+ handleSubmit(setSubmitting);
138
+ setButtonKey(nanoid());
139
+ break;
140
+ case 'cancel':
141
+ handleCancel(event);
142
+ break;
143
+ case 'save-draft':
144
+ handleSaveDraft(setSubmitting);
145
+ break;
146
+ default:
147
+ button.onClick();
148
+ }
149
+ }), loading: submitting }, button.label));
150
+ };
@@ -0,0 +1,17 @@
1
+ /// <reference types="react" />
2
+ import { ApiServices, DocumentValidation, ObjectInstance, Property } from '@evoke-platform/context';
3
+ import { SavedDocumentReference } from '../../types';
4
+ declare type DocumentProps = {
5
+ id: string;
6
+ handleChange: (propertyId: string, value: (File | SavedDocumentReference)[] | null) => void;
7
+ property: Property;
8
+ instance: ObjectInstance;
9
+ canUpdateProperty: boolean;
10
+ error: boolean;
11
+ apiServices: ApiServices;
12
+ objectId?: string;
13
+ validate?: DocumentValidation;
14
+ value: (File | SavedDocumentReference)[] | undefined;
15
+ };
16
+ export declare const Document: (props: DocumentProps) => JSX.Element;
17
+ export {};
@@ -0,0 +1,80 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { isNil } from 'lodash';
11
+ import React, { useEffect, useState } from 'react';
12
+ import { useDropzone } from 'react-dropzone';
13
+ import { UploadCloud } from '../../../../../icons';
14
+ import { Skeleton, Snackbar, Typography } from '../../../../core';
15
+ import { Box, Grid } from '../../../../layout';
16
+ import { getPrefixedUrl } from '../../utils';
17
+ import { DocumentList } from './DocumentList';
18
+ export const Document = (props) => {
19
+ var _a;
20
+ const { id, handleChange, property, instance, canUpdateProperty, apiServices, error, objectId, value, validate } = props;
21
+ const [documents, setDocuments] = useState();
22
+ const [hasUpdatePermission, setHasUpdatePermission] = useState();
23
+ const [snackbarError, setSnackbarError] = useState();
24
+ useEffect(() => {
25
+ setDocuments(value);
26
+ }, [value]);
27
+ useEffect(() => {
28
+ const currentValue = instance === null || instance === void 0 ? void 0 : instance[property.id];
29
+ if (currentValue === null || currentValue === void 0 ? void 0 : currentValue.length) {
30
+ setDocuments(currentValue);
31
+ }
32
+ }, [property]);
33
+ useEffect(() => {
34
+ checkPermissions();
35
+ }, []);
36
+ const checkPermissions = () => {
37
+ if (canUpdateProperty) {
38
+ apiServices
39
+ .get(getPrefixedUrl(`/objects/${objectId}/instances/${instance.id}/documents/checkAccess?action=update`))
40
+ .then((accessCheck) => setHasUpdatePermission(accessCheck.result));
41
+ }
42
+ };
43
+ const handleUpload = (files) => __awaiter(void 0, void 0, void 0, function* () {
44
+ const newDocuments = [...(documents !== null && documents !== void 0 ? documents : []), ...(files !== null && files !== void 0 ? files : [])];
45
+ setDocuments(newDocuments);
46
+ handleChange(property.id, newDocuments);
47
+ });
48
+ const uploadDisabled = !!(validate === null || validate === void 0 ? void 0 : validate.maxDocuments) && ((_a = documents === null || documents === void 0 ? void 0 : documents.length) !== null && _a !== void 0 ? _a : 0) >= validate.maxDocuments;
49
+ const { getRootProps, getInputProps, open } = useDropzone({
50
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
51
+ onDrop: (files) => handleUpload(files),
52
+ disabled: uploadDisabled,
53
+ });
54
+ return (React.createElement(React.Fragment, null,
55
+ canUpdateProperty && hasUpdatePermission && (React.createElement(Box, Object.assign({ sx: {
56
+ margin: '5px 0',
57
+ height: '115px',
58
+ borderRadius: '8px',
59
+ display: 'flex',
60
+ justifyContent: 'center',
61
+ alignItems: 'center',
62
+ border: `1px dashed ${error ? 'red' : uploadDisabled ? '#DFE3E8' : '#858585'}`,
63
+ position: 'relative',
64
+ cursor: uploadDisabled ? 'cursor' : 'pointer',
65
+ } }, getRootProps(), { onClick: open }),
66
+ React.createElement("input", Object.assign({}, getInputProps({ id }), { disabled: uploadDisabled })),
67
+ React.createElement(Grid, { container: true, sx: { width: '100%' } },
68
+ React.createElement(Grid, { item: true, xs: 12, sx: { display: 'flex', justifyContent: 'center', paddingBottom: '7px' } },
69
+ React.createElement(UploadCloud, { sx: { color: uploadDisabled ? '#919EAB' : '#919EAB', width: '50px', height: '30px' } })),
70
+ React.createElement(Grid, { item: true, xs: 12 },
71
+ React.createElement(Typography, { variant: "body2", sx: { color: uploadDisabled ? '#919EAB' : '#212B36', textAlign: 'center' } },
72
+ "Drag and drop or",
73
+ ' ',
74
+ React.createElement(Typography, { component: 'span', sx: { color: uploadDisabled ? '#919EAB' : '#0075A7', fontSize: '14px' } }, "select file"),
75
+ ' ',
76
+ "to upload"))))),
77
+ canUpdateProperty && isNil(hasUpdatePermission) && (React.createElement(Skeleton, { variant: "rectangular", height: "115px", sx: { margin: '5px 0', borderRadius: '8px' } })),
78
+ React.createElement(DocumentList, { property: property, instance: instance, objectId: objectId, handleChange: handleChange, value: value, apiServices: apiServices, setSnackbarError: (type, message) => setSnackbarError({ message, type }), canUpdateProperty: canUpdateProperty && !!hasUpdatePermission }),
79
+ React.createElement(Snackbar, { open: !!(snackbarError === null || snackbarError === void 0 ? void 0 : snackbarError.message), handleClose: () => setSnackbarError(null), message: snackbarError === null || snackbarError === void 0 ? void 0 : snackbarError.message, error: (snackbarError === null || snackbarError === void 0 ? void 0 : snackbarError.type) === 'error' })));
80
+ };
@@ -0,0 +1,23 @@
1
+ import { ReactComponent } from '@formio/react';
2
+ import { Root } from 'react-dom/client';
3
+ import { BaseFormComponentProps, SavedDocumentReference } from '../../types';
4
+ export declare class DocumentComponent extends ReactComponent {
5
+ [x: string]: any;
6
+ static schema: any;
7
+ component: BaseFormComponentProps;
8
+ errorDetails: any;
9
+ componentRoot?: Root;
10
+ constructor(component: any, options: any, data: any);
11
+ init(): void;
12
+ clearErrors(): void;
13
+ hasErrors(): boolean;
14
+ errorMessages(): string;
15
+ /**
16
+ * Synchronizes out-of-the-box form.io errors with this field's errorDetails object
17
+ */
18
+ manageFormErrors(): void;
19
+ handleChange: (key: string, value?: (File | SavedDocumentReference)[] | null) => void;
20
+ handleValidation(value?: (File | SavedDocumentReference)[] | null): void;
21
+ beforeSubmit(): Promise<void>;
22
+ attachReact(element: Element): void;
23
+ }
@@ -0,0 +1,154 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ /* eslint-disable @typescript-eslint/no-explicit-any */
11
+ import { ReactComponent } from '@formio/react';
12
+ import { isEmpty } from 'lodash';
13
+ import React from 'react';
14
+ import ReactDOM from 'react-dom';
15
+ import { createRoot } from 'react-dom/client';
16
+ import { FormComponentWrapper } from '../../Common';
17
+ import { Document } from './Document';
18
+ export class DocumentComponent extends ReactComponent {
19
+ constructor(component, options, data) {
20
+ super(Object.assign(Object.assign({}, component), { canUpdateProperty: !component.readOnly, hideLabel: true }), options, data);
21
+ this.handleChange = (key, value) => {
22
+ delete this.errorDetails['api-error'];
23
+ this.setValue(value);
24
+ this.updateValue(value !== null && value !== void 0 ? value : [], { modified: true });
25
+ this.handleValidation(value);
26
+ this.emit('changed-' + this.component.key, value);
27
+ this.attach(this.element);
28
+ if (this.component.autoSave) {
29
+ this.component.autoSave({ [key]: value });
30
+ }
31
+ };
32
+ this.errorDetails = {};
33
+ this.handleChange = this.handleChange.bind(this);
34
+ }
35
+ init() {
36
+ this.on('changed-' + this.component.conditional.when, (value) => {
37
+ // set default value when conditional field is shown
38
+ if (this.component.defaultValue && value === this.component.conditional.eq) {
39
+ this.setValue(this.component.defaultValue);
40
+ this.updateValue(this.component.defaultValue, { modified: true });
41
+ }
42
+ // clear data and errors when a true conditional field is hidden
43
+ if (this.component.conditional.show && value !== this.component.conditional.eq) {
44
+ this.setValue('');
45
+ this.updateValue('', { modified: true });
46
+ this.clearErrors();
47
+ super.detach();
48
+ }
49
+ });
50
+ this.on(`api-error`, (details) => {
51
+ const error = details.find((detail) => detail.code === 'errorMessage' && detail.path.replace('/', '') === this.component.key);
52
+ if (error) {
53
+ if (!this.root.customErrors.find((err) => err.formattedKeyOrPath === this.component.key && err.message === error.message)) {
54
+ this.root.customErrors = [
55
+ ...this.root.customErrors,
56
+ Object.assign(Object.assign({}, error), { code: 'api-error', component: this.component, formattedKeyOrPath: this.component.key }),
57
+ ];
58
+ }
59
+ this.errorDetails['api-error'] = error === null || error === void 0 ? void 0 : error.message;
60
+ }
61
+ else {
62
+ this.root.customErrors = this.root.customErrors.filter((item) => item.formattedKeyOrPath !== this.component.key);
63
+ delete this.errorDetails['api-error'];
64
+ }
65
+ this.attach(this.element);
66
+ this.attachReact(this.element);
67
+ });
68
+ }
69
+ clearErrors() {
70
+ this.errorDetails = {};
71
+ this.root.customErrors = this.root.customErrors.filter((error) => error.formattedKeyOrPath !== this.component.key);
72
+ }
73
+ hasErrors() {
74
+ return !isEmpty(this.errorDetails);
75
+ }
76
+ errorMessages() {
77
+ return Object.values(this.errorDetails).join(', ');
78
+ }
79
+ /**
80
+ * Synchronizes out-of-the-box form.io errors with this field's errorDetails object
81
+ */
82
+ manageFormErrors() {
83
+ var _a;
84
+ const outOfTheBoxError = (_a = this.root.errors.find((error) => {
85
+ return error.component.key === this.component.key;
86
+ })) === null || _a === void 0 ? void 0 : _a.message;
87
+ // add OoB form.io error to errorDetails object to show under field
88
+ if (outOfTheBoxError) {
89
+ this.errorDetails['rootError'] = outOfTheBoxError;
90
+ }
91
+ else {
92
+ delete this.errorDetails['rootError'];
93
+ }
94
+ // add custom errors to be read by the Buttons component to show above the form
95
+ Object.values(this.errorDetails).forEach((error, index) => {
96
+ if (!this.root.errors.some((err) => err.message === error) &&
97
+ !this.root.customErrors.some((err) => err.message === error)) {
98
+ this.root.customErrors = [
99
+ ...this.root.customErrors,
100
+ { component: this.component, message: error, formattedKeyOrPath: this.component.key },
101
+ ];
102
+ }
103
+ });
104
+ }
105
+ handleValidation(value) {
106
+ var _a;
107
+ const validate = this.component.validate;
108
+ const amountOfDocuments = (_a = value === null || value === void 0 ? void 0 : value.length) !== null && _a !== void 0 ? _a : 0;
109
+ if (amountOfDocuments &&
110
+ (((validate === null || validate === void 0 ? void 0 : validate.minDocuments) && amountOfDocuments < validate.minDocuments) ||
111
+ ((validate === null || validate === void 0 ? void 0 : validate.maxDocuments) && amountOfDocuments > validate.maxDocuments))) {
112
+ let error;
113
+ if (validate === null || validate === void 0 ? void 0 : validate.customMessage) {
114
+ error = validate.customMessage;
115
+ }
116
+ else if (validate.minDocuments && validate.maxDocuments) {
117
+ error = `Please select between ${validate.minDocuments} and ${validate.maxDocuments} documents`;
118
+ }
119
+ else if (validate.minDocuments && amountOfDocuments < validate.minDocuments) {
120
+ error = `Please select at least ${validate.minDocuments} documents`;
121
+ }
122
+ else if (validate.maxDocuments && amountOfDocuments > validate.maxDocuments) {
123
+ error = `Please select no more than ${validate.maxDocuments} documents`;
124
+ }
125
+ this.errorDetails['documentError'] = error;
126
+ }
127
+ else {
128
+ delete this.errorDetails['documentError'];
129
+ }
130
+ // check for out-of-the-box form.io errors which store on this.root.errors
131
+ this.checkValidity(this.dataValue, true, this.data);
132
+ this.manageFormErrors();
133
+ }
134
+ beforeSubmit() {
135
+ return __awaiter(this, void 0, void 0, function* () {
136
+ this.handleValidation(this.dataValue);
137
+ this.element && this.attach(this.element);
138
+ });
139
+ }
140
+ attachReact(element) {
141
+ let root = ReactDOM.findDOMNode(element);
142
+ if (!root) {
143
+ root = element;
144
+ }
145
+ if (!this.componentRoot) {
146
+ this.componentRoot = createRoot(root);
147
+ }
148
+ // Form.io uses id for an enclosing div, so we need to give the input field a different id
149
+ const inputId = `${this.component.id}-input`;
150
+ return ReactDOM.render(React.createElement("div", null,
151
+ React.createElement(FormComponentWrapper, Object.assign({}, this.component, { inputId: inputId, viewOnly: !this.component.canUpdateProperty, errorMessage: this.errorMessages() }),
152
+ React.createElement(Document, Object.assign({}, this.component, { id: inputId, handleChange: this.handleChange, error: this.hasErrors(), value: this.dataValue })))), root);
153
+ }
154
+ }
@@ -0,0 +1,15 @@
1
+ /// <reference types="react" />
2
+ import { ApiServices, ObjectInstance, Property } from '@evoke-platform/context';
3
+ import { SavedDocumentReference } from '../../types';
4
+ declare type DocumentListProps = {
5
+ handleChange: (propertyId: string, value: (File | SavedDocumentReference)[] | null) => void;
6
+ property: Property;
7
+ instance: ObjectInstance;
8
+ canUpdateProperty: boolean;
9
+ value: (File | SavedDocumentReference)[] | undefined;
10
+ apiServices: ApiServices;
11
+ objectId?: string;
12
+ setSnackbarError: (type: 'error' | 'success', message: string) => void;
13
+ };
14
+ export declare const DocumentList: (props: DocumentListProps) => JSX.Element;
15
+ export {};
@@ -0,0 +1,172 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { isEqual } from 'lodash';
11
+ import prettyBytes from 'pretty-bytes';
12
+ import React, { useEffect, useState } from 'react';
13
+ import { FileWithExtension, LaunchRounded, TrashCan, WarningRounded } from '../../../../../icons';
14
+ import { Chip, IconButton, Typography } from '../../../../core';
15
+ import { Box, Grid } from '../../../../layout';
16
+ import { getPrefixedUrl } from '../../utils';
17
+ const styles = {
18
+ icon: {
19
+ padding: '3px',
20
+ color: '#637381',
21
+ },
22
+ };
23
+ const viewableFileTypes = [
24
+ 'application/pdf',
25
+ 'image/jpeg',
26
+ 'image/jpg',
27
+ 'image/png',
28
+ 'image/gif',
29
+ 'image/bmp',
30
+ 'image/webp',
31
+ 'text/plain',
32
+ ];
33
+ export const DocumentList = (props) => {
34
+ const { handleChange, property, instance, canUpdateProperty, objectId, value: documents, apiServices, setSnackbarError, } = props;
35
+ const [savedDocuments, setSavedDocuments] = useState();
36
+ const [hasViewPermission, setHasViewPermission] = useState(true);
37
+ useEffect(() => {
38
+ const currentValue = instance === null || instance === void 0 ? void 0 : instance[property.id];
39
+ if (currentValue === null || currentValue === void 0 ? void 0 : currentValue.length) {
40
+ const currentDocumentIds = currentValue.map((doc) => doc.id);
41
+ if (currentDocumentIds.length &&
42
+ !isEqual(currentDocumentIds, savedDocuments === null || savedDocuments === void 0 ? void 0 : savedDocuments.map((doc) => doc.id))) {
43
+ getDocuments(currentDocumentIds);
44
+ }
45
+ }
46
+ }, [property]);
47
+ const getDocuments = (currentDocumentIds, shouldRetry = true) => {
48
+ apiServices.get(getPrefixedUrl(`/objects/${objectId}/instances/${instance.id}/documents`), {
49
+ params: { filter: { where: { id: { inq: currentDocumentIds } } } },
50
+ }, (error, docs) => {
51
+ // There is a short delay between when a document is uploaded and when
52
+ // it is indexed. Therefore, try again if documents are not found.
53
+ if (shouldRetry &&
54
+ (!docs || currentDocumentIds.some((docId) => !docs.find((doc) => docId === doc.id)))) {
55
+ setTimeout(() => getDocuments(currentDocumentIds, false), 2000);
56
+ }
57
+ else if (error) {
58
+ setSnackbarError('error', 'Error occurred while retrieving saved documents');
59
+ }
60
+ else {
61
+ setSavedDocuments(docs);
62
+ }
63
+ });
64
+ };
65
+ useEffect(() => {
66
+ checkPermissions();
67
+ }, []);
68
+ const checkPermissions = () => {
69
+ var _a;
70
+ if ((_a = instance === null || instance === void 0 ? void 0 : instance[property.id]) === null || _a === void 0 ? void 0 : _a.length) {
71
+ apiServices
72
+ .get(getPrefixedUrl(`/objects/${objectId}/instances/${instance.id}/documents/checkAccess?action=view`))
73
+ .then((accessCheck) => setHasViewPermission(accessCheck.result));
74
+ }
75
+ };
76
+ const isFile = (doc) => doc instanceof File;
77
+ const fileExists = (doc) => savedDocuments === null || savedDocuments === void 0 ? void 0 : savedDocuments.find((d) => d.id === doc.id);
78
+ const handleRemove = (index) => {
79
+ var _a;
80
+ const updatedDocuments = (_a = documents === null || documents === void 0 ? void 0 : documents.filter((_, i) => i !== index)) !== null && _a !== void 0 ? _a : [];
81
+ handleChange(property.id, updatedDocuments);
82
+ };
83
+ const openDocument = (index) => __awaiter(void 0, void 0, void 0, function* () {
84
+ var _a, _b;
85
+ const doc = documents === null || documents === void 0 ? void 0 : documents[index];
86
+ if (doc) {
87
+ let url;
88
+ const contentType = doc instanceof File
89
+ ? doc.type
90
+ : (_a = savedDocuments === null || savedDocuments === void 0 ? void 0 : savedDocuments.find((savedDocument) => savedDocument.id === doc.id)) === null || _a === void 0 ? void 0 : _a.contentType;
91
+ if (!isFile(doc)) {
92
+ try {
93
+ const documentResponse = yield apiServices.get(getPrefixedUrl(`/objects/${objectId}/instances/${instance.id}/documents/${doc.id}/content`), { responseType: 'blob' });
94
+ const blob = new Blob([documentResponse], { type: contentType });
95
+ url = window.URL.createObjectURL(blob);
96
+ }
97
+ catch (error) {
98
+ setSnackbarError('error', `Could not open ${doc.name}`);
99
+ return;
100
+ }
101
+ }
102
+ else {
103
+ url = URL.createObjectURL(doc);
104
+ }
105
+ if (contentType && viewableFileTypes.includes(contentType)) {
106
+ window.open(url, '_blank');
107
+ }
108
+ else {
109
+ const link = document.createElement('a');
110
+ link.href = url;
111
+ link.setAttribute('download', doc.name);
112
+ document.body.appendChild(link);
113
+ link.click();
114
+ // Clean up and remove the link
115
+ (_b = link.parentNode) === null || _b === void 0 ? void 0 : _b.removeChild(link);
116
+ }
117
+ }
118
+ });
119
+ const getDocumentSize = (doc) => {
120
+ let size;
121
+ if (isFile(doc)) {
122
+ size = prettyBytes(doc.size);
123
+ }
124
+ else {
125
+ const savedDoc = savedDocuments === null || savedDocuments === void 0 ? void 0 : savedDocuments.find((savedDocument) => savedDocument.id === doc.id);
126
+ size = savedDoc ? prettyBytes(savedDoc.size) : '';
127
+ }
128
+ return size;
129
+ };
130
+ return (React.createElement(React.Fragment, null,
131
+ !documents && !canUpdateProperty && (React.createElement(Typography, { variant: "body2", sx: { color: '#637381' } }, "No documents")),
132
+ !!(documents === null || documents === void 0 ? void 0 : documents.length) && (React.createElement(Box, null, documents.map((doc, index) => {
133
+ var _a, _b, _c;
134
+ return (React.createElement(Grid, { container: true, sx: {
135
+ width: '100%',
136
+ border: '1px solid #C4CDD5',
137
+ borderRadius: '6px',
138
+ margin: '5px 2px',
139
+ padding: ' 8px',
140
+ display: 'flex',
141
+ alignItems: 'center',
142
+ } },
143
+ React.createElement(Grid, { item: true, sx: { display: 'flex', justifyContent: 'center', padding: '7px', marginLeft: '4px' } },
144
+ React.createElement(FileWithExtension, { fontFamily: "Arial", fileExtension: (_c = (_b = (_a = doc.name) === null || _a === void 0 ? void 0 : _a.split('.')) === null || _b === void 0 ? void 0 : _b.pop()) !== null && _c !== void 0 ? _c : '', sx: { height: '1.5em', width: '1.5em' } })),
145
+ React.createElement(Grid, { item: true, sx: { flex: 1, justifyContent: 'center', paddingBottom: '5px' } },
146
+ React.createElement(Grid, { item: true, xs: 12 },
147
+ React.createElement(Typography, { sx: {
148
+ fontSize: '14px',
149
+ fontWeight: 700,
150
+ color: '#212B36',
151
+ lineHeight: '15px',
152
+ paddingTop: '8px',
153
+ } }, doc.name)),
154
+ React.createElement(Grid, { item: true, xs: 12 },
155
+ React.createElement(Typography, { sx: { fontSize: '12px', color: '#637381' } }, getDocumentSize(doc)))),
156
+ (isFile(doc) || (hasViewPermission && !isFile(doc) && fileExists(doc))) && (React.createElement(Grid, { item: true },
157
+ React.createElement(IconButton, { "aria-label": "open document", sx: Object.assign(Object.assign({}, styles.icon), { marginRight: '16px' }), onClick: () => openDocument(index) },
158
+ React.createElement(LaunchRounded, { sx: { color: '#637381', fontSize: '22px' } })))),
159
+ !isFile(doc) && savedDocuments && !fileExists(doc) && (React.createElement(Chip, { label: "Deleted", sx: {
160
+ marginRight: '16px',
161
+ backgroundColor: 'rgba(222, 48, 36, 0.16)',
162
+ color: '#A91813',
163
+ borderRadius: '6px',
164
+ height: '25px',
165
+ fontWeight: 700,
166
+ '& .MuiChip-icon': { color: '#A91813', width: '.8em', marginBottom: '2px' },
167
+ }, icon: React.createElement(WarningRounded, null) })),
168
+ canUpdateProperty && (React.createElement(Grid, { item: true },
169
+ React.createElement(IconButton, { "aria-label": "delete document", sx: styles.icon, onClick: () => handleRemove(index) },
170
+ React.createElement(TrashCan, { sx: { color: '#637381' } }))))));
171
+ })))));
172
+ };
@@ -0,0 +1,41 @@
1
+ import { Property } from '@evoke-platform/context';
2
+ import { ReactComponent } from '@formio/react';
3
+ import { BaseFormComponentProps } from '../types';
4
+ declare type FormFieldComponentProps = Omit<BaseFormComponentProps, 'property'> & {
5
+ property: Omit<Property, 'type'> & {
6
+ type: Property['type'] | 'choices';
7
+ };
8
+ inputMask?: string;
9
+ inputMaskPlaceholderChar?: string;
10
+ addressPropertyId?: string;
11
+ isAddressLine1?: boolean;
12
+ };
13
+ export declare class FormFieldComponent extends ReactComponent {
14
+ [x: string]: any;
15
+ static schema: any;
16
+ component: FormFieldComponentProps;
17
+ errorDetails: any;
18
+ /**
19
+ * Called when the component has been instantiated. This is useful to define
20
+ * default instance variable values.
21
+ *
22
+ * @param component - The JSON representation of the component created.
23
+ * @param options - The global options for the renderer
24
+ * @param data - The contextual data object (model) used for this component.
25
+ */
26
+ constructor(component: FormFieldComponentProps, options: any, data: any);
27
+ init(): void;
28
+ clearErrors(): void;
29
+ handleValidation(value: string): void;
30
+ hasErrors(): boolean;
31
+ errorMessages(): string;
32
+ /**
33
+ * Synchronizes out-of-the-box formio errors with this field's errorDetails object
34
+ */
35
+ manageFormErrors(): void;
36
+ beforeSubmit(): void;
37
+ handleComponentChange: (components: any, value: any) => void;
38
+ handleChange: (key: string, value: any) => void;
39
+ attachReact(element: Element): void;
40
+ }
41
+ export {};