@kitconcept/core 2.0.0-alpha.4 → 2.0.0-alpha.6

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.
@@ -0,0 +1,307 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { useDispatch, useSelector, shallowEqual } from 'react-redux';
3
+ import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
4
+ import {
5
+ Button,
6
+ Modal,
7
+ Table,
8
+ Input,
9
+ Dimmer,
10
+ Progress,
11
+ } from 'semantic-ui-react';
12
+ import cx from 'classnames';
13
+ import filesize from 'filesize';
14
+ import { readAsDataURL } from 'promise-file-reader';
15
+
16
+ import { createContent } from '@plone/volto/actions/content/content';
17
+ import { usePrevious } from '@plone/volto/helpers/Utils/usePrevious';
18
+ import { validateFileUploadSize } from '@plone/volto/helpers/FormValidation/FormValidation';
19
+ import Icon from '@plone/volto/components/theme/Icon/Icon';
20
+ import uploadSVG from '@plone/volto/icons/upload.svg';
21
+ import clearSVG from '@plone/volto/icons/clear.svg';
22
+ import FormattedRelativeDate from '@plone/volto/components/theme/FormattedDate/FormattedRelativeDate';
23
+ import Image from '@plone/volto/components/theme/Image/Image';
24
+
25
+ const SUBREQUEST = 'batch-upload';
26
+
27
+ const messages = defineMessages({
28
+ cancel: {
29
+ id: 'Cancel',
30
+ defaultMessage: 'Cancel',
31
+ },
32
+ upload: {
33
+ id: '{count, plural, one {Upload {count} file} other {Upload {count} files}}',
34
+ defaultMessage:
35
+ '{count, plural, one {Upload {count} file} other {Upload {count} files}}',
36
+ },
37
+ filesUploaded: {
38
+ id: 'Files uploaded: {uploadedFiles}',
39
+ defaultMessage: 'Files uploaded: {uploadedFiles}',
40
+ },
41
+ totalFilesToUpload: {
42
+ id: 'Total files to upload: {totalFiles}',
43
+ defaultMessage: 'Total files to upload: {totalFiles}',
44
+ },
45
+ });
46
+
47
+ const DropZoneContent = (props) => {
48
+ const { onOk, onCancel, pathname, children } = props;
49
+ const [isDragOver, setIsDragOver] = useState(false);
50
+ const [showModal, setShowModal] = useState(false);
51
+ const [droppedFiles, setDroppedFiles] = useState([]);
52
+ const [totalFiles, setTotalFiles] = useState(0);
53
+
54
+ const intl = useIntl();
55
+ const dispatch = useDispatch();
56
+
57
+ const request = useSelector(
58
+ (state) => state.content.subrequests?.[SUBREQUEST] || {},
59
+ shallowEqual,
60
+ );
61
+
62
+ const uploadedFiles = useSelector((state) => state.content.uploadedFiles);
63
+ const prevrequestloading = usePrevious(request.loading);
64
+
65
+ useEffect(() => {
66
+ if (prevrequestloading && request.loaded) {
67
+ onOk();
68
+ setDroppedFiles([]);
69
+ }
70
+ }, [prevrequestloading, request.loaded, onOk]);
71
+
72
+ const handleDragEnter = (e) => {
73
+ e.preventDefault();
74
+ e.stopPropagation();
75
+ setIsDragOver(true);
76
+ };
77
+
78
+ const handleDragLeave = (e) => {
79
+ e.preventDefault();
80
+ e.stopPropagation();
81
+ if (!e.currentTarget.contains(e.relatedTarget)) {
82
+ setIsDragOver(false);
83
+ }
84
+ };
85
+
86
+ const handleDragOver = (e) => {
87
+ e.preventDefault();
88
+ e.stopPropagation();
89
+ };
90
+
91
+ const onDrop = async (e) => {
92
+ setIsDragOver(false);
93
+ const newFiles = Array.from(e.dataTransfer.files);
94
+ const validFiles = [];
95
+ for (let i = 0; i < newFiles.length; i++) {
96
+ if (validateFileUploadSize(newFiles[i], intl.formatMessage)) {
97
+ await readAsDataURL(newFiles[i]).then((data) => {
98
+ const fields = data.match(/^data:(.*);(.*),(.*)$/);
99
+ newFiles[i].preview = fields[0];
100
+ });
101
+ validFiles.push(newFiles[i]);
102
+ }
103
+ }
104
+ setDroppedFiles(droppedFiles.concat(validFiles));
105
+ setTotalFiles(validFiles.length);
106
+ setShowModal(true);
107
+ };
108
+
109
+ const handleCloseModal = () => {
110
+ setShowModal(false);
111
+ onCancel();
112
+ setDroppedFiles([]);
113
+ setTotalFiles(0);
114
+ };
115
+
116
+ const onSubmit = () => {
117
+ Promise.all(droppedFiles.map((file) => readAsDataURL(file))).then(
118
+ (dataUrls) => {
119
+ dispatch(
120
+ createContent(
121
+ pathname,
122
+ droppedFiles.map((file, index) => {
123
+ const fields = dataUrls[index].match(/^data:(.*);(.*),(.*)$/);
124
+ const image = fields[1].split('/')[0] === 'image';
125
+ return {
126
+ '@type': image ? 'Image' : 'File',
127
+ title: file.name,
128
+ [image ? 'image' : 'file']: {
129
+ data: fields[3],
130
+ encoding: fields[2],
131
+ 'content-type': fields[1],
132
+ filename: file.name,
133
+ },
134
+ };
135
+ }),
136
+ SUBREQUEST,
137
+ ),
138
+ );
139
+ },
140
+ );
141
+ handleCloseModal();
142
+ };
143
+ const onRemoveFile = (index) => {
144
+ const updatedFiles = droppedFiles.filter((file, i) => i !== index);
145
+ setDroppedFiles(updatedFiles);
146
+ setTotalFiles(updatedFiles.length);
147
+ };
148
+
149
+ const onChangeFileName = (e, index) => {
150
+ let copyOfFiles = [...droppedFiles];
151
+ let originalFile = droppedFiles[index];
152
+ let newFile = new File([originalFile], e.target.value, {
153
+ type: originalFile.type,
154
+ });
155
+
156
+ newFile.preview = originalFile.preview;
157
+ newFile.path = e.target.value;
158
+ copyOfFiles[index] = newFile;
159
+ setDroppedFiles(copyOfFiles);
160
+ };
161
+
162
+ return (
163
+ <>
164
+ <div
165
+ className={cx('contents-dropzone', {
166
+ 'drag-over': isDragOver,
167
+ 'drag-inactive': !isDragOver,
168
+ })}
169
+ onDragEnter={handleDragEnter}
170
+ onDragLeave={handleDragLeave}
171
+ onDragOver={handleDragOver}
172
+ onDrop={onDrop}
173
+ >
174
+ {children}
175
+ {isDragOver && (
176
+ <div className="dropzone-overlay">
177
+ <div className="dropzone-content">
178
+ <Icon name={uploadSVG} size="48px" />
179
+ <h3>Drop files here to upload</h3>
180
+ <p>Release to add file(s) to this folder</p>
181
+ </div>
182
+ </div>
183
+ )}
184
+ </div>
185
+ <Modal
186
+ open={showModal}
187
+ onClose={handleCloseModal}
188
+ className="contents-upload-modal"
189
+ >
190
+ <Modal.Header>Upload Files ({droppedFiles.length})</Modal.Header>
191
+ <Dimmer active={request.loading}>
192
+ <div className="progress-container">
193
+ <Progress
194
+ className="progress-bar"
195
+ value={uploadedFiles}
196
+ total={totalFiles}
197
+ >
198
+ {intl.formatMessage(messages.filesUploaded, {
199
+ uploadedFiles,
200
+ })}
201
+ <br />
202
+ {intl.formatMessage(messages.totalFilesToUpload, {
203
+ totalFiles,
204
+ })}
205
+ </Progress>
206
+ </div>
207
+ </Dimmer>
208
+ <Modal.Content>
209
+ {droppedFiles.length > 0 && (
210
+ <Table compact singleLine>
211
+ <Table.Header>
212
+ <Table.Row>
213
+ <Table.HeaderCell width={8}>
214
+ <FormattedMessage id="Filename" defaultMessage="Filename" />
215
+ </Table.HeaderCell>
216
+ <Table.HeaderCell width={4}>
217
+ <FormattedMessage
218
+ id="Last modified"
219
+ defaultMessage="Last modified"
220
+ />
221
+ </Table.HeaderCell>
222
+ <Table.HeaderCell width={4}>
223
+ <FormattedMessage
224
+ id="File size"
225
+ defaultMessage="File size"
226
+ />
227
+ </Table.HeaderCell>
228
+ <Table.HeaderCell width={4}>
229
+ <FormattedMessage id="Preview" defaultMessage="Preview" />
230
+ </Table.HeaderCell>
231
+ <Table.HeaderCell />
232
+ </Table.Row>
233
+ </Table.Header>
234
+ <Table.Body>
235
+ {droppedFiles.map((file, index) => (
236
+ <Table.Row className="upload-row" key={index}>
237
+ <Table.Cell>
238
+ <Input
239
+ className="file-name"
240
+ value={file.name}
241
+ onChange={(e) => onChangeFileName(e, index)}
242
+ />
243
+ </Table.Cell>
244
+ <Table.Cell>
245
+ {file.lastModifiedDate && (
246
+ <FormattedRelativeDate date={file.lastModifiedDate} />
247
+ )}
248
+ </Table.Cell>
249
+ <Table.Cell>{filesize(file.size, { round: 0 })}</Table.Cell>
250
+ <Table.Cell>
251
+ {file.type.split('/')[0] === 'image' && (
252
+ <Image
253
+ src={file.preview}
254
+ height={60}
255
+ className="ui image"
256
+ />
257
+ )}
258
+ </Table.Cell>
259
+ <Table.Cell>
260
+ <Icon
261
+ name={clearSVG}
262
+ size="24px"
263
+ onClick={() => onRemoveFile(index)}
264
+ />
265
+ </Table.Cell>
266
+ </Table.Row>
267
+ ))}
268
+ </Table.Body>
269
+ </Table>
270
+ )}
271
+ </Modal.Content>
272
+ <Modal.Actions>
273
+ {droppedFiles.length > 0 && (
274
+ <Button
275
+ basic
276
+ circular
277
+ primary
278
+ floated="right"
279
+ icon="arrow right"
280
+ aria-label={intl.formatMessage(messages.upload, {
281
+ count: droppedFiles.length,
282
+ })}
283
+ onClick={onSubmit}
284
+ title={intl.formatMessage(messages.upload, {
285
+ count: droppedFiles.length,
286
+ })}
287
+ size="big"
288
+ />
289
+ )}
290
+ <Button
291
+ basic
292
+ circular
293
+ secondary
294
+ icon="remove"
295
+ aria-label={intl.formatMessage(messages.cancel)}
296
+ title={intl.formatMessage(messages.cancel)}
297
+ floated="right"
298
+ size="big"
299
+ onClick={handleCloseModal}
300
+ />
301
+ </Modal.Actions>
302
+ </Modal>
303
+ </>
304
+ );
305
+ };
306
+
307
+ export default DropZoneContent;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * OVERRIDE Contents.jsx
3
+ * REASON: Add component DropzoneContent to enable drag and drop upload.
4
+ * FILE: https://github.com/plone/volto/blob/829f0a61b80adb0d2a637f0160c1e9b00931114f/packages/volto/src/components/manage/Contents/Contents.jsx
5
+ * FILE VERSION: Volto 18.29.1
6
+ * PULL REQUEST: https://github.com/kitconcept/kitconcept-core/pull/71
7
+ * TICKET: https://gitlab.kitconcept.io/kitconcept/distribution-kitconcept-intranet/-/issues/6
8
+ * DEVELOPER: @Tishasoumya-02
9
+ * CHANGELOG:
10
+ * - Add component DropzoneContent to enable drag and drop upload (#6) @Tishasoumya-02
11
+ */
12
+
13
+ /**
14
+ * Contents component.
15
+ * @module components/manage/Contents/Contents
16
+ */
17
+
18
+ import Contents from '../../../../../components/Contents/Contents';
19
+
20
+ export default Contents;
@@ -1,2 +1,3 @@
1
1
  // This theme extends the volto-light-theme
2
2
  @import 'components/version_overview';
3
+ @import 'components/dragDropFolderContent.scss';
@@ -0,0 +1,62 @@
1
+ .contents-dropzone {
2
+ position: relative;
3
+
4
+ &.drag-over {
5
+ overflow: hidden;
6
+ }
7
+ }
8
+
9
+ .dropzone-overlay {
10
+ position: absolute;
11
+ z-index: 10;
12
+ display: flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ border: 3px dashed black;
16
+ animation: fadeIn 0.3s ease-in;
17
+ backdrop-filter: blur(2px);
18
+ inset: 0;
19
+ }
20
+
21
+ .dropzone-content {
22
+ animation: scaleIn 0.3s ease-out;
23
+ color: black;
24
+ text-align: center;
25
+
26
+ h3 {
27
+ margin-top: 16px;
28
+ font-size: 24px;
29
+ font-weight: 600;
30
+ }
31
+
32
+ p {
33
+ font-size: 18px;
34
+ }
35
+
36
+ h3,
37
+ p {
38
+ color: black;
39
+ }
40
+ }
41
+
42
+ @keyframes fadeIn {
43
+ from {
44
+ opacity: 0;
45
+ }
46
+
47
+ to {
48
+ opacity: 1;
49
+ }
50
+ }
51
+
52
+ @keyframes scaleIn {
53
+ from {
54
+ opacity: 0;
55
+ transform: scale(0.9);
56
+ }
57
+
58
+ to {
59
+ opacity: 1;
60
+ transform: scale(1);
61
+ }
62
+ }