Skip to content

Commit 00de041

Browse files
committed
refactor: attachment components with page instructions
1 parent e81ce34 commit 00de041

File tree

5 files changed

+639
-257
lines changed

5 files changed

+639
-257
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
export interface ResponseProps {
2+
ID: string;
3+
extension: string;
4+
createDateTime?: Date | string | number;
5+
createUser?: string;
6+
name: string;
7+
}
8+
9+
export interface AttachmentActions {
10+
rel: string;
11+
href: string;
12+
title: string;
13+
type: string;
14+
}
15+
16+
export interface AttachmentLinks {
17+
delete: AttachmentActions;
18+
download: AttachmentActions;
19+
edit: AttachmentActions;
20+
}
21+
export interface FileObject extends File {
22+
icon?: string;
23+
ID: string;
24+
fileName: string;
25+
category: string;
26+
responseType: string;
27+
fileType: string;
28+
mimeType: string;
29+
extension: string;
30+
thumbnail?: string;
31+
nameWithExt: string;
32+
inProgress?: boolean;
33+
progress?: number;
34+
handle: string;
35+
label: string;
36+
delete?: boolean;
37+
error?: boolean;
38+
description: string;
39+
40+
props: {
41+
icon?: string;
42+
id: string;
43+
error?: string;
44+
format?: string;
45+
name: string;
46+
thumbnail?: string;
47+
onPreview?: () => void;
48+
onDelete?: () => void;
49+
onOpen?: () => void;
50+
onEdit?: () => void;
51+
onCancel?: () => void;
52+
};
53+
responseProps: ResponseProps;
54+
value?: {
55+
filename: string;
56+
ID: string;
57+
thumbnail: string;
58+
};
59+
categoryName: string;
60+
createTime: string;
61+
createdBy: string;
62+
createdByName: string;
63+
links: AttachmentLinks;
64+
name: string;
65+
meta?: any;
66+
}
67+
68+
export interface ReduxAttachments {
69+
ID?: string;
70+
pzInsKey?: string;
71+
FileName: string;
72+
Category: string;
73+
MimeType?: string;
74+
FileExtension: string;
75+
error: string | null;
76+
localAttachment: boolean;
77+
thumbnail?: string;
78+
fileIndex?: number;
79+
instruction?: string;
80+
}
81+
82+
export interface PageInstructionOptions {
83+
allowMultiple: boolean;
84+
isMultiAttachmentInInlineEditTable: boolean;
85+
attachmentCount: number;
86+
insertPageInstruction: boolean;
87+
deletePageInstruction: boolean;
88+
deleteIndex: number;
89+
insertRedux: boolean;
90+
isOldAttachment: boolean;
91+
deleteRedux: boolean;
92+
}
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
import download from 'downloadjs';
2+
3+
import type { FileObject, PageInstructionOptions, ReduxAttachments } from './Attachment.types';
4+
5+
const megabyteSize = 1048576;
6+
7+
export const isContentBinary = (headers: Record<string, string>) => {
8+
return headers && headers['content-transfer-encoding'] === 'binary';
9+
};
10+
11+
export const isContentBase64 = (headers: Record<string, string>) => {
12+
return headers && headers['content-transfer-encoding'] === 'base64';
13+
};
14+
15+
export const validateMaxSize = (fileObj: Record<string, number>, maxSizeInMB: string) => {
16+
const fileSize = (fileObj['size'] / megabyteSize).toFixed(2);
17+
return parseFloat(fileSize) < parseFloat(maxSizeInMB);
18+
};
19+
20+
export const validateFileExtension = (fileObj: Record<string, string>, allowedExtensions: string) => {
21+
if (!allowedExtensions) {
22+
return true;
23+
}
24+
const allowedExtensionList = allowedExtensions
25+
.toLowerCase()
26+
.split(',')
27+
.map(item => item.replaceAll('.', '').trim());
28+
const extension = fileObj['name'].split('.').pop()?.toLowerCase() || '';
29+
return allowedExtensionList.includes(extension);
30+
};
31+
32+
export const fileDownload = (data: string | Blob, fileName: string, ext: string | null, headers: Record<string, string>) => {
33+
const name = ext ? `${fileName}.${ext}` : fileName;
34+
// Temp fix: downloading EMAIl type attachment as html file
35+
if (ext === 'html') {
36+
download(isContentBase64(headers) ? atob(data as string) : data, name, 'text/html');
37+
} else if (isContentBinary(headers)) {
38+
download(data, name);
39+
} else {
40+
download(atob(data as string), name);
41+
}
42+
};
43+
44+
export const fileDownloadVar = (content: { data: string; headers: Record<string, string> }, type: string, name: string, extension: string) => {
45+
if (type === 'FILE' || type === undefined) {
46+
fileDownload(content.data, name, extension, content.headers);
47+
} else if (type === 'URL') {
48+
let { data } = content;
49+
if (!/^(http|https):\/\//.test(data)) {
50+
data = `//${data}`;
51+
}
52+
window.open(content.data, '_blank');
53+
} else if (type === 'EMAIL') {
54+
// Temp Fix: for EMAIL type attachment
55+
fileDownload(content.data, name, 'html', content.headers);
56+
}
57+
};
58+
59+
export const getMappedValue = (value: string): string => {
60+
return PCore.getEnvironmentInfo().getKeyMapping(value) ?? value;
61+
};
62+
63+
const generateInstructions = (
64+
files: FileObject[],
65+
pConn: typeof PConnect,
66+
attachmentsInModal: ReduxAttachments[] | Pick<ReduxAttachments, 'instruction' | 'fileIndex'>[],
67+
options: {
68+
allowMultiple: boolean;
69+
isMultiAttachmentInInlineEditTable: boolean;
70+
attachmentCount: number;
71+
insertPageInstruction: boolean;
72+
deletePageInstruction: boolean;
73+
deleteIndex: number;
74+
}
75+
) => {
76+
const { allowMultiple, isMultiAttachmentInInlineEditTable, attachmentCount, insertPageInstruction, deletePageInstruction, deleteIndex } = options;
77+
const transformedAttachments: ReduxAttachments[] = [];
78+
let valueRef = pConn.getStateProps().value;
79+
valueRef = valueRef?.indexOf('.') === 0 ? valueRef.substring(1) : valueRef;
80+
const uniqueKey = getMappedValue('pzInsKey');
81+
files.forEach((file, index) => {
82+
const filename = file.value?.filename || file.props?.name || '';
83+
const payload = {
84+
[uniqueKey]: file.value?.ID || file.props?.id,
85+
FileName: filename,
86+
Category: '',
87+
// MimeType: getMimeTypeFromFile(filename),
88+
FileExtension: filename.split('.').pop() ?? filename,
89+
error: file.props?.error || null,
90+
localAttachment: true,
91+
thumbnail: file.value?.thumbnail
92+
};
93+
transformedAttachments.push(payload);
94+
if (payload.error) {
95+
return; // Don't process page instructions for error files, skip current iteration
96+
}
97+
if (allowMultiple) {
98+
if (isMultiAttachmentInInlineEditTable) {
99+
if (insertPageInstruction) {
100+
attachmentsInModal.push({ ...payload, instruction: 'insert' } as any);
101+
} else if (deletePageInstruction) {
102+
(attachmentsInModal as Pick<ReduxAttachments, 'instruction' | 'fileIndex'>[]).push({
103+
instruction: 'delete',
104+
fileIndex: deleteIndex
105+
});
106+
}
107+
} else if (insertPageInstruction) {
108+
pConn.getListActions().insert({ ID: payload[uniqueKey] }, attachmentCount + index, undefined, {
109+
skipStateUpdate: true
110+
});
111+
} else if (deletePageInstruction) {
112+
pConn.getListActions().deleteEntry(deleteIndex, undefined, { skipStateUpdate: true });
113+
}
114+
} else if (insertPageInstruction) {
115+
pConn.getListActions().replacePage(`.${valueRef}`, { ID: payload[uniqueKey] }, { skipStateUpdate: true });
116+
} else if (deletePageInstruction) {
117+
pConn.getListActions().deletePage(`.${valueRef}`, { skipStateUpdate: true });
118+
}
119+
});
120+
return transformedAttachments;
121+
};
122+
123+
export const updateReduxState = (
124+
transformedAttachments: ReduxAttachments[],
125+
pConn: typeof PConnect,
126+
valueRef: string,
127+
options: PageInstructionOptions
128+
) => {
129+
const { allowMultiple, isOldAttachment, insertRedux, deleteRedux } = options;
130+
let deleteIndex = -1;
131+
132+
if (allowMultiple || isOldAttachment) {
133+
transformedAttachments.forEach(attachment => {
134+
const key = isOldAttachment ? `${valueRef}.pxResults` : valueRef;
135+
const existingAttachments: ReduxAttachments[] = PCore.getStoreValue(`.${key}`, pConn.getPageReference(), pConn.getContextName()) || [];
136+
137+
if (insertRedux) {
138+
const actionPayLoad = {
139+
type: 'LIST_ACTION',
140+
payload: {
141+
instruction: 'INSERT',
142+
context: pConn.getContextName(),
143+
referenceList: `${pConn.getPageReference()}.${key}`,
144+
listIndex: existingAttachments.length,
145+
content: attachment
146+
}
147+
};
148+
PCore.getStore()?.dispatch(actionPayLoad);
149+
} else if (deleteRedux) {
150+
const uniqueKey = getMappedValue('pzInsKey');
151+
deleteIndex = existingAttachments.findIndex(
152+
existingAttachment =>
153+
existingAttachment[uniqueKey as keyof ReduxAttachments] === transformedAttachments[0][uniqueKey as keyof ReduxAttachments]
154+
);
155+
const actionPayLoad = {
156+
type: 'LIST_ACTION',
157+
payload: {
158+
instruction: 'DELETE',
159+
context: pConn.getContextName(),
160+
referenceList: `${pConn.getPageReference()}.${key}`,
161+
listIndex: deleteIndex
162+
}
163+
};
164+
PCore.getStore()?.dispatch(actionPayLoad);
165+
}
166+
});
167+
} else if (insertRedux) {
168+
const actionPayLoad = {
169+
type: 'LIST_ACTION',
170+
payload: {
171+
instruction: 'REPLACE',
172+
context: pConn.getContextName(),
173+
referenceList: `${pConn.getPageReference()}.${valueRef}`,
174+
content: transformedAttachments[0]
175+
}
176+
};
177+
PCore.getStore()?.dispatch(actionPayLoad);
178+
} else if (deleteRedux) {
179+
const actionPayLoad = {
180+
type: 'LIST_ACTION',
181+
payload: {
182+
instruction: 'DELETEPAGE',
183+
context: pConn.getContextName(),
184+
referenceList: `${pConn.getPageReference()}.${valueRef}`
185+
}
186+
};
187+
PCore.getStore()?.dispatch(actionPayLoad);
188+
}
189+
};
190+
191+
export const insertAttachments = (
192+
files: FileObject[],
193+
pConn: typeof PConnect,
194+
attachmentsInModal: ReduxAttachments[],
195+
options: PageInstructionOptions
196+
) => {
197+
const { isMultiAttachmentInInlineEditTable } = options;
198+
let valueRef = pConn.getStateProps().value;
199+
valueRef = valueRef?.indexOf('.') === 0 ? valueRef.substring(1) : valueRef;
200+
const transformedAttachments = generateInstructions(files, pConn, attachmentsInModal, {
201+
...options,
202+
insertPageInstruction: true
203+
});
204+
205+
if (isMultiAttachmentInInlineEditTable) {
206+
return; // For attachments within modal, redux update is not necessary yet, as modal isn't submitted at this stage
207+
}
208+
updateReduxState(transformedAttachments, pConn, valueRef, { ...options, insertRedux: true });
209+
};
210+
211+
export const deleteAttachments = (
212+
files: FileObject[],
213+
pConn: typeof PConnect,
214+
attachmentsInModal: Pick<ReduxAttachments, 'instruction' | 'fileIndex'>[],
215+
options: PageInstructionOptions
216+
) => {
217+
const { isMultiAttachmentInInlineEditTable } = options;
218+
let valueRef = pConn.getStateProps().value;
219+
valueRef = valueRef?.indexOf('.') === 0 ? valueRef.substring(1) : valueRef;
220+
const transformedAttachments = generateInstructions(files, pConn, attachmentsInModal, {
221+
...options,
222+
deletePageInstruction: true
223+
});
224+
225+
if (isMultiAttachmentInInlineEditTable) {
226+
return; // For attachments within modal, redux update is not necessary yet, as modal isn't submitted at this stage
227+
}
228+
updateReduxState(transformedAttachments, pConn, valueRef, { ...options, deleteRedux: true });
229+
};
230+
231+
export const clearFieldErrorMessages = (pConn: typeof PConnect) => {
232+
const fieldName = pConn.getStateProps().value;
233+
PCore.getMessageManager().clearMessages({
234+
type: PCore.getConstants().MESSAGES.MESSAGES_TYPE_ERROR,
235+
property: fieldName,
236+
pageReference: pConn.getPageReference(),
237+
context: pConn.getContextName()
238+
});
239+
};
240+
241+
export const onFileDownload = (responseProps, context) => {
242+
const { ID, name, extension, type, category, responseType } = responseProps;
243+
244+
if (category !== 'pxDocument') {
245+
(
246+
PCore.getAttachmentUtils().downloadAttachment(ID, context, responseType) as Promise<{
247+
data: string;
248+
headers: Record<string, string>;
249+
}>
250+
)
251+
.then(content => {
252+
fileDownloadVar(content, type, name, extension);
253+
})
254+
255+
.catch(console.error);
256+
} else {
257+
(
258+
PCore.getAttachmentUtils().downloadDocument(ID, context) as Promise<{
259+
data: string;
260+
headers: Record<string, string>;
261+
}>
262+
)
263+
.then(content => {
264+
fileDownloadVar(content, type, name, extension);
265+
})
266+
267+
.catch(console.error);
268+
}
269+
};
270+
271+
// Prepares new structure as per Cosmos component
272+
export const transformAttachments = attachments => {
273+
const transformedFiles = [...attachments];
274+
let deleteIndex = -1;
275+
transformedFiles.forEach(attachment => {
276+
attachment.props.id = attachment.responseProps.ID;
277+
attachment.props.format = attachment.props.name.split('.').pop();
278+
if (attachment.props.error) {
279+
attachment.responseProps.deleteIndex = deleteIndex;
280+
} else {
281+
deleteIndex += 1;
282+
attachment.responseProps.deleteIndex = deleteIndex;
283+
}
284+
});
285+
286+
return transformedFiles;
287+
};

0 commit comments

Comments
 (0)