@lowdefy/plugin-aws 4.1.0 → 4.2.1

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,70 @@
1
+ /*
2
+ Copyright 2020-2024 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import React, { useEffect } from 'react';
16
+ import { Upload } from 'antd';
17
+ import { blockDefaultProps } from '@lowdefy/block-utils';
18
+ const downloadFile = async ({ file, methods })=>{
19
+ const s3DownloadPolicy = await methods.triggerEvent({
20
+ name: '__getS3DownloadPolicy',
21
+ event: {
22
+ file
23
+ }
24
+ });
25
+ window.open(s3DownloadPolicy?.responses?.__getS3DownloadPolicy?.response?.[0]);
26
+ };
27
+ const S3Download = ({ blockId, methods, properties })=>{
28
+ useEffect(()=>{
29
+ methods.registerEvent({
30
+ name: '__getS3DownloadPolicy',
31
+ actions: [
32
+ {
33
+ id: '__getS3DownloadPolicy',
34
+ type: 'Request',
35
+ params: [
36
+ properties.s3GetPolicyRequestId
37
+ ]
38
+ }
39
+ ]
40
+ });
41
+ }, []);
42
+ return /*#__PURE__*/ React.createElement(Upload, {
43
+ id: blockId,
44
+ className: methods.makeCssClass([
45
+ properties.style
46
+ ]),
47
+ fileList: properties.fileList ?? [],
48
+ onPreview: async (file)=>await downloadFile({
49
+ file,
50
+ methods
51
+ }),
52
+ showUploadList: {
53
+ showRemoveIcon: false,
54
+ showDownloadIcon: true
55
+ },
56
+ onDownload: async (file)=>await downloadFile({
57
+ file,
58
+ methods
59
+ })
60
+ });
61
+ };
62
+ S3Download.defaultProps = blockDefaultProps;
63
+ S3Download.meta = {
64
+ category: 'display',
65
+ icons: [],
66
+ styles: [
67
+ 'blocks/S3Download/style.less'
68
+ ]
69
+ };
70
+ export default S3Download;
@@ -0,0 +1,85 @@
1
+ {
2
+ "type": "object",
3
+ "properties": {
4
+ "type": "object",
5
+ "required": ["s3GetPolicyRequestId"],
6
+ "properties": {
7
+ "fileList": {
8
+ "type": "array",
9
+ "description": "List of files to be downloaded. If files were uploaded using an S3Upload block, the fileList value can just be mapped to this field.",
10
+ "items": {
11
+ "type": "object",
12
+ "required": ["key"],
13
+ "properties": {
14
+ "key": {
15
+ "type": "string",
16
+ "description": "S3 file key."
17
+ },
18
+ "lastModified": {
19
+ "type": "string",
20
+ "description": "File last modified date."
21
+ },
22
+ "name": {
23
+ "type": "string",
24
+ "description": "File name."
25
+ },
26
+ "size": {
27
+ "type": "number",
28
+ "description": "File size in bytes."
29
+ },
30
+ "type": {
31
+ "type": "string",
32
+ "description": "File MIME type."
33
+ }
34
+ }
35
+ }
36
+ },
37
+ "s3GetPolicyRequestId": {
38
+ "type": "string",
39
+ "description": "Id of a request of type s3GetPolicyRequestId that defines to which S3 bucket and what file should be downloaded.",
40
+ "docs": {
41
+ "displayType": "manual",
42
+ "block": {
43
+ "id": "block_properties_s3GetPolicyRequestId",
44
+ "layout": { "_global": "settings_input_layout" },
45
+ "type": "Label",
46
+ "required": true,
47
+ "properties": {
48
+ "title": "s3GetPolicyRequestId",
49
+ "span": 8,
50
+ "align": "right"
51
+ },
52
+ "blocks": [
53
+ {
54
+ "id": "block_properties_s3GetPolicyRequestId_text",
55
+ "type": "Markdown",
56
+ "style": {
57
+ "color": "#8c8c8c"
58
+ },
59
+ "properties": {
60
+ "content": "Id of a request of type AwsS3PresignedGetPolicy that defines to which S3 bucket and what file should be downloaded."
61
+ }
62
+ }
63
+ ]
64
+ }
65
+ }
66
+ },
67
+ "style": {
68
+ "type": "object",
69
+ "description": "Css style object to applied to download component.",
70
+ "docs": {
71
+ "displayType": "yaml"
72
+ }
73
+ }
74
+ }
75
+ },
76
+ "events": {
77
+ "type": "object",
78
+ "properties": {
79
+ "onChange": {
80
+ "type": "array",
81
+ "description": "Triggered when the upload state is changing."
82
+ }
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,17 @@
1
+ /*
2
+ Copyright 2020-2024 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ @import 'antd/lib/upload/style/index.less';
@@ -12,146 +12,21 @@
12
12
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
- */ import React, { useEffect, useState } from 'react';
15
+ */ import React, { useEffect } from 'react';
16
16
  import { blockDefaultProps } from '@lowdefy/block-utils';
17
- import { get } from '@lowdefy/helpers';
18
17
  import { Button } from '@lowdefy/blocks-antd/blocks';
19
18
  import { Upload } from 'antd';
20
- const makeFileValue = (file, s3Parameters)=>{
21
- const { lastModified, name, percent, size, status, type, uid } = file;
22
- const { bucket, key } = get(s3Parameters, uid, {
23
- default: {}
24
- });
25
- return {
26
- bucket,
27
- key,
28
- lastModified,
29
- name,
30
- percent,
31
- size,
32
- status,
33
- type,
34
- uid
35
- };
36
- };
37
- const makeOnChangeValue = (s3Parameters, changeEvent)=>{
38
- const { file, fileList } = changeEvent;
39
- return {
40
- file: makeFileValue(file, s3Parameters),
41
- fileList: fileList.map((fl)=>makeFileValue(fl, s3Parameters))
42
- };
43
- };
44
- const getDisabled = ({ properties, value })=>{
45
- if (properties.disabled) return true;
46
- return properties.singleFile && value && (value.fileList || []).length >= 1;
47
- };
48
- const getCustomRequest = ({ methods, setS3Parameters })=>async ({ file, onError, onProgress, onSuccess })=>{
49
- let meta;
50
- try {
51
- const { name, size, type, uid } = file;
52
- const s3PostPolicyResponse = await methods.triggerEvent({
53
- name: '__getS3PostPolicy',
54
- event: {
55
- filename: name,
56
- size,
57
- type,
58
- uid
59
- }
60
- });
61
- if (s3PostPolicyResponse.success !== true) {
62
- throw new Error('S3 post policy request error.');
63
- }
64
- const { url, fields } = s3PostPolicyResponse.responses.__getS3PostPolicy.response[0];
65
- const { bucket, key } = fields;
66
- meta = {
67
- bucket,
68
- key,
69
- filename: name,
70
- size,
71
- type,
72
- uid
73
- };
74
- setS3Parameters((prevState)=>{
75
- const ret = {
76
- ...prevState
77
- };
78
- ret[uid] = {
79
- bucket,
80
- key
81
- };
82
- return ret;
83
- });
84
- // Set 20 % progress on policy is acquired else user waits to long before progress is reported
85
- onProgress({
86
- percent: 20
87
- });
88
- // Create FormData with all required fields in S3 policy
89
- const formData = new FormData();
90
- Object.keys(fields).forEach((field)=>{
91
- formData.append(field, fields[field]);
92
- });
93
- // file needs to be the last field in the form
94
- formData.append('file', file);
95
- const xhr = new XMLHttpRequest();
96
- xhr.upload.onprogress = (event)=>{
97
- if (event.lengthComputable) {
98
- onProgress({
99
- percent: event.loaded / event.total * 80 + 20
100
- });
101
- }
102
- };
103
- xhr.addEventListener('error', async (event)=>{
104
- await methods.triggerEvent({
105
- name: 'onError',
106
- event: {
107
- meta,
108
- event
109
- }
110
- });
111
- onError(event);
112
- });
113
- xhr.addEventListener('load', async (event)=>{
114
- await methods.triggerEvent({
115
- name: 'onSuccess',
116
- event: {
117
- meta,
118
- event
119
- }
120
- });
121
- onSuccess(event);
122
- });
123
- xhr.addEventListener('loadend', async (event)=>{
124
- await methods.triggerEvent({
125
- name: 'onDone',
126
- event: {
127
- meta,
128
- event
129
- }
130
- });
131
- });
132
- xhr.open('post', url);
133
- xhr.send(formData);
134
- } catch (error) {
135
- console.error(error);
136
- await methods.triggerEvent({
137
- name: 'onError',
138
- event: {
139
- meta,
140
- error
141
- }
142
- });
143
- onError(error);
144
- }
145
- };
19
+ import useFileList from '../utils/useFileList.js';
20
+ import getS3Upload from '../utils/getS3Upload.js';
146
21
  const S3UploadButtonBlock = ({ blockId, components, events, methods, properties, value })=>{
147
- // Use state here because we need to set s3 bucket and key as block value
148
- // The customRequest function does not have access to the updated block value,
149
- // so it cannot set the value directly. customRequest sets the parameters to s3Parameters state,
150
- // and then onChange updates the block value.
151
- const [s3Parameters, setS3Parameters] = useState(value);
152
- const customRequest = getCustomRequest({
22
+ const [state, loadFileList, setFileList, removeFile, setValue] = useFileList({
23
+ properties,
24
+ methods,
25
+ value
26
+ });
27
+ const s3UploadRequest = getS3Upload({
153
28
  methods,
154
- setS3Parameters
29
+ setFileList
155
30
  });
156
31
  useEffect(()=>{
157
32
  methods.setValue({
@@ -171,19 +46,25 @@ const S3UploadButtonBlock = ({ blockId, components, events, methods, properties,
171
46
  ]
172
47
  });
173
48
  }, []);
174
- const disabled = getDisabled({
175
- properties,
49
+ useEffect(()=>{
50
+ if (JSON.stringify(value) !== JSON.stringify(state)) {
51
+ setValue(value);
52
+ }
53
+ }, [
176
54
  value
177
- });
55
+ ]);
178
56
  return /*#__PURE__*/ React.createElement(Upload, {
179
- accept: properties.accept,
180
- customRequest: customRequest,
181
- disabled: disabled,
57
+ accept: properties.accept ?? '*',
58
+ beforeUpload: loadFileList,
59
+ customRequest: s3UploadRequest,
60
+ disabled: properties.disabled,
61
+ fileList: state.fileList,
182
62
  id: blockId,
63
+ maxCount: properties.maxCount,
183
64
  multiple: !properties.singleFile,
65
+ onRemove: removeFile,
184
66
  showUploadList: properties.showUploadList,
185
- onChange: (event)=>{
186
- methods.setValue(makeOnChangeValue(s3Parameters, event));
67
+ onChange: ()=>{
187
68
  methods.triggerEvent({
188
69
  name: 'onChange'
189
70
  });
@@ -193,7 +74,7 @@ const S3UploadButtonBlock = ({ blockId, components, events, methods, properties,
193
74
  components: components,
194
75
  events: events,
195
76
  properties: {
196
- disabled,
77
+ disabled: properties.disabled,
197
78
  icon: 'AiOutlineUpload',
198
79
  title: 'Upload',
199
80
  type: 'default',
@@ -24,6 +24,10 @@
24
24
  "type": "boolean",
25
25
  "description": "Disable the file input."
26
26
  },
27
+ "maxCount": {
28
+ "type": "number",
29
+ "description": "Maximum number of files that can be uploaded."
30
+ },
27
31
  "s3PostPolicyRequestId": {
28
32
  "type": "string",
29
33
  "description": "Id of a request of type AwsS3PresignedPostPolicy that defines to which S3 bucket and how the file should be uploaded.",
@@ -63,7 +67,7 @@
63
67
  "singleFile": {
64
68
  "type": "boolean",
65
69
  "default": false,
66
- "description": "Only allow a single file to be uploaded. Only one file can be selected in the prompt and the upload button is disabled after a file is uploaded."
70
+ "description": "Only allow a single file to be uploaded. Only one file can be selected in the prompt."
67
71
  }
68
72
  }
69
73
  },
@@ -0,0 +1,101 @@
1
+ /*
2
+ Copyright 2020-2024 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import React, { useEffect } from 'react';
16
+ import { Upload } from 'antd';
17
+ import { blockDefaultProps, renderHtml } from '@lowdefy/block-utils';
18
+ import useFileList from '../utils/useFileList.js';
19
+ import getS3Upload from '../utils/getS3Upload.js';
20
+ import getOnPaste from '../utils/getOnPaste.js';
21
+ const { Dragger } = Upload;
22
+ const S3UploadDragger = ({ blockId, methods, properties, value })=>{
23
+ const [state, loadFileList, setFileList, removeFile, setValue] = useFileList({
24
+ properties,
25
+ methods,
26
+ value
27
+ });
28
+ const s3UploadRequest = getS3Upload({
29
+ methods,
30
+ setFileList
31
+ });
32
+ const onPaste = getOnPaste({
33
+ s3UploadRequest,
34
+ properties
35
+ });
36
+ useEffect(()=>{
37
+ methods.registerEvent({
38
+ name: '__getS3PostPolicy',
39
+ actions: [
40
+ {
41
+ id: '__getS3PostPolicy',
42
+ type: 'Request',
43
+ params: [
44
+ properties.s3PostPolicyRequestId
45
+ ]
46
+ }
47
+ ]
48
+ });
49
+ }, []);
50
+ useEffect(()=>{
51
+ if (JSON.stringify(value) !== JSON.stringify(state)) {
52
+ setValue(value);
53
+ }
54
+ }, [
55
+ value
56
+ ]);
57
+ useEffect(()=>{
58
+ methods.registerMethod('uploadFromPaste', async ()=>{
59
+ await onPaste();
60
+ });
61
+ }, [
62
+ onPaste
63
+ ]);
64
+ return /*#__PURE__*/ React.createElement("div", {
65
+ id: blockId,
66
+ onPaste: onPaste
67
+ }, /*#__PURE__*/ React.createElement(Dragger, {
68
+ accept: properties.accept ?? '*',
69
+ beforeUpload: loadFileList,
70
+ className: methods.makeCssClass([
71
+ properties.style
72
+ ]),
73
+ customRequest: s3UploadRequest,
74
+ disabled: properties.disabled,
75
+ fileList: state.fileList,
76
+ maxCount: properties.maxCount,
77
+ multiple: !properties.singleFile,
78
+ onRemove: removeFile,
79
+ showUploadList: properties.showUploadList,
80
+ onChange: ()=>{
81
+ methods.triggerEvent({
82
+ name: 'onChange'
83
+ });
84
+ }
85
+ }, /*#__PURE__*/ React.createElement("div", {
86
+ className: "ant-upload-hint"
87
+ }, renderHtml({
88
+ html: properties.title ?? 'Click or drag to add a file.',
89
+ methods
90
+ }))));
91
+ };
92
+ S3UploadDragger.defaultProps = blockDefaultProps;
93
+ S3UploadDragger.meta = {
94
+ valueType: 'object',
95
+ category: 'input',
96
+ icons: [],
97
+ styles: [
98
+ 'blocks/S3UploadDragger/style.less'
99
+ ]
100
+ };
101
+ export default S3UploadDragger;
@@ -0,0 +1,81 @@
1
+ {
2
+ "type": "object",
3
+ "properties": {
4
+ "type": "object",
5
+ "required": ["s3PostPolicyRequestId"],
6
+ "properties": {
7
+ "title": {
8
+ "type": "string",
9
+ "description": "Title of the file input to be displayed on the draggable area."
10
+ },
11
+ "accept": {
12
+ "type": "string",
13
+ "description": "File types accepted by the input. See html file type input accept property at https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept."
14
+ },
15
+ "disabled": {
16
+ "type": "boolean",
17
+ "description": "Disable the file input."
18
+ },
19
+ "maxCount": {
20
+ "type": "number",
21
+ "description": "Maximum number of files that can be uploaded."
22
+ },
23
+ "s3PostPolicyRequestId": {
24
+ "type": "string",
25
+ "description": "Id of a request of type AwsS3PresignedPostPolicy that defines to which S3 bucket and how the file should be uploaded.",
26
+ "docs": {
27
+ "displayType": "manual",
28
+ "block": {
29
+ "id": "block_properties_s3PostPolicyRequestId",
30
+ "layout": { "_global": "settings_input_layout" },
31
+ "type": "Label",
32
+ "required": true,
33
+ "properties": {
34
+ "title": "s3PostPolicyRequestId",
35
+ "span": 8,
36
+ "align": "right"
37
+ },
38
+ "blocks": [
39
+ {
40
+ "id": "block_properties_s3PostPolicyRequestId_text",
41
+ "type": "Markdown",
42
+ "style": {
43
+ "color": "#8c8c8c"
44
+ },
45
+ "properties": {
46
+ "content": "Id of a request of type AwsS3PresignedPostPolicy that defines to which S3 bucket and how the file should be uploaded."
47
+ }
48
+ }
49
+ ]
50
+ }
51
+ }
52
+ },
53
+ "showUploadList": {
54
+ "type": "boolean",
55
+ "default": true,
56
+ "description": "Whether to show default upload list."
57
+ },
58
+ "singleFile": {
59
+ "type": "boolean",
60
+ "default": false,
61
+ "description": "Only allow a single file to be uploaded. Only one file can be selected in the prompt."
62
+ },
63
+ "style": {
64
+ "type": "object",
65
+ "description": "Css style object to applied to draggable area.",
66
+ "docs": {
67
+ "displayType": "yaml"
68
+ }
69
+ }
70
+ }
71
+ },
72
+ "events": {
73
+ "type": "object",
74
+ "properties": {
75
+ "onChange": {
76
+ "type": "array",
77
+ "description": "Triggered when the upload state is changing."
78
+ }
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,17 @@
1
+ /*
2
+ Copyright 2020-2024 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ @import 'antd/lib/upload/style/index.less';
@@ -13,144 +13,20 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import React, { useEffect, useState } from 'react';
16
- import { blockDefaultProps } from '@lowdefy/block-utils';
17
- import { get } from '@lowdefy/helpers';
16
+ import { blockDefaultProps, renderHtml } from '@lowdefy/block-utils';
18
17
  import { Upload } from 'antd';
19
- const makeFileValue = (file, s3Parameters)=>{
20
- const { lastModified, name, percent, size, status, type, uid } = file;
21
- const { bucket, key } = get(s3Parameters, uid, {
22
- default: {}
23
- });
24
- return {
25
- bucket,
26
- key,
27
- lastModified,
28
- name,
29
- percent,
30
- size,
31
- status,
32
- type,
33
- uid
34
- };
35
- };
36
- const makeOnChangeValue = (s3Parameters, changeEvent)=>{
37
- const { file, fileList } = changeEvent;
38
- return {
39
- file: makeFileValue(file, s3Parameters),
40
- fileList: fileList.map((fl)=>makeFileValue(fl, s3Parameters))
41
- };
42
- };
43
- const getCustomRequest = ({ methods, setS3Parameters, setLoading })=>async ({ file, onError, onProgress, onSuccess })=>{
44
- let meta;
45
- try {
46
- setLoading(true);
47
- const { name, size, type, uid } = file;
48
- if (size > 1024 * 1024 * 10) throw new Error('File cannot exceed 10mb.');
49
- const s3PostPolicyResponse = await methods.triggerEvent({
50
- name: '__getS3PostPolicy',
51
- event: {
52
- filename: name,
53
- size,
54
- type,
55
- uid
56
- }
57
- });
58
- if (s3PostPolicyResponse.success !== true) {
59
- throw new Error('S3 post policy request error.');
60
- }
61
- const { url, fields } = s3PostPolicyResponse.responses.__getS3PostPolicy.response[0];
62
- const { bucket, key } = fields;
63
- meta = {
64
- bucket,
65
- key,
66
- filename: name,
67
- size,
68
- type,
69
- uid
70
- };
71
- setS3Parameters((prevState)=>{
72
- const ret = {
73
- ...prevState
74
- };
75
- ret[uid] = {
76
- bucket,
77
- key
78
- };
79
- return ret;
80
- });
81
- // Set 20 % progress on policy is acquired else user waits to long before progress is reported
82
- onProgress({
83
- percent: 20
84
- });
85
- // Create FormData with all required fields in S3 policy
86
- const formData = new FormData();
87
- Object.keys(fields).forEach((field)=>{
88
- formData.append(field, fields[field]);
89
- });
90
- // file needs to be the last field in the form
91
- formData.append('file', file);
92
- const xhr = new XMLHttpRequest();
93
- xhr.upload.onprogress = (event)=>{
94
- if (event.lengthComputable) {
95
- onProgress({
96
- percent: event.loaded / event.total * 80 + 20
97
- });
98
- }
99
- };
100
- xhr.addEventListener('error', async (event)=>{
101
- await methods.triggerEvent({
102
- name: 'onError',
103
- event: {
104
- meta,
105
- event
106
- }
107
- });
108
- onError(event);
109
- });
110
- xhr.addEventListener('load', async (event)=>{
111
- await methods.triggerEvent({
112
- name: 'onSuccess',
113
- event: {
114
- meta,
115
- event
116
- }
117
- });
118
- onSuccess(event);
119
- });
120
- xhr.addEventListener('loadend', async (event)=>{
121
- await methods.triggerEvent({
122
- name: 'onDone',
123
- event: {
124
- meta,
125
- event
126
- }
127
- });
128
- setLoading(false);
129
- });
130
- xhr.open('post', url);
131
- xhr.send(formData);
132
- } catch (error) {
133
- console.error(error);
134
- await methods.triggerEvent({
135
- name: 'onError',
136
- event: {
137
- meta,
138
- error
139
- }
140
- });
141
- onError(error);
142
- }
143
- };
18
+ import useFileList from '../utils/useFileList.js';
19
+ import getS3Upload from '../utils/getS3Upload.js';
144
20
  const S3UploadPhoto = ({ blockId, components: { Icon }, events, methods, properties, value })=>{
145
- // Use state here because we need to set s3 bucket and key as block value
146
- // The customRequest function does not have access to the updated block value,
147
- // so it cannot set the value directly. customRequest sets the parameters to s3Parameters state,
148
- // and then onChange updates the block value.
149
- const [s3Parameters, setS3Parameters] = useState(value);
21
+ const [state, loadFileList, setFileList, removeFile, setValue] = useFileList({
22
+ properties,
23
+ methods,
24
+ value
25
+ });
150
26
  const [loading, setLoading] = useState(false);
151
- const customRequest = getCustomRequest({
27
+ const s3UploadRequest = getS3Upload({
152
28
  methods,
153
- setS3Parameters,
29
+ setFileList,
154
30
  setLoading
155
31
  });
156
32
  useEffect(()=>{
@@ -171,22 +47,36 @@ const S3UploadPhoto = ({ blockId, components: { Icon }, events, methods, propert
171
47
  ]
172
48
  });
173
49
  }, []);
50
+ useEffect(()=>{
51
+ if (JSON.stringify(value) !== JSON.stringify(state)) {
52
+ setValue(value);
53
+ }
54
+ }, [
55
+ value
56
+ ]);
174
57
  return /*#__PURE__*/ React.createElement(Upload, {
175
- listType: "picture-card",
176
- className: "avatar-uploader",
177
58
  accept: "image/*",
178
- customRequest: customRequest,
179
- maxCount: 1,
59
+ beforeUpload: loadFileList,
60
+ className: "avatar-uploader",
61
+ customRequest: s3UploadRequest,
62
+ disabled: properties.disabled,
63
+ fileList: state.fileList,
180
64
  id: blockId,
65
+ listType: "picture-card",
66
+ maxCount: properties.maxCount,
181
67
  multiple: !properties.singleFile,
68
+ onRemove: removeFile,
182
69
  showUploadList: properties.showUploadList,
183
- onChange: (event)=>{
184
- methods.setValue(makeOnChangeValue(s3Parameters, event));
70
+ onChange: ()=>{
185
71
  methods.triggerEvent({
186
72
  name: 'onChange'
187
73
  });
188
74
  }
189
- }, /*#__PURE__*/ React.createElement("div", null, loading ? /*#__PURE__*/ React.createElement(Icon, {
75
+ }, /*#__PURE__*/ React.createElement("div", {
76
+ className: methods.makeCssClass([
77
+ properties.style
78
+ ])
79
+ }, loading ? /*#__PURE__*/ React.createElement(Icon, {
190
80
  blockId: `${blockId}_icon`,
191
81
  events: events,
192
82
  properties: {
@@ -204,7 +94,10 @@ const S3UploadPhoto = ({ blockId, components: { Icon }, events, methods, propert
204
94
  style: {
205
95
  marginTop: 8
206
96
  }
207
- }, "Upload a photo")));
97
+ }, renderHtml({
98
+ html: properties.title ?? 'Upload image',
99
+ methods
100
+ }))));
208
101
  };
209
102
  S3UploadPhoto.defaultProps = blockDefaultProps;
210
103
  S3UploadPhoto.meta = {
@@ -4,6 +4,19 @@
4
4
  "type": "object",
5
5
  "required": ["s3PostPolicyRequestId"],
6
6
  "properties": {
7
+ "title": {
8
+ "type": "string",
9
+ "description": "Title of the file input to be displayed instead of 'Upload image'.",
10
+ "default": "Upload image"
11
+ },
12
+ "disabled": {
13
+ "type": "boolean",
14
+ "description": "Disable the file input."
15
+ },
16
+ "maxCount": {
17
+ "type": "number",
18
+ "description": "Maximum number of files that can be uploaded."
19
+ },
7
20
  "s3PostPolicyRequestId": {
8
21
  "type": "string",
9
22
  "description": "Id of a request of type AwsS3PresignedPostPolicy that defines to which S3 bucket and how the file should be uploaded.",
@@ -43,7 +56,14 @@
43
56
  "singleFile": {
44
57
  "type": "boolean",
45
58
  "default": false,
46
- "description": "Only allow a single file to be uploaded. Only one file can be selected in the prompt and the upload button is disabled after a file is uploaded."
59
+ "description": "Only allow a single file to be uploaded. Only one file can be selected in the prompt."
60
+ },
61
+ "style": {
62
+ "type": "object",
63
+ "description": "Css style object to applied to draggable area.",
64
+ "docs": {
65
+ "displayType": "yaml"
66
+ }
47
67
  }
48
68
  }
49
69
  },
@@ -53,10 +73,6 @@
53
73
  "onChange": {
54
74
  "type": "array",
55
75
  "description": "Triggered when the upload state is changing."
56
- },
57
- "onClick": {
58
- "type": "array",
59
- "description": "Triggered when the upload button is clicked."
60
76
  }
61
77
  }
62
78
  }
@@ -0,0 +1,48 @@
1
+ /*
2
+ Copyright 2020-2024 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ const getFileFromEvent = async (event)=>{
16
+ const items = event.clipboardData.items;
17
+ for (const item of items){
18
+ if (item.kind === 'file') {
19
+ return item.getAsFile();
20
+ }
21
+ }
22
+ };
23
+ const getFileFromNavigator = async ()=>{
24
+ const items = await navigator.clipboard.read();
25
+ for (const item of items){
26
+ for (const type of item.types){
27
+ if (type === 'image/png' || type === 'image/jpeg') {
28
+ const blob = await item.getType(type);
29
+ return new File([
30
+ blob
31
+ ], 'clipboard.png', {
32
+ type: blob.type
33
+ });
34
+ }
35
+ }
36
+ }
37
+ };
38
+ const getOnPaste = ({ s3UploadRequest, properties })=>async (event)=>{
39
+ event?.preventDefault?.();
40
+ if (properties.disabled) return;
41
+ const file = event ? await getFileFromEvent(event) : await getFileFromNavigator();
42
+ if (!file) return;
43
+ file.uid = `${properties.fileName ?? file.name ?? 'clipboard'}-${Date.now()}`;
44
+ await s3UploadRequest({
45
+ file
46
+ });
47
+ };
48
+ export default getOnPaste;
@@ -0,0 +1,81 @@
1
+ /*
2
+ Copyright 2020-2024 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ const getS3Upload = ({ methods, setFileList, setLoading = ()=>null })=>async ({ file })=>{
16
+ if (!file) {
17
+ console.warn('File is undefined in useS3Upload');
18
+ return;
19
+ }
20
+ try {
21
+ setLoading(true);
22
+ const { lastModified, name, size, type, uid } = file;
23
+ const s3PostPolicyResponse = await methods.triggerEvent({
24
+ name: '__getS3PostPolicy',
25
+ event: {
26
+ file: {
27
+ name,
28
+ lastModified,
29
+ size,
30
+ type,
31
+ uid
32
+ }
33
+ }
34
+ });
35
+ if (s3PostPolicyResponse.success !== true) {
36
+ throw new Error('S3 post policy request error.');
37
+ }
38
+ const { url, fields = {} } = s3PostPolicyResponse.responses.__getS3PostPolicy.response[0];
39
+ const { bucket, key } = fields;
40
+ file.bucket = bucket;
41
+ file.key = key;
42
+ file.percent = 20;
43
+ const formData = new FormData();
44
+ Object.keys(fields).forEach((field)=>{
45
+ formData.append(field, fields[field]);
46
+ });
47
+ formData.append('file', file);
48
+ const xhr = new XMLHttpRequest();
49
+ xhr.upload.onprogress = async (event)=>{
50
+ if (event.lengthComputable) {
51
+ await setFileList({
52
+ event: 'onProgress',
53
+ file,
54
+ percent: event.loaded / event.total * 80 + 20
55
+ });
56
+ }
57
+ };
58
+ xhr.addEventListener('error', async ()=>{
59
+ await setFileList({
60
+ event: 'onError',
61
+ file
62
+ });
63
+ });
64
+ xhr.addEventListener('loadend', async ()=>{
65
+ await setFileList({
66
+ event: 'onSuccess',
67
+ file
68
+ });
69
+ setLoading(false);
70
+ });
71
+ xhr.open('post', url);
72
+ xhr.send(formData);
73
+ } catch (error) {
74
+ console.error(error);
75
+ await setFileList({
76
+ event: 'onError',
77
+ file
78
+ });
79
+ }
80
+ };
81
+ export default getS3Upload;
@@ -0,0 +1,115 @@
1
+ /*
2
+ Copyright 2020-2024 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { useState } from 'react';
16
+ import { type } from '@lowdefy/helpers';
17
+ const useFileList = ({ properties, methods, value = {} })=>{
18
+ const checkedValue = (value)=>{
19
+ let file = type.isObject(value?.file) ? value.file : null;
20
+ if (!file && type.isObject(value?.fileList?.[0])) {
21
+ file = value.fileList[0];
22
+ }
23
+ const fileList = type.isArray(value?.fileList) ? value.fileList : [];
24
+ if (properties.singleFile === true) {
25
+ fileList.splice(1);
26
+ }
27
+ if (type.isInt(properties.maxCount)) {
28
+ fileList.splice(properties.maxCount);
29
+ }
30
+ return {
31
+ file,
32
+ fileList
33
+ };
34
+ };
35
+ const [state, setState] = useState(checkedValue(value));
36
+ const setValue = (stateValue)=>{
37
+ setState(()=>{
38
+ return checkedValue(stateValue);
39
+ });
40
+ };
41
+ const setFileList = async ({ event, file, percent })=>{
42
+ if (!file) {
43
+ throw new Error('File is undefined in useFileList');
44
+ }
45
+ // destruct the file object to avoid the file object being mutated.
46
+ const { bucket, key, lastModified, name, size, status, type, uid } = file;
47
+ const fileObj = {
48
+ bucket,
49
+ key,
50
+ lastModified,
51
+ name,
52
+ percent: percent ?? file.percent ?? 0,
53
+ size,
54
+ status,
55
+ type,
56
+ uid,
57
+ url: file instanceof Blob || file instanceof File ? URL.createObjectURL(file) : null
58
+ };
59
+ switch(event){
60
+ case 'onProgress':
61
+ fileObj.status = 'uploading';
62
+ fileObj.percent = percent ?? fileObj.percent;
63
+ break;
64
+ case 'onSuccess':
65
+ fileObj.status = 'done';
66
+ fileObj.percent = 100;
67
+ break;
68
+ case 'onRemove':
69
+ fileObj.status = 'removed';
70
+ break;
71
+ default:
72
+ fileObj.status = 'error';
73
+ break;
74
+ }
75
+ state.fileList.splice(state.fileList.findIndex((f)=>f.uid === fileObj.uid), 1, fileObj);
76
+ const nextState = checkedValue({
77
+ file: fileObj,
78
+ fileList: state.fileList
79
+ });
80
+ await methods.triggerEvent({
81
+ name: event,
82
+ event: nextState
83
+ });
84
+ setValue(nextState);
85
+ methods.setValue(nextState);
86
+ };
87
+ const loadFileList = (file, nextFiles)=>{
88
+ if (properties.singleFile === true && nextFiles.filter((f)=>type.isString(f.uid)).length > 1) {
89
+ return false;
90
+ }
91
+ if (type.isInt(properties.maxCount) && nextFiles.filter((f)=>type.isString(f.uid)).length > properties.maxCount) {
92
+ return false;
93
+ }
94
+ setValue({
95
+ file,
96
+ fileList: [
97
+ ...nextFiles,
98
+ ...state.fileList
99
+ ]
100
+ });
101
+ };
102
+ const removeFile = (file)=>{
103
+ state.fileList.splice(state.fileList.findIndex((f)=>f.uid === file.uid), 1);
104
+ setValue(state);
105
+ methods.setValue(state);
106
+ };
107
+ return [
108
+ state,
109
+ loadFileList,
110
+ setFileList,
111
+ removeFile,
112
+ setValue
113
+ ];
114
+ };
115
+ export default useFileList;
package/dist/blocks.js CHANGED
@@ -12,5 +12,7 @@
12
12
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
- */ export { default as S3UploadButton } from './blocks/S3UploadButton/S3UploadButton.js';
15
+ */ export { default as S3Download } from './blocks/S3Download/S3Download.js';
16
+ export { default as S3UploadDragger } from './blocks/S3UploadDragger/S3UploadDragger.js';
17
+ export { default as S3UploadButton } from './blocks/S3UploadButton/S3UploadButton.js';
16
18
  export { default as S3UploadPhoto } from './blocks/S3UploadPhoto/S3UploadPhoto.js';
@@ -14,9 +14,10 @@
14
14
  limitations under the License.
15
15
  */ import AWS from 'aws-sdk';
16
16
  import schema from './schema.js';
17
+ import { type } from '@lowdefy/helpers';
17
18
  function AwsS3PresignedPostPolicy({ request, connection }) {
18
19
  const { accessKeyId, secretAccessKey, region, bucket } = connection;
19
- const { acl, conditions, expires, key } = request;
20
+ const { acl, conditions, expires, key, fields = {} } = request;
20
21
  const params = {
21
22
  Bucket: bucket,
22
23
  Fields: {
@@ -32,6 +33,14 @@ function AwsS3PresignedPostPolicy({ request, connection }) {
32
33
  if (acl) {
33
34
  params.Fields.acl = acl;
34
35
  }
36
+ if (type.isObject(fields) === false) {
37
+ throw new Error('properties.fields must be an object.');
38
+ }
39
+ Object.keys(fields).forEach((field)=>{
40
+ if (fields[field]) {
41
+ params.Fields[field] = fields[field];
42
+ }
43
+ });
35
44
  const s3 = new AWS.S3({
36
45
  accessKeyId,
37
46
  secretAccessKey,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowdefy/plugin-aws",
3
- "version": "4.1.0",
3
+ "version": "4.2.1",
4
4
  "license": "Apache-2.0",
5
5
  "description": "",
6
6
  "homepage": "https://lowdefy.com",
@@ -40,9 +40,9 @@
40
40
  "dist/*"
41
41
  ],
42
42
  "dependencies": {
43
- "@lowdefy/block-utils": "4.1.0",
44
- "@lowdefy/blocks-antd": "4.1.0",
45
- "@lowdefy/helpers": "4.1.0",
43
+ "@lowdefy/block-utils": "4.2.1",
44
+ "@lowdefy/blocks-antd": "4.2.1",
45
+ "@lowdefy/helpers": "4.2.1",
46
46
  "antd": "4.24.14",
47
47
  "aws-sdk": "2.1459.0",
48
48
  "react": "18.2.0",
@@ -50,9 +50,9 @@
50
50
  },
51
51
  "devDependencies": {
52
52
  "@emotion/jest": "11.10.5",
53
- "@lowdefy/ajv": "4.1.0",
54
- "@lowdefy/block-dev": "4.1.0",
55
- "@lowdefy/jest-yaml-transform": "4.1.0",
53
+ "@lowdefy/ajv": "4.2.1",
54
+ "@lowdefy/block-dev": "4.2.1",
55
+ "@lowdefy/jest-yaml-transform": "4.2.1",
56
56
  "@swc/cli": "0.1.63",
57
57
  "@swc/core": "1.3.99",
58
58
  "@swc/jest": "0.2.29",