@evoke-platform/ui-components 1.13.0-dev.6 → 1.13.0
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/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.d.ts +4 -4
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +72 -145
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.js +67 -189
- package/dist/published/components/custom/CriteriaBuilder/PropertyTree.d.ts +6 -6
- package/dist/published/components/custom/CriteriaBuilder/PropertyTree.js +25 -12
- package/dist/published/components/custom/CriteriaBuilder/PropertyTreeItem.d.ts +5 -4
- package/dist/published/components/custom/CriteriaBuilder/PropertyTreeItem.js +22 -34
- package/dist/published/components/custom/CriteriaBuilder/types.d.ts +11 -2
- package/dist/published/components/custom/CriteriaBuilder/utils.d.ts +34 -6
- package/dist/published/components/custom/CriteriaBuilder/utils.js +89 -18
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +1 -1
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentList.js +3 -6
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +1 -1
- package/dist/published/components/custom/Form/utils.d.ts +0 -1
- package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +1 -2
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +1 -1
- package/dist/published/components/custom/FormV2/FormRenderer.js +2 -1
- package/dist/published/components/custom/FormV2/FormRendererContainer.d.ts +3 -1
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +23 -26
- package/dist/published/components/custom/FormV2/components/Body.js +1 -1
- package/dist/published/components/custom/FormV2/components/Footer.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +0 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.d.ts +0 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +3 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.d.ts +2 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +10 -46
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +3 -4
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +23 -29
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormSections.js +0 -1
- package/dist/published/components/custom/FormV2/components/Header.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/Header.js +19 -8
- package/dist/published/components/custom/FormV2/components/HtmlView.d.ts +9 -0
- package/dist/published/components/custom/FormV2/components/HtmlView.js +42 -0
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +4 -8
- package/dist/published/components/custom/FormV2/components/types.d.ts +1 -6
- package/dist/published/components/custom/FormV2/components/utils.d.ts +7 -5
- package/dist/published/components/custom/FormV2/components/utils.js +79 -156
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +2 -2
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +84 -0
- package/dist/published/components/custom/HistoryLog/HistoryData.js +1 -2
- package/dist/published/components/custom/HistoryLog/index.js +1 -2
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +10 -24
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.d.ts +3 -0
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +3 -1
- package/dist/published/stories/Backdrop.stories.d.ts +2 -2
- package/dist/published/stories/CriteriaBuilder.stories.js +22 -70
- package/dist/published/stories/FormLabel.stories.d.ts +2 -2
- package/dist/published/stories/FormRenderer.stories.d.ts +3 -3
- package/dist/published/stories/FormRendererContainer.stories.d.ts +15 -5
- package/dist/published/stories/ViewDetailsV2Container.stories.d.ts +9 -0
- package/dist/published/theme/hooks.d.ts +1 -2
- package/package.json +10 -15
package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useApiServices
|
|
1
|
+
import { useApiServices } from '@evoke-platform/context';
|
|
2
2
|
import { isNil } from 'lodash';
|
|
3
3
|
import prettyBytes from 'pretty-bytes';
|
|
4
4
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
@@ -7,12 +7,12 @@ import { InfoRounded, UploadCloud } from '../../../../../../icons';
|
|
|
7
7
|
import { useFormContext } from '../../../../../../theme/hooks';
|
|
8
8
|
import { Skeleton, Snackbar, Typography } from '../../../../../core';
|
|
9
9
|
import { Box, Grid } from '../../../../../layout';
|
|
10
|
-
import {
|
|
10
|
+
import { getPrefixedUrl } from '../../utils';
|
|
11
11
|
import { DocumentList } from './DocumentList';
|
|
12
12
|
export const Document = (props) => {
|
|
13
|
-
const { id,
|
|
13
|
+
const { id, canUpdateProperty, error, value, validate, hasDescription } = props;
|
|
14
14
|
const apiServices = useApiServices();
|
|
15
|
-
const { fetchedOptions, setFetchedOptions, object, handleChange, onAutosave: onAutosave, instance,
|
|
15
|
+
const { fetchedOptions, setFetchedOptions, object, handleChange, onAutosave: onAutosave, instance, } = useFormContext();
|
|
16
16
|
const [snackbarError, setSnackbarError] = useState();
|
|
17
17
|
const [documents, setDocuments] = useState();
|
|
18
18
|
const [hasUpdatePermission, setHasUpdatePermission] = useState(fetchedOptions[`${id}UpdatePermission`]);
|
|
@@ -35,17 +35,8 @@ export const Document = (props) => {
|
|
|
35
35
|
}, [value]);
|
|
36
36
|
const checkPermissions = useCallback(() => {
|
|
37
37
|
if (canUpdateProperty && !fetchedOptions[`${id}UpdatePermission`] && instance?.id) {
|
|
38
|
-
// Find the entry to get the configured createActionId
|
|
39
|
-
const allEntries = getUnnestedEntries(form?.entries ?? []) ?? [];
|
|
40
|
-
const entry = allEntries?.find((entry) => getEntryId(entry) === id);
|
|
41
|
-
const createActionId = entry?.display?.createActionId ?? '_create';
|
|
42
|
-
// For 'file' type properties, check regular object instance permissions
|
|
43
|
-
// For 'document' type properties, check document attachment permissions
|
|
44
|
-
const endpoint = fieldType === 'file'
|
|
45
|
-
? getPrefixedUrl(`/objects/sys__file/instances/checkAccess?action=execute&field=${createActionId}`)
|
|
46
|
-
: getPrefixedUrl(`/objects/${object?.id}/instances/${instance.id}/documents/checkAccess?action=update`);
|
|
47
38
|
apiServices
|
|
48
|
-
.get(
|
|
39
|
+
.get(getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/checkAccess?action=update`))
|
|
49
40
|
.then((accessCheck) => {
|
|
50
41
|
setFetchedOptions({
|
|
51
42
|
[`${id}UpdatePermission`]: accessCheck.result,
|
|
@@ -59,43 +50,16 @@ export const Document = (props) => {
|
|
|
59
50
|
setHasUpdatePermission(false);
|
|
60
51
|
});
|
|
61
52
|
}
|
|
62
|
-
}, [
|
|
63
|
-
canUpdateProperty,
|
|
64
|
-
fetchedOptions[`${id}UpdatePermission`],
|
|
65
|
-
instance?.id,
|
|
66
|
-
object,
|
|
67
|
-
id,
|
|
68
|
-
apiServices,
|
|
69
|
-
form,
|
|
70
|
-
fieldType,
|
|
71
|
-
]);
|
|
53
|
+
}, [canUpdateProperty, fetchedOptions, instance, object]);
|
|
72
54
|
useEffect(() => {
|
|
73
55
|
checkPermissions();
|
|
74
56
|
}, [checkPermissions]);
|
|
75
57
|
const handleUpload = async (files) => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
let uploadedFiles = files;
|
|
80
|
-
// Get the createActionId from display options, default to '_create'
|
|
81
|
-
const allEntries = getUnnestedEntries(form?.entries ?? []);
|
|
82
|
-
const entry = allEntries?.find((entry) => getEntryId(entry) === id);
|
|
83
|
-
const createActionId = entry?.display?.createActionId ?? '_create';
|
|
84
|
-
// Immediately upload files for 'file' type properties when autosave is not enabled.
|
|
85
|
-
// Linking will happen upon final submission.
|
|
86
|
-
// If autosave is enabled, upload and linking will happen in the autosave handler.
|
|
87
|
-
if (fieldType === 'file' && !onAutosave) {
|
|
88
|
-
const { successfulUploads, errorMessage } = await uploadFiles(files, apiServices, createActionId, undefined, false);
|
|
89
|
-
uploadedFiles = successfulUploads;
|
|
90
|
-
if (errorMessage) {
|
|
91
|
-
setSnackbarError({ message: errorMessage, type: 'error' });
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
// Store uploaded file references (or File objects) in form state
|
|
95
|
-
const newDocuments = [...(documents ?? []), ...uploadedFiles];
|
|
58
|
+
// Store File objects in form state - they will be uploaded during autosave via formatSubmission()
|
|
59
|
+
const newDocuments = [...(documents ?? []), ...(files ?? [])];
|
|
96
60
|
setDocuments(newDocuments);
|
|
97
61
|
try {
|
|
98
|
-
await handleChange
|
|
62
|
+
handleChange && (await handleChange(id, newDocuments));
|
|
99
63
|
}
|
|
100
64
|
catch (error) {
|
|
101
65
|
console.error('Failed to update field:', error);
|
|
@@ -163,7 +127,7 @@ export const Document = (props) => {
|
|
|
163
127
|
} }, validate?.maxDocuments === 1
|
|
164
128
|
? `Maximum size is ${formattedMaxSize}.`
|
|
165
129
|
: `The maximum size of each document is ${formattedMaxSize}.`)))))),
|
|
166
|
-
canUpdateProperty && isNil(hasUpdatePermission) ? (React.createElement(Skeleton, { variant: "rectangular", height: formattedMaxSize || allowedTypesMessage ? '136px' : '115px', sx: { margin: '5px 0', borderRadius: '8px' } })) : (React.createElement(DocumentList, { id: id,
|
|
130
|
+
canUpdateProperty && isNil(hasUpdatePermission) ? (React.createElement(Skeleton, { variant: "rectangular", height: formattedMaxSize || allowedTypesMessage ? '136px' : '115px', sx: { margin: '5px 0', borderRadius: '8px' } })) : (React.createElement(DocumentList, { id: id, handleChange: handleChange, onAutosave: onAutosave, value: value, setSnackbarError: (type, message) => setSnackbarError({ message, type }), canUpdateProperty: canUpdateProperty && !!hasUpdatePermission })),
|
|
167
131
|
React.createElement(Snackbar, { open: !!snackbarError?.message, handleClose: () => setSnackbarError(null), message: snackbarError?.message, error: snackbarError?.type === 'error' }),
|
|
168
132
|
errors.length > 0 && (React.createElement(Box, { display: 'flex', alignItems: 'center' },
|
|
169
133
|
React.createElement(InfoRounded, { sx: { fontSize: '.75rem', marginRight: '3px', color: '#D3271B' } }),
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { SavedDocumentReference } from '../../types';
|
|
3
3
|
type DocumentListProps = {
|
|
4
|
-
handleChange?: (propertyId: string, value: (File |
|
|
4
|
+
handleChange?: (propertyId: string, value: (File | SavedDocumentReference)[] | undefined) => void;
|
|
5
5
|
onAutosave?: (fieldId: string) => void | Promise<void>;
|
|
6
6
|
id: string;
|
|
7
|
-
fieldType?: 'document' | 'file';
|
|
8
7
|
canUpdateProperty: boolean;
|
|
9
|
-
value: (File |
|
|
8
|
+
value: (File | SavedDocumentReference)[] | undefined;
|
|
10
9
|
setSnackbarError: (type: 'error' | 'success', message: string) => void;
|
|
11
10
|
};
|
|
12
11
|
export declare const DocumentList: (props: DocumentListProps) => React.JSX.Element;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useApiServices } from '@evoke-platform/context';
|
|
2
|
+
import { isEqual } from 'lodash';
|
|
2
3
|
import prettyBytes from 'pretty-bytes';
|
|
3
4
|
import React, { useEffect, useState } from 'react';
|
|
4
5
|
import { FileWithExtension, LaunchRounded, TrashCan, WarningRounded } from '../../../../../../icons';
|
|
@@ -23,33 +24,29 @@ const viewableFileTypes = [
|
|
|
23
24
|
'text/plain',
|
|
24
25
|
];
|
|
25
26
|
export const DocumentList = (props) => {
|
|
26
|
-
const { handleChange, onAutosave, id,
|
|
27
|
+
const { handleChange, onAutosave, id, canUpdateProperty, value: documents, setSnackbarError } = props;
|
|
27
28
|
const apiServices = useApiServices();
|
|
28
29
|
const { fetchedOptions, setFetchedOptions, object, instance } = useFormContext();
|
|
29
30
|
// Determine property type once at component level
|
|
30
|
-
const
|
|
31
|
+
const propertyType = object?.properties?.find((p) => p.id === id)?.type;
|
|
32
|
+
const isFileType = propertyType === 'file';
|
|
31
33
|
const [hasViewPermission, setHasViewPermission] = useState(fetchedOptions[`${id}ViewPermission`] ?? true);
|
|
32
34
|
// savedDocuments is either FileInstance[] or DocumentType[], never a mix
|
|
33
35
|
const [savedDocuments, setSavedDocuments] = useState(fetchedOptions[`${id}SavedDocuments`]);
|
|
34
36
|
useEffect(() => {
|
|
35
|
-
|
|
36
|
-
// This ensures newly uploaded files trigger a fetch even before they're saved to instance
|
|
37
|
-
const currentValue = documents;
|
|
37
|
+
const currentValue = instance?.[id];
|
|
38
38
|
if (currentValue?.length) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (missingDocumentIds.length > 0) {
|
|
48
|
-
getDocuments(currentDocumentIds);
|
|
49
|
-
}
|
|
39
|
+
const currentDocumentIds = currentValue.map((doc) => doc.id);
|
|
40
|
+
if (currentDocumentIds.length &&
|
|
41
|
+
// these need to be sorted otherwise it will evaluate as not equal if the ids are in different orders causing unnecessary fetches
|
|
42
|
+
!isEqual(currentDocumentIds.slice().sort(), savedDocuments
|
|
43
|
+
?.map((doc) => doc.id)
|
|
44
|
+
.slice()
|
|
45
|
+
.sort())) {
|
|
46
|
+
getDocuments(currentDocumentIds);
|
|
50
47
|
}
|
|
51
48
|
}
|
|
52
|
-
}, [documents,
|
|
49
|
+
}, [id, documents, object]);
|
|
53
50
|
useEffect(() => {
|
|
54
51
|
if (fetchedOptions[`${id}SavedDocuments`]) {
|
|
55
52
|
setSavedDocuments(fetchedOptions[`${id}SavedDocuments`]);
|
|
@@ -86,31 +83,26 @@ export const DocumentList = (props) => {
|
|
|
86
83
|
if (!fetchedOptions[`${id}ViewPermission`]) {
|
|
87
84
|
checkPermissions();
|
|
88
85
|
}
|
|
89
|
-
}, [object
|
|
86
|
+
}, [object]);
|
|
90
87
|
const checkPermissions = () => {
|
|
91
88
|
if (instance?.[id]?.length) {
|
|
92
|
-
const endpoint = isFileType
|
|
93
|
-
? getPrefixedUrl(`/objects/sys__file/instances/checkAccess?action=read&field=content`)
|
|
94
|
-
: getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/checkAccess?action=view`);
|
|
95
89
|
apiServices
|
|
96
|
-
.get(
|
|
90
|
+
.get(getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/checkAccess?action=view`))
|
|
97
91
|
.then((viewPermissionCheck) => {
|
|
98
92
|
setFetchedOptions({
|
|
99
93
|
[`${id}ViewPermission`]: viewPermissionCheck.result,
|
|
100
94
|
});
|
|
101
95
|
setHasViewPermission(viewPermissionCheck.result);
|
|
102
|
-
})
|
|
103
|
-
.catch(() => setHasViewPermission(false));
|
|
96
|
+
});
|
|
104
97
|
}
|
|
105
98
|
};
|
|
106
99
|
const isFile = (doc) => doc instanceof File;
|
|
107
|
-
const isUnsavedFile = (doc) => isFile(doc) || !!doc.unsaved;
|
|
108
100
|
const fileExists = (doc) => savedDocuments?.find((d) => d.id === doc.id);
|
|
109
101
|
const handleRemove = async (index) => {
|
|
110
102
|
const updatedDocuments = documents?.filter((_, i) => i !== index) ?? [];
|
|
111
103
|
const newValue = updatedDocuments.length === 0 ? undefined : updatedDocuments;
|
|
112
104
|
try {
|
|
113
|
-
handleChange
|
|
105
|
+
handleChange && (await handleChange(id, newValue));
|
|
114
106
|
}
|
|
115
107
|
catch (error) {
|
|
116
108
|
console.error('Failed to update field:', error);
|
|
@@ -132,7 +124,9 @@ export const DocumentList = (props) => {
|
|
|
132
124
|
: savedDocuments?.find((savedDocument) => savedDocument.id === doc.id)?.contentType;
|
|
133
125
|
if (!isFile(doc)) {
|
|
134
126
|
try {
|
|
135
|
-
|
|
127
|
+
// Determine property type to use the correct endpoint
|
|
128
|
+
const propertyType = object?.properties?.find((p) => p.id === id)?.type;
|
|
129
|
+
const contentEndpoint = propertyType === 'file'
|
|
136
130
|
? getPrefixedUrl(`/files/${doc.id}/content`)
|
|
137
131
|
: getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/${doc.id}/content`);
|
|
138
132
|
const documentResponse = await apiServices.get(contentEndpoint, { responseType: 'blob' });
|
|
@@ -195,10 +189,10 @@ export const DocumentList = (props) => {
|
|
|
195
189
|
} }, doc.name)),
|
|
196
190
|
React.createElement(Grid, { item: true, xs: 12 },
|
|
197
191
|
React.createElement(Typography, { sx: { fontSize: '12px', color: '#637381' } }, getDocumentSize(doc)))),
|
|
198
|
-
(
|
|
192
|
+
(isFile(doc) || (hasViewPermission && !isFile(doc) && fileExists(doc))) && (React.createElement(Grid, { item: true },
|
|
199
193
|
React.createElement(IconButton, { "aria-label": "open document", sx: { ...styles.icon, marginRight: '16px' }, onClick: () => openDocument(index) },
|
|
200
194
|
React.createElement(LaunchRounded, { sx: { color: '#637381', fontSize: '22px' } })))),
|
|
201
|
-
!isFile(doc) &&
|
|
195
|
+
!isFile(doc) && savedDocuments && !fileExists(doc) && (React.createElement(Chip, { label: "Deleted", sx: {
|
|
202
196
|
marginRight: '16px',
|
|
203
197
|
backgroundColor: 'rgba(222, 48, 36, 0.16)',
|
|
204
198
|
color: '#A91813',
|
|
@@ -16,7 +16,7 @@ const SearchField = (props) => {
|
|
|
16
16
|
};
|
|
17
17
|
const handleSearch = async (e) => {
|
|
18
18
|
const searchProperties = searchableColumns.map((column) => {
|
|
19
|
-
const columnId = column.id;
|
|
19
|
+
const columnId = column.type === 'object' ? `${column.id}.name` : column.id;
|
|
20
20
|
return {
|
|
21
21
|
[columnId]: {
|
|
22
22
|
like: e.target.value,
|
|
@@ -126,7 +126,6 @@ function FormSections(props) {
|
|
|
126
126
|
display: 'flex',
|
|
127
127
|
flexDirection: 'column',
|
|
128
128
|
justifyContent: section.entries && section?.entries.length > 0 ? 'space-between' : 'flex-end',
|
|
129
|
-
height: '100%',
|
|
130
129
|
} },
|
|
131
130
|
React.createElement(Box, null, section.entries &&
|
|
132
131
|
section.entries.map((sectionEntry, index) => {
|
|
@@ -7,7 +7,7 @@ import { Typography } from '../../../core/Typography';
|
|
|
7
7
|
import Box from '../../../layout/Box/Box';
|
|
8
8
|
import ValidationErrors from './ValidationFiles/ValidationErrors';
|
|
9
9
|
const Header = (props) => {
|
|
10
|
-
const { title, errors, hasAccordions, shouldShowValidationErrors, validationContainerRef, sx, autosaveEnabled } = props;
|
|
10
|
+
const { title, errors, hasAccordions, shouldShowValidationErrors, validationContainerRef, sx, autosaveEnabled, hideTitle, autosaving, } = props;
|
|
11
11
|
const { width } = useFormContext();
|
|
12
12
|
const { breakpoints, isBelow } = useWidgetSize({
|
|
13
13
|
scroll: false,
|
|
@@ -17,23 +17,32 @@ const Header = (props) => {
|
|
|
17
17
|
const { isXs, isSm } = breakpoints;
|
|
18
18
|
const isSmall = isSm || isXs;
|
|
19
19
|
const displayValidationErrors = shouldShowValidationErrors && !isEmpty(errors);
|
|
20
|
+
if (hideTitle && !autosaveEnabled && !hasAccordions && !displayValidationErrors) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
20
23
|
return (React.createElement(Box, { sx: {
|
|
21
24
|
paddingX: isSmallerThanMd ? 2 : 3,
|
|
22
25
|
paddingTop: '0px',
|
|
23
26
|
display: 'flex',
|
|
24
27
|
alignItems: 'center',
|
|
28
|
+
justifyContent: 'flex-end',
|
|
25
29
|
flexWrap: 'wrap',
|
|
26
30
|
paddingY: isSm || isXs ? 2 : 3,
|
|
27
31
|
borderBottom: '1px solid #e9ecef',
|
|
28
32
|
gap: isSm || isXs ? 2 : 3,
|
|
33
|
+
minHeight: '24px',
|
|
29
34
|
...sx,
|
|
30
35
|
}, ref: validationContainerRef },
|
|
31
|
-
title && (React.createElement(Box, { sx: {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
((!hideTitle && title) || (autosaving && !isSmall)) && (React.createElement(Box, { sx: {
|
|
37
|
+
flex: '1 1 auto',
|
|
38
|
+
minWidth: 0,
|
|
39
|
+
display: 'flex',
|
|
40
|
+
alignItems: 'center',
|
|
41
|
+
gap: 1,
|
|
42
|
+
} },
|
|
43
|
+
!hideTitle && title && React.createElement(Title, { ...props }),
|
|
44
|
+
autosaving && !isSmall ? React.createElement(SavingIndicator, null) : React.createElement(React.Fragment, null))),
|
|
45
|
+
hasAccordions && (React.createElement(Box, { sx: { flex: '0 0 auto', display: 'flex', alignItems: 'center', justifyContent: 'space-between' } },
|
|
37
46
|
autosaveEnabled && (React.createElement(Box, { sx: {
|
|
38
47
|
width: '96px',
|
|
39
48
|
minWidth: '72px',
|
|
@@ -41,7 +50,9 @@ const Header = (props) => {
|
|
|
41
50
|
justifyContent: 'flex-end',
|
|
42
51
|
alignItems: 'center',
|
|
43
52
|
marginLeft: 0.5,
|
|
44
|
-
} },
|
|
53
|
+
} }, autosaving && isSmall ? React.createElement(SavingIndicator, null) : React.createElement(React.Fragment, null))),
|
|
54
|
+
React.createElement(Box, { sx: { display: 'flex', alignItems: 'center', justifyContent: 'center' } },
|
|
55
|
+
React.createElement(AccordionActions, { ...props })))),
|
|
45
56
|
displayValidationErrors ? React.createElement(ValidationErrors, { errors: errors }) : null));
|
|
46
57
|
};
|
|
47
58
|
// Default slot components for convenience
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import 'quill/dist/quill.snow.css';
|
|
3
|
+
import 'quill-table-up/index.css';
|
|
4
|
+
type EditorProps = {
|
|
5
|
+
/** HTML content to display in the read-only editor */
|
|
6
|
+
value: string;
|
|
7
|
+
};
|
|
8
|
+
declare const HtmlView: ({ value }: EditorProps) => React.JSX.Element;
|
|
9
|
+
export default HtmlView;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import Quill from 'quill';
|
|
2
|
+
import React, { useEffect, useRef } from 'react';
|
|
3
|
+
import 'quill/dist/quill.snow.css';
|
|
4
|
+
import TableUp from 'quill-table-up';
|
|
5
|
+
import 'quill-table-up/index.css';
|
|
6
|
+
import { Box } from '@mui/material';
|
|
7
|
+
import DOMPurify from 'dompurify';
|
|
8
|
+
Quill.register({ [`modules/${TableUp.moduleName}`]: TableUp }, true);
|
|
9
|
+
const HtmlView = ({ value }) => {
|
|
10
|
+
const containerRef = useRef(null);
|
|
11
|
+
const quillRef = useRef(null);
|
|
12
|
+
// Initialize ONCE
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!containerRef.current || quillRef.current)
|
|
15
|
+
return;
|
|
16
|
+
const quill = new Quill(containerRef.current, {
|
|
17
|
+
theme: 'snow',
|
|
18
|
+
readOnly: true,
|
|
19
|
+
modules: {
|
|
20
|
+
toolbar: false,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
quillRef.current = quill;
|
|
24
|
+
}, []);
|
|
25
|
+
// Update content only
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!quillRef.current)
|
|
28
|
+
return;
|
|
29
|
+
quillRef.current.setContents([]);
|
|
30
|
+
quillRef.current.clipboard.dangerouslyPasteHTML(DOMPurify.sanitize(value));
|
|
31
|
+
}, [value]);
|
|
32
|
+
return (React.createElement(Box, { sx: {
|
|
33
|
+
width: '100%',
|
|
34
|
+
height: '100%',
|
|
35
|
+
'.ql-container': {
|
|
36
|
+
border: 'none',
|
|
37
|
+
minHeight: 20,
|
|
38
|
+
},
|
|
39
|
+
} },
|
|
40
|
+
React.createElement("div", { ref: containerRef })));
|
|
41
|
+
};
|
|
42
|
+
export default HtmlView;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useApiServices, useAuthenticationContext, } from '@evoke-platform/context';
|
|
2
2
|
import { WarningRounded } from '@mui/icons-material';
|
|
3
|
-
import DOMPurify from 'dompurify';
|
|
4
3
|
import { isEmpty } from 'lodash';
|
|
5
4
|
import React, { useEffect, useMemo } from 'react';
|
|
6
5
|
import useWidgetSize, { useFormContext } from '../../../../theme/hooks';
|
|
@@ -19,6 +18,7 @@ import ObjectPropertyInput from './FormFieldTypes/relatedObjectFiles/ObjectPrope
|
|
|
19
18
|
import UserProperty from './FormFieldTypes/UserProperty';
|
|
20
19
|
import FormSections from './FormSections';
|
|
21
20
|
import { entryIsVisible, fetchCollectionData, filterEmptySections, getEntryId, getFieldDefinition, isAddressProperty, isOptionEqualToValue, updateCriteriaInputs, } from './utils';
|
|
21
|
+
import HtmlView from './HtmlView';
|
|
22
22
|
function getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors, validation) {
|
|
23
23
|
return {
|
|
24
24
|
inputId: entryId,
|
|
@@ -70,9 +70,7 @@ export function RecursiveEntryRenderer(props) {
|
|
|
70
70
|
return null;
|
|
71
71
|
}
|
|
72
72
|
if (entry.type === 'content') {
|
|
73
|
-
return
|
|
74
|
-
fontFamily: 'Roboto, Helvetica, Arial, sans-serif',
|
|
75
|
-
} }));
|
|
73
|
+
return React.createElement(HtmlView, { value: entry.html });
|
|
76
74
|
}
|
|
77
75
|
else if ((entry.type === 'input' || entry.type === 'readonlyField' || entry.type === 'inputField') &&
|
|
78
76
|
fieldDefinition) {
|
|
@@ -87,9 +85,7 @@ export function RecursiveEntryRenderer(props) {
|
|
|
87
85
|
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
88
86
|
React.createElement(ObjectPropertyInput, { relatedObjectId: !fieldDefinition.objectId ? display?.relatedObjectId : fieldDefinition.objectId, fieldDefinition: fieldDefinition, id: entryId, mode: display?.mode || 'default', error: !!errors?.[entryId], displayOption: display?.relatedObjectDisplay || 'dialogBox', initialValue: fieldValue, readOnly: entry.type === 'readonlyField', filter: 'criteria' in validation && validation.criteria
|
|
89
87
|
? updateCriteriaInputs(validation.criteria, getValues(), userAccount, instance)
|
|
90
|
-
:
|
|
91
|
-
? updateCriteriaInputs(entry.display?.criteria, getValues(), userAccount, instance)
|
|
92
|
-
: undefined, sortBy: typeof display?.defaultValue === 'object' && 'sortBy' in display.defaultValue
|
|
88
|
+
: undefined, sortBy: typeof display?.defaultValue === 'object' && 'sortBy' in display.defaultValue
|
|
93
89
|
? display?.defaultValue.sortBy
|
|
94
90
|
: undefined, orderBy: typeof display?.defaultValue === 'object' && 'orderBy' in display.defaultValue
|
|
95
91
|
? display?.defaultValue.orderBy
|
|
@@ -138,7 +134,7 @@ export function RecursiveEntryRenderer(props) {
|
|
|
138
134
|
}
|
|
139
135
|
else if (fieldDefinition.type === 'document' || fieldDefinition.type === 'file') {
|
|
140
136
|
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
141
|
-
React.createElement(Document, { id: entryId,
|
|
137
|
+
React.createElement(Document, { id: entryId, error: !!errors?.[entryId], value: fieldValue, canUpdateProperty: !(entry.type === 'readonlyField'), hasDescription: !!display?.description, validate: validation })));
|
|
142
138
|
}
|
|
143
139
|
else if (fieldDefinition.type === 'criteria') {
|
|
144
140
|
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
@@ -12,10 +12,9 @@ export type FieldAddress = {
|
|
|
12
12
|
export type AccessCheck = {
|
|
13
13
|
result: boolean;
|
|
14
14
|
};
|
|
15
|
-
export type
|
|
15
|
+
export type SavedDocumentReference = {
|
|
16
16
|
id: string;
|
|
17
17
|
name: string;
|
|
18
|
-
unsaved?: boolean;
|
|
19
18
|
};
|
|
20
19
|
export type Document = {
|
|
21
20
|
id: string;
|
|
@@ -113,7 +112,3 @@ export type InstanceLink = {
|
|
|
113
112
|
id: string;
|
|
114
113
|
objectId: string;
|
|
115
114
|
};
|
|
116
|
-
export type FileUploadBatchResult = {
|
|
117
|
-
errorMessage?: string;
|
|
118
|
-
successfulUploads: DocumentReference[];
|
|
119
|
-
};
|
|
@@ -4,7 +4,7 @@ import { LocalDateTime } from '@js-joda/core';
|
|
|
4
4
|
import { FieldErrors, FieldValues } from 'react-hook-form';
|
|
5
5
|
import { ObjectProperty } from '../../../../types';
|
|
6
6
|
import { AutocompleteOption } from '../../../core';
|
|
7
|
-
import {
|
|
7
|
+
import { InstanceLink, SavedDocumentReference } from './types';
|
|
8
8
|
export declare const scrollIntoViewWithOffset: (el: HTMLElement, offset: number, container?: HTMLElement) => void;
|
|
9
9
|
export declare const normalizeDateTime: (dateTime: LocalDateTime) => string;
|
|
10
10
|
export declare function isAddressProperty(key: string): boolean;
|
|
@@ -49,13 +49,16 @@ export declare const convertPropertiesToParams: (object: Obj) => InputParameter[
|
|
|
49
49
|
export declare function getUnnestedEntries(entries: FormEntry[]): FormEntry[];
|
|
50
50
|
export declare const isEmptyWithDefault: (fieldValue: unknown, entry: InputParameterReference | InputField, instance: Record<string, unknown> | object) => boolean | "" | 0 | undefined;
|
|
51
51
|
export declare const docProperties: Property[];
|
|
52
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Upload files using the POST /files endpoint for sys__file objects
|
|
54
|
+
*/
|
|
55
|
+
export declare const uploadFiles: (files: (File | SavedDocumentReference)[], apiServices: ApiServices, actionId?: string, metadata?: Record<string, string>, linkTo?: InstanceLink) => Promise<SavedDocumentReference[]>;
|
|
53
56
|
/**
|
|
54
57
|
* Creates file links for uploaded files by calling the objects endpoint with sys__fileLink
|
|
55
58
|
* This is used after instance creation when the instance ID becomes available
|
|
56
59
|
*/
|
|
57
|
-
export declare const createFileLinks: (
|
|
58
|
-
export declare const uploadDocuments: (files: (File |
|
|
60
|
+
export declare const createFileLinks: (files: SavedDocumentReference[], linkedInstance: InstanceLink, apiServices: ApiServices) => Promise<void>;
|
|
61
|
+
export declare const uploadDocuments: (files: (File | SavedDocumentReference)[], metadata: Record<string, string>, apiServices: ApiServices, instanceId: string, objectId: string) => Promise<SavedDocumentReference[]>;
|
|
59
62
|
export declare const deleteDocuments: (submittedFields: FieldValues, requestSuccess: boolean, apiServices: ApiServices, object: Obj, instance: FieldValues, action?: Action, setSnackbarError?: React.Dispatch<React.SetStateAction<{
|
|
60
63
|
showAlert: boolean;
|
|
61
64
|
message?: string;
|
|
@@ -81,7 +84,6 @@ export declare function formatSubmission(submission: FieldValues, apiServices?:
|
|
|
81
84
|
}>>, associatedObject?: {
|
|
82
85
|
instanceId: string;
|
|
83
86
|
propertyId: string;
|
|
84
|
-
objectId?: string;
|
|
85
87
|
}, parameters?: InputParameter[]): Promise<FieldValues>;
|
|
86
88
|
export declare function filterEmptySections(entry: Sections | Columns, instance?: FieldValues, formData?: FieldValues): Sections | Columns | null;
|
|
87
89
|
export declare function assignIdsToSectionsAndRichText(entries: FormEntry[], object: Obj, parameters?: InputParameter[]): FormEntry[];
|