neo.mjs 5.15.5 → 5.16.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.
- package/apps/ServiceWorker.mjs +2 -2
- package/buildScripts/docs/jsdocx.mjs +8 -6
- package/docs/app/view/MainContainer.mjs +10 -0
- package/docs/app/view/classdetails/HeaderComponent.mjs +3 -3
- package/docs/app/view/classdetails/MainContainer.mjs +14 -4
- package/docs/app/view/classdetails/MembersList.mjs +5 -5
- package/examples/ServiceWorker.mjs +2 -2
- package/examples/form/field/fileupload/MainContainer.mjs +68 -10
- package/examples/form/field/fileupload/README.md +9 -0
- package/examples/form/field/fileupload/server.mjs +49 -0
- package/examples/form/field/switch/MainContainer.mjs +124 -0
- package/examples/form/field/switch/app.mjs +6 -0
- package/examples/form/field/switch/index.html +11 -0
- package/examples/form/field/switch/neo-config.json +6 -0
- package/package.json +2 -1
- package/resources/scss/src/apps/docs/HeaderContainer.scss +1 -1
- package/resources/scss/src/apps/docs/MainContainer.scss +5 -1
- package/resources/scss/src/apps/docs/classdetails/HeaderComponent.scss +2 -5
- package/resources/scss/src/apps/docs/classdetails/MainContainer.scss +1 -1
- package/resources/scss/src/component/Splitter.scss +3 -1
- package/resources/scss/src/form/field/FileUpload.scss +248 -2
- package/resources/scss/src/form/field/Switch.scss +85 -115
- package/resources/scss/src/tree/List.scss +2 -1
- package/resources/scss/theme-dark/form/field/FileUpload.scss +20 -4
- package/resources/scss/theme-dark/form/field/Switch.scss +10 -10
- package/resources/scss/theme-light/form/field/FileUpload.scss +20 -4
- package/resources/scss/theme-light/form/field/Switch.scss +10 -10
- package/src/DefaultConfig.mjs +2 -2
- package/src/component/Splitter.mjs +27 -22
- package/src/form/field/FileUpload.mjs +512 -4
- package/src/form/field/Switch.mjs +11 -11
- package/src/main/addon/Markdown.mjs +2 -2
- package/src/main/addon/ResizeObserver.mjs +79 -0
@@ -77,7 +77,7 @@ class Splitter extends Component {
|
|
77
77
|
me.addDomListeners([
|
78
78
|
{'drag:end' : me.onDragEnd, scope: me},
|
79
79
|
{'drag:start': me.onDragStart, scope: me}
|
80
|
-
])
|
80
|
+
])
|
81
81
|
}
|
82
82
|
|
83
83
|
/**
|
@@ -87,20 +87,24 @@ class Splitter extends Component {
|
|
87
87
|
* @protected
|
88
88
|
*/
|
89
89
|
afterSetDirection(value, oldValue) {
|
90
|
-
let me
|
91
|
-
cls
|
90
|
+
let me = this,
|
91
|
+
cls = me.cls,
|
92
|
+
height = value === 'vertical' ? null : me.size,
|
93
|
+
width = value !== 'vertical' ? null : me.size;
|
92
94
|
|
93
95
|
NeoArray.add(cls, `neo-${value}`);
|
94
96
|
|
95
97
|
if (oldValue) {
|
96
|
-
NeoArray.remove(cls, `neo-${oldValue}`)
|
98
|
+
NeoArray.remove(cls, `neo-${oldValue}`)
|
97
99
|
}
|
98
100
|
|
99
101
|
me.set({
|
100
102
|
cls,
|
101
|
-
height
|
102
|
-
|
103
|
-
|
103
|
+
height,
|
104
|
+
minHeight: height,
|
105
|
+
minWidth : width,
|
106
|
+
width
|
107
|
+
})
|
104
108
|
}
|
105
109
|
|
106
110
|
/**
|
@@ -110,7 +114,7 @@ class Splitter extends Component {
|
|
110
114
|
* @protected
|
111
115
|
*/
|
112
116
|
afterSetSize(value, oldValue) {
|
113
|
-
this[this.direction === 'vertical' ? 'width' : 'height'] = value
|
117
|
+
this[this.direction === 'vertical' ? 'width' : 'height'] = value
|
114
118
|
}
|
115
119
|
|
116
120
|
/**
|
@@ -121,7 +125,7 @@ class Splitter extends Component {
|
|
121
125
|
* @returns {String}
|
122
126
|
*/
|
123
127
|
beforeSetDirection(value, oldValue) {
|
124
|
-
return this.beforeSetEnumValue(value, oldValue, 'direction')
|
128
|
+
return this.beforeSetEnumValue(value, oldValue, 'direction')
|
125
129
|
}
|
126
130
|
|
127
131
|
/**
|
@@ -132,7 +136,7 @@ class Splitter extends Component {
|
|
132
136
|
* @returns {String}
|
133
137
|
*/
|
134
138
|
beforeSetResizeTarget(value, oldValue) {
|
135
|
-
return this.beforeSetEnumValue(value, oldValue, 'resizeTarget')
|
139
|
+
return this.beforeSetEnumValue(value, oldValue, 'resizeTarget')
|
136
140
|
}
|
137
141
|
|
138
142
|
/**
|
@@ -163,24 +167,25 @@ class Splitter extends Component {
|
|
163
167
|
style.flex = 'none';
|
164
168
|
|
165
169
|
if (me.direction === 'vertical') {
|
166
|
-
newSize = data.clientX - data.offsetX - size;
|
170
|
+
newSize = data.clientX - data.offsetX - size - parentRect.left;
|
167
171
|
|
168
172
|
if (resizeNext) {
|
169
|
-
|
173
|
+
console.log(parentRect);
|
174
|
+
newSize = parentRect.width - newSize - 2 * size
|
170
175
|
} else {
|
171
|
-
newSize += size
|
176
|
+
newSize += size
|
172
177
|
}
|
173
178
|
|
174
179
|
newSize = Math.min(Math.max(newSize, 0), parentRect.width - size);
|
175
180
|
|
176
|
-
style.width = `${newSize}px
|
181
|
+
style.width = `${newSize}px`
|
177
182
|
} else {
|
178
|
-
newSize = data.clientY - data.offsetY - size;
|
183
|
+
newSize = data.clientY - data.offsetY - size - parentRect.top;
|
179
184
|
|
180
185
|
if (resizeNext) {
|
181
|
-
newSize = parentRect.height - newSize
|
186
|
+
newSize = parentRect.height - newSize - 2 * size
|
182
187
|
} else {
|
183
|
-
newSize += size
|
188
|
+
newSize += size
|
184
189
|
}
|
185
190
|
|
186
191
|
newSize = Math.min(Math.max(newSize, 0), parentRect.height - size);
|
@@ -188,8 +193,8 @@ class Splitter extends Component {
|
|
188
193
|
style.height = `${newSize}px`;
|
189
194
|
}
|
190
195
|
|
191
|
-
sibling.style = style
|
192
|
-
})
|
196
|
+
sibling.style = style
|
197
|
+
})
|
193
198
|
}
|
194
199
|
|
195
200
|
/**
|
@@ -214,20 +219,20 @@ class Splitter extends Component {
|
|
214
219
|
owner : me,
|
215
220
|
useProxyWrapper : false,
|
216
221
|
...me.dragZoneConfig
|
217
|
-
})
|
222
|
+
})
|
218
223
|
} else {
|
219
224
|
me.dragZone.set({
|
220
225
|
bodyCursorStyle: vertical ? 'ew-resize !important' : 'ns-resize !important',
|
221
226
|
moveHorizontal : vertical,
|
222
227
|
moveVertical : !vertical
|
223
|
-
})
|
228
|
+
})
|
224
229
|
}
|
225
230
|
|
226
231
|
me.dragZone.dragStart(data);
|
227
232
|
|
228
233
|
style.opacity = 0.5;
|
229
234
|
|
230
|
-
me.style = style
|
235
|
+
me.style = style
|
231
236
|
}
|
232
237
|
}
|
233
238
|
|
@@ -1,6 +1,81 @@
|
|
1
1
|
import Base from '../../form/field/Base.mjs';
|
2
|
+
import NeoArray from '../../util/Array.mjs';
|
3
|
+
|
4
|
+
const
|
5
|
+
sizeRE = /^(\d+)(kb|mb|gb)?$/i,
|
6
|
+
sizeMultiplier = {
|
7
|
+
unit : 1,
|
8
|
+
kb : 1000,
|
9
|
+
mb : 1000000,
|
10
|
+
gb : 1000000000
|
11
|
+
};
|
2
12
|
|
3
13
|
/**
|
14
|
+
* An accessible file uploading widget which automatically commences an upload as soon as
|
15
|
+
* a file is selected using the UI.
|
16
|
+
*
|
17
|
+
* The URL to which the file must be uploaded is specified in the {@link #member-uploadUrl} property.
|
18
|
+
* This service must return a JSON status response in the following form for successful uploads:
|
19
|
+
*
|
20
|
+
* ```json
|
21
|
+
* {
|
22
|
+
* "success" : true,
|
23
|
+
* "documentId" : 1
|
24
|
+
* }
|
25
|
+
* ```
|
26
|
+
*
|
27
|
+
* And the following form for unsuccessful uploads:
|
28
|
+
*
|
29
|
+
* ```json
|
30
|
+
* {
|
31
|
+
* "success" : false,
|
32
|
+
* "message" : "Why the upload was rejected"
|
33
|
+
* }
|
34
|
+
* ```
|
35
|
+
*
|
36
|
+
* The name of the `documentId` property is configured in {@link #member-documentIdParameter}.
|
37
|
+
* It defaults to `'documentId'`.
|
38
|
+
*
|
39
|
+
* The `documentId` is used when requesting the document malware scan status, and when requesting
|
40
|
+
* that the document be deleted, or downloaded.
|
41
|
+
*
|
42
|
+
* If the upload is successful, then the {@link #member-documentStatusUrl} is polled until the
|
43
|
+
* malware scan. The document id returned from the upload is passed in the parameter named
|
44
|
+
* by the {@link #member-documentIdParameter}. It defaults to `'documentId'`.
|
45
|
+
*
|
46
|
+
* This service must return a JSON status response in the following if the scan is still progressing:
|
47
|
+
*
|
48
|
+
* ```json
|
49
|
+
* {
|
50
|
+
* "status" : "scanning"
|
51
|
+
* }
|
52
|
+
* ```
|
53
|
+
*
|
54
|
+
* And the following form is malware was detected:
|
55
|
+
*
|
56
|
+
* ```json
|
57
|
+
* {
|
58
|
+
* "status" : "scan-failed"
|
59
|
+
* }
|
60
|
+
* ```
|
61
|
+
*
|
62
|
+
* After a successful scan, a document may or may not be downloadable.
|
63
|
+
*
|
64
|
+
* For a downloadable document, the response must be:
|
65
|
+
*
|
66
|
+
* ```json
|
67
|
+
* {
|
68
|
+
* "status" : "downloadable"
|
69
|
+
* }
|
70
|
+
* ```
|
71
|
+
*
|
72
|
+
* For a non-downloadable document, the response must be:
|
73
|
+
*
|
74
|
+
* ```json
|
75
|
+
* {
|
76
|
+
* "status" : "not-downloadable"
|
77
|
+
* }
|
78
|
+
* ```
|
4
79
|
* @class Neo.form.field.FileUpload
|
5
80
|
* @extends Neo.form.field.Base
|
6
81
|
*/
|
@@ -17,15 +92,448 @@ class FileUpload extends Base {
|
|
17
92
|
*/
|
18
93
|
ntype: 'file-upload-field',
|
19
94
|
/**
|
20
|
-
* @member {String[]}
|
95
|
+
* @member {String[]} baseCls=['neo-file-upload-field']
|
21
96
|
* @protected
|
22
97
|
*/
|
23
98
|
baseCls: ['neo-file-upload-field'],
|
24
99
|
/**
|
25
|
-
* @member {Object}
|
100
|
+
* @member {Object} _vdom
|
26
101
|
*/
|
27
|
-
_vdom:
|
28
|
-
|
102
|
+
_vdom: {
|
103
|
+
cn : [
|
104
|
+
{
|
105
|
+
tag : 'i',
|
106
|
+
cls : 'neo-file-upload-state-icon'
|
107
|
+
},
|
108
|
+
{
|
109
|
+
cls : 'neo-file-upload-body',
|
110
|
+
cn : [{
|
111
|
+
cls : 'neo-file-upload-filename'
|
112
|
+
}, {
|
113
|
+
cls : 'neo-file-upload-state'
|
114
|
+
}]
|
115
|
+
},
|
116
|
+
{
|
117
|
+
cls : 'neo-file-upload-action-button',
|
118
|
+
tag : 'button'
|
119
|
+
},
|
120
|
+
{
|
121
|
+
cls : 'neo-file-upload-input',
|
122
|
+
tag : 'input',
|
123
|
+
type : 'file'
|
124
|
+
},
|
125
|
+
{
|
126
|
+
cls : 'neo-file-upload-error-message'
|
127
|
+
}
|
128
|
+
]
|
129
|
+
},
|
130
|
+
|
131
|
+
cls : [],
|
132
|
+
|
133
|
+
/**
|
134
|
+
* The URL of the file upload service to which the selected file is sent.
|
135
|
+
*
|
136
|
+
* This service must return a JSON response of the form:
|
137
|
+
*
|
138
|
+
* ```json
|
139
|
+
* {
|
140
|
+
* "success" : true,
|
141
|
+
* "message" : "Only needed if the success property is false",
|
142
|
+
* "documentId" : 1
|
143
|
+
* }
|
144
|
+
* ```
|
145
|
+
*
|
146
|
+
* The document id is needed so that this widget can follow up and request the results of the
|
147
|
+
* scan operation to see if the file was accepted, and whether it is to be subsequently downloadable.
|
148
|
+
*
|
149
|
+
* The document status request URL must be configured in {@link #member-documentStatusUrl}
|
150
|
+
* @member {String} uploadUrl
|
151
|
+
*/
|
152
|
+
uploadUrl_ : null,
|
153
|
+
|
154
|
+
/**
|
155
|
+
* The name of the JSON property in which the document id is returned in the upload response
|
156
|
+
* JSON packet and the HTTP parameter which is used when requesting a malware scan and a document
|
157
|
+
* deletion.
|
158
|
+
*
|
159
|
+
* @member {String} downloadUrl
|
160
|
+
*/
|
161
|
+
documentIdParameter : 'documentId',
|
162
|
+
|
163
|
+
/**
|
164
|
+
* The URL from which the file may be downloaded after it has finished its scan.
|
165
|
+
*
|
166
|
+
* The document id returned from the {@link #member-uploadUrl upload} is passed in the parameter named
|
167
|
+
* by the {@link #member-documentIdParameter}. It defaults to `'documentId'`.
|
168
|
+
*
|
169
|
+
* @member {String} downloadUrl
|
170
|
+
*/
|
171
|
+
downloadUrl_ : null,
|
172
|
+
|
173
|
+
/**
|
174
|
+
* The URL of the file status reporting service.
|
175
|
+
*
|
176
|
+
* This widget will use this service after a successful upload to determine its next
|
177
|
+
* state.
|
178
|
+
*
|
179
|
+
* This service must return a JSON response of the form:
|
180
|
+
*
|
181
|
+
* ```json
|
182
|
+
* {
|
183
|
+
* "status" : "scanning" or "scan-failed" or "downloadable or "not-downloadable"
|
184
|
+
* }
|
185
|
+
* ```
|
186
|
+
*
|
187
|
+
* @member {String} documentStatusUrl
|
188
|
+
*/
|
189
|
+
documentStatusUrl : null,
|
190
|
+
|
191
|
+
/**
|
192
|
+
* The URL of the file deletion service.
|
193
|
+
*
|
194
|
+
* This widget will use this service after a successful upload to determine its next
|
195
|
+
* state.
|
196
|
+
*
|
197
|
+
* If this service yields an HTTP 200 status, the deletion is taken to have been successful.
|
198
|
+
*
|
199
|
+
* @member {String} documentDeleteUrl
|
200
|
+
*/
|
201
|
+
documentDeleteUrl : null,
|
202
|
+
|
203
|
+
headers_ : {},
|
204
|
+
|
205
|
+
/**
|
206
|
+
* @member {String} state_=null
|
207
|
+
*/
|
208
|
+
state_: 'ready',
|
209
|
+
|
210
|
+
/**
|
211
|
+
* @member {Object} types=null
|
212
|
+
*/
|
213
|
+
types_ : null,
|
214
|
+
|
215
|
+
/**
|
216
|
+
* @member {String|Number} maxSize
|
217
|
+
*/
|
218
|
+
maxSize_: null
|
219
|
+
}
|
220
|
+
|
221
|
+
/**
|
222
|
+
* @param {Object} config
|
223
|
+
*/
|
224
|
+
construct(config) {
|
225
|
+
super.construct(config);
|
226
|
+
|
227
|
+
const me = this;
|
228
|
+
|
229
|
+
me.addDomListeners([
|
230
|
+
{ input : me.onInputValueChange, scope: me},
|
231
|
+
{ click : me.onActionButtonClick, delegate : '.neo-file-upload-action-button', scope : me}
|
232
|
+
]);
|
233
|
+
}
|
234
|
+
|
235
|
+
async clear() {
|
236
|
+
const me = this;
|
237
|
+
|
238
|
+
me.vdom.cn[3] = {
|
239
|
+
cls : 'neo-file-upload-input',
|
240
|
+
tag : 'input',
|
241
|
+
type : 'file',
|
242
|
+
value : ''
|
243
|
+
};
|
244
|
+
me.state = 'ready';
|
245
|
+
me.error = '';
|
246
|
+
|
247
|
+
// We have to wait for the DOM to have changed, and the input field to be visible
|
248
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
249
|
+
me.focus(me.vdom.cn[3].id);
|
250
|
+
}
|
251
|
+
|
252
|
+
/**
|
253
|
+
* @param {Object} data
|
254
|
+
* @protected
|
255
|
+
*/
|
256
|
+
onInputValueChange({ files }) {
|
257
|
+
const
|
258
|
+
me = this,
|
259
|
+
{ types } = me;
|
260
|
+
|
261
|
+
if (files.length) {
|
262
|
+
const
|
263
|
+
file = files.item(0),
|
264
|
+
pointPos = file.name.lastIndexOf('.'),
|
265
|
+
type = pointPos > -1 ? file.name.slice(pointPos + 1) : '';
|
266
|
+
|
267
|
+
if (me.types && !types[type]) {
|
268
|
+
me.error = `Please use these file types: .${Object.keys(types).join(' .')}`;
|
269
|
+
}
|
270
|
+
else if (file.size > me.maxSize) {
|
271
|
+
me.error = `File size exceeds ${String(me._maxSize).toUpperCase()}`;
|
272
|
+
}
|
273
|
+
// If it passes the type and maxSize check, upload it
|
274
|
+
else {
|
275
|
+
me.fileSize = me.formatSize(file.size);
|
276
|
+
me.error = '';
|
277
|
+
me.upload(file);
|
278
|
+
}
|
279
|
+
}
|
280
|
+
// If cleared, we go back to ready state
|
281
|
+
else {
|
282
|
+
me.state = 'ready';
|
283
|
+
}
|
284
|
+
}
|
285
|
+
|
286
|
+
async upload(file) {
|
287
|
+
const
|
288
|
+
me = this,
|
289
|
+
xhr = me.xhr = new XMLHttpRequest(),
|
290
|
+
{ upload } = xhr,
|
291
|
+
fileData = new FormData();
|
292
|
+
|
293
|
+
// Show the action button
|
294
|
+
me.state = 'starting';
|
295
|
+
|
296
|
+
// We have to wait for the DOM to have changed, and the action button to be visible
|
297
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
298
|
+
me.focus(me.vdom.cn[2].id);
|
299
|
+
|
300
|
+
me.vdom.cn[1].cn[0].innerHTML = file.name;
|
301
|
+
me.update();
|
302
|
+
me.state = 'uploading';
|
303
|
+
|
304
|
+
fileData.append("file", file);
|
305
|
+
|
306
|
+
// React to upload state changes
|
307
|
+
upload.addEventListener('progress', me.onUploadProgress.bind(me));
|
308
|
+
upload.addEventListener('error', me.onUploadError.bind(me));
|
309
|
+
upload.addEventListener('abort', me.onUploadAbort.bind(me));
|
310
|
+
xhr.addEventListener('loadend', me.onUploadDone.bind(me));
|
311
|
+
|
312
|
+
xhr.open("POST", me.uploadUrl, true);
|
313
|
+
|
314
|
+
xhr.send(fileData);
|
315
|
+
}
|
316
|
+
|
317
|
+
onUploadProgress({ loaded, total }) {
|
318
|
+
const
|
319
|
+
progress = this.progress = loaded / total,
|
320
|
+
{ vdom } = this;
|
321
|
+
|
322
|
+
(vdom.style || (vdom.style = {}))['--upload-progress'] = `${progress}turn`;
|
323
|
+
|
324
|
+
vdom.cn[1].cn[1].innerHTML = `Uploading... (${Math.round(progress * 100)}%)`;
|
325
|
+
|
326
|
+
this.uploadSize = loaded;
|
327
|
+
this.update();
|
328
|
+
}
|
329
|
+
|
330
|
+
onUploadAbort(e) {
|
331
|
+
this.xhr = null;
|
332
|
+
this.clear();
|
333
|
+
}
|
334
|
+
|
335
|
+
onUploadError(e) {
|
336
|
+
this.xhr = null;
|
337
|
+
this.state = 'upload-failed';
|
338
|
+
this.error = e.type;
|
339
|
+
}
|
340
|
+
|
341
|
+
onUploadDone({ loaded, target : xhr }) {
|
342
|
+
const me = this;
|
343
|
+
|
344
|
+
me.xhr = null;
|
345
|
+
|
346
|
+
if (loaded !== 0) {
|
347
|
+
const response = JSON.parse(xhr.response);
|
348
|
+
|
349
|
+
if (response.success) {
|
350
|
+
me.documentId = response[me.documentIdParameter];
|
351
|
+
|
352
|
+
// The status check phase is optional.
|
353
|
+
// If no URL specified, the file is taken to be downloadable.
|
354
|
+
if (me.documentStatusUrl) {
|
355
|
+
me.state = 'processing';
|
356
|
+
|
357
|
+
// Start polling the server to see when the scan has a result;
|
358
|
+
me.checkDocumentStatus();
|
359
|
+
}
|
360
|
+
else {
|
361
|
+
me.state = 'downloadable';
|
362
|
+
}
|
363
|
+
}
|
364
|
+
else {
|
365
|
+
me.error = response.message;
|
366
|
+
me.state = 'upload-failed';
|
367
|
+
}
|
368
|
+
}
|
369
|
+
}
|
370
|
+
|
371
|
+
onActionButtonClick() {
|
372
|
+
const
|
373
|
+
me = this,
|
374
|
+
{ state } = me;
|
375
|
+
|
376
|
+
// When they click the action button, depending on which state we are in, we go to
|
377
|
+
// different states.
|
378
|
+
switch (state) {
|
379
|
+
// During upload, its an abort
|
380
|
+
case 'uploading':
|
381
|
+
me.abortUpload();
|
382
|
+
break;
|
383
|
+
|
384
|
+
// If the upload or the scan failed, the document will not have been
|
385
|
+
// saved, so we just go back to ready state
|
386
|
+
case 'upload-failed':
|
387
|
+
case 'scan-failed':
|
388
|
+
me.clear();
|
389
|
+
me.state = 'ready';
|
390
|
+
break;
|
391
|
+
|
392
|
+
// During scanning and for stored documents, we need to tell the server the document
|
393
|
+
// is not required.
|
394
|
+
case 'processing':
|
395
|
+
case 'downloadable':
|
396
|
+
case 'not-downloadable':
|
397
|
+
me.deleteDocument();
|
398
|
+
break;
|
399
|
+
}
|
400
|
+
}
|
401
|
+
|
402
|
+
abortUpload() {
|
403
|
+
this.xhr?.abort();
|
404
|
+
}
|
405
|
+
|
406
|
+
async deleteDocument() {
|
407
|
+
// We ask the server to delete using our this.documentId
|
408
|
+
const statusResponse = await fetch(`${this.documentDeleteUrl}?${this.documentIdParameter}=${this.documentId}`);
|
409
|
+
|
410
|
+
// Success
|
411
|
+
if (String(statusResponse.status).slice(0, 1) === '2') {
|
412
|
+
this.clear();
|
413
|
+
this.state = 'ready';
|
414
|
+
}
|
415
|
+
else {
|
416
|
+
this.error = `Document delete service error: ${statusResponse.statusText}`;
|
417
|
+
}
|
418
|
+
}
|
419
|
+
|
420
|
+
async checkDocumentStatus() {
|
421
|
+
const me = this;
|
422
|
+
|
423
|
+
if (this.state === 'processing') {
|
424
|
+
const statusResponse = await fetch(`${this.documentStatusUrl}?${me.documentIdParameter}=${this.documentId}`);
|
425
|
+
|
426
|
+
// Success
|
427
|
+
if (String(statusResponse.status).slice(0, 1) === '2') {
|
428
|
+
const status = (await statusResponse.json()).status;
|
429
|
+
|
430
|
+
switch (status) {
|
431
|
+
case 'scanning':
|
432
|
+
setTimeout(() => me.checkDocumentStatus(), 2000);
|
433
|
+
break;
|
434
|
+
default:
|
435
|
+
me.state = status;
|
436
|
+
}
|
437
|
+
}
|
438
|
+
else {
|
439
|
+
this.error = `Document status service error: ${statusResponse.statusText}`;
|
440
|
+
}
|
441
|
+
}
|
442
|
+
}
|
443
|
+
|
444
|
+
/**
|
445
|
+
* Triggered after the state config got changed
|
446
|
+
* @param {String} value
|
447
|
+
* @param {String} oldValue
|
448
|
+
* @protected
|
449
|
+
*/
|
450
|
+
afterSetState(value, oldValue) {
|
451
|
+
const
|
452
|
+
me = this,
|
453
|
+
{
|
454
|
+
vdom
|
455
|
+
} = me,
|
456
|
+
anchor = vdom.cn[1].cn[0],
|
457
|
+
status = vdom.cn[1].cn[1];
|
458
|
+
|
459
|
+
switch (value) {
|
460
|
+
case 'ready':
|
461
|
+
anchor.tag = 'div';
|
462
|
+
anchor.href = '';
|
463
|
+
break;
|
464
|
+
case 'upload-failed':
|
465
|
+
status.innerHTML = `Upload failed... (${Math.round(me.progress * 100)}%)`;
|
466
|
+
break;
|
467
|
+
case 'processing':
|
468
|
+
status.innerHTML = `Scanning... (${me.formatSize(me.uploadSize)})`;
|
469
|
+
break;
|
470
|
+
case 'scan-failed':
|
471
|
+
status.innerHTML = `Malware found in file. \u2022 ${me.fileSize}`;
|
472
|
+
me.error = 'Please check the file and try again';
|
473
|
+
break;
|
474
|
+
case 'downloadable':
|
475
|
+
anchor.tag = 'a';
|
476
|
+
anchor.href = `${me.downloadUrl}?${me.documentIdParameter}=${me.documentId}`;
|
477
|
+
status.innerHTML = me.fileSize;
|
478
|
+
break;
|
479
|
+
case 'not-downloadable':
|
480
|
+
status.innerHTML = `Successfully uploaded \u2022 ${me.fileSize}`;
|
481
|
+
}
|
482
|
+
|
483
|
+
me.update();
|
484
|
+
|
485
|
+
// Processing above may mutate cls
|
486
|
+
const { cls } = me;
|
487
|
+
|
488
|
+
NeoArray.remove(cls, 'neo-file-upload-state-' + oldValue);
|
489
|
+
NeoArray.add(cls, 'neo-file-upload-state-' + value);
|
490
|
+
me.cls = cls;
|
491
|
+
}
|
492
|
+
|
493
|
+
beforeGetMaxSize(maxSize) {
|
494
|
+
// Not configured means no limit
|
495
|
+
if (maxSize == null) {
|
496
|
+
return Number.MAX_SAFE_INTEGER;
|
497
|
+
}
|
498
|
+
|
499
|
+
// Split eg "100mb" into the numeric and units parts
|
500
|
+
const sizeParts = sizeRE.exec(maxSize);
|
501
|
+
|
502
|
+
if (sizeParts) {
|
503
|
+
// Convert mb to 1000000 etc
|
504
|
+
const multiplier = sizeMultiplier[(sizeParts[2]||'unit').toLowerCase()];
|
505
|
+
|
506
|
+
return parseInt(sizeParts[1]) * multiplier;
|
507
|
+
}
|
508
|
+
}
|
509
|
+
|
510
|
+
set error(text) {
|
511
|
+
const { cls } = this;
|
512
|
+
|
513
|
+
if (text) {
|
514
|
+
this.vdom.cn[4].cn = [{
|
515
|
+
vtype : 'text',
|
516
|
+
html : text
|
517
|
+
}];
|
518
|
+
NeoArray.add(cls, 'neo-invalid');
|
519
|
+
}
|
520
|
+
else {
|
521
|
+
NeoArray.remove(cls, 'neo-invalid');
|
522
|
+
}
|
523
|
+
|
524
|
+
this.cls = cls;
|
525
|
+
this.update();
|
526
|
+
}
|
527
|
+
|
528
|
+
formatSize(bytes, separator = '', postFix = '') {
|
529
|
+
if (bytes) {
|
530
|
+
const
|
531
|
+
sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'],
|
532
|
+
i = Math.min(parseInt(Math.floor(Math.log(bytes) / Math.log(1024)).toString(), 10), sizes.length - 1);
|
533
|
+
|
534
|
+
return `${(bytes / (1024 ** i)).toFixed(i ? 1 : 0)}${separator}${sizes[i]}${postFix}`;
|
535
|
+
}
|
536
|
+
return 'n/a';
|
29
537
|
}
|
30
538
|
}
|
31
539
|
|
@@ -17,24 +17,24 @@ class Switch extends CheckBox {
|
|
17
17
|
*/
|
18
18
|
ntype: 'switchfield',
|
19
19
|
/**
|
20
|
-
* @member {String[]} baseCls=['neo-
|
20
|
+
* @member {String[]} baseCls=['neo-switchfield']
|
21
21
|
*/
|
22
22
|
baseCls: ['neo-switchfield'],
|
23
23
|
/**
|
24
24
|
* @member {Object} _vdom
|
25
25
|
*/
|
26
26
|
_vdom:
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
]}
|
27
|
+
{cn: [
|
28
|
+
{tag: 'label', cls: ['neo-checkbox-label'], cn: [
|
29
|
+
{tag: 'span', cls: []},
|
30
|
+
{tag: 'input', cls: ['neo-checkbox-input']},
|
31
|
+
{tag: 'i', cls: ['neo-checkbox-icon'], removeDom: true},
|
32
|
+
{tag: 'span', cls: ['neo-checkbox-value-label']}
|
33
|
+
]},
|
34
|
+
{cls: ['neo-error-wrapper'], removeDom: true, cn: [
|
35
|
+
{cls: ['neo-error']}
|
37
36
|
]}
|
37
|
+
]}
|
38
38
|
}
|
39
39
|
}
|
40
40
|
|