vgapp 1.5.1 → 1.5.2
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/app/langs/ru/messages.json +1 -1
- package/app/modules/vgfilepreview/scss/vgfilepreview.scss +1 -1
- package/app/modules/vgfiles/js/base.js +165 -62
- package/app/modules/vgfiles/js/droppable.js +17 -10
- package/app/modules/vgfiles/js/sortable.js +52 -18
- package/app/modules/vgfiles/js/vgfiles.js +37 -22
- package/app/modules/vgfiles/scss/_variables.scss +10 -3
- package/app/modules/vgfiles/scss/vgfiles.scss +29 -15
- package/build/vgapp.css +1 -1
- package/build/vgapp.css.map +1 -1
- package/build/vgapp.js +1 -1
- package/build/vgapp.js.map +1 -1
- package/package.json +1 -1
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"files": {
|
|
18
18
|
"is-count": "Превышен лимит по количеству файлов",
|
|
19
|
-
"is-sizes": "Превышен размер
|
|
19
|
+
"is-sizes": "Превышен размер файла",
|
|
20
20
|
"is-types": "Недопустимый тип файла",
|
|
21
21
|
"is-total-size": "Превышен максимально разрешённый размер для выбранных файлов",
|
|
22
22
|
"titles": "Удаление файлов",
|
|
@@ -26,9 +26,10 @@ class VGFilesBase extends BaseModule {
|
|
|
26
26
|
this._isInitialized = true;
|
|
27
27
|
|
|
28
28
|
this._tpl = Html('dom');
|
|
29
|
-
this._files = [];
|
|
30
|
-
this._errors = new Set();
|
|
31
|
-
this.
|
|
29
|
+
this._files = [];
|
|
30
|
+
this._errors = new Set();
|
|
31
|
+
this._validationErrorsByKey = new Map();
|
|
32
|
+
this._fileObjectUrls = new Map();
|
|
32
33
|
this._audioMetaPromises = new Map();
|
|
33
34
|
|
|
34
35
|
this._nodes = {
|
|
@@ -197,9 +198,10 @@ class VGFilesBase extends BaseModule {
|
|
|
197
198
|
filesToProcess = this._mergeFilesByOrder(incoming, this._files, Boolean(this._params.prepend));
|
|
198
199
|
}
|
|
199
200
|
|
|
200
|
-
this._files = this._filterFiles(filesToProcess);
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
this._files = this._filterFiles(filesToProcess);
|
|
202
|
+
this._syncSingleInputSubmission();
|
|
203
|
+
|
|
204
|
+
this._renderErrors();
|
|
203
205
|
this._enrichAudioMetadata(this._files);
|
|
204
206
|
|
|
205
207
|
return this._files;
|
|
@@ -280,47 +282,119 @@ class VGFilesBase extends BaseModule {
|
|
|
280
282
|
return attrs;
|
|
281
283
|
}
|
|
282
284
|
|
|
283
|
-
_filterFiles(files) {
|
|
284
|
-
this._errors.clear();
|
|
285
|
-
|
|
286
|
-
const
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if (
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
285
|
+
_filterFiles(files) {
|
|
286
|
+
this._errors.clear();
|
|
287
|
+
this._validationErrorsByKey.clear();
|
|
288
|
+
const { count, sizes, total } = this._params.limits;
|
|
289
|
+
const maxSize = sizes * 1024 * 1024;
|
|
290
|
+
const maxTotalSize = total * 1024 * 1024;
|
|
291
|
+
|
|
292
|
+
let currentTotalSize = 0;
|
|
293
|
+
let validCount = 0;
|
|
294
|
+
const visibleFiles = [];
|
|
295
|
+
|
|
296
|
+
for (const file of files) {
|
|
297
|
+
const fileErrors = new Set();
|
|
298
|
+
|
|
299
|
+
if (count > 0 && validCount >= count) {
|
|
300
|
+
this._errors.add('is-count');
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (this._params.types.length && !this._params.types.includes(file.type)) {
|
|
305
|
+
fileErrors.add('is-types');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (file.size > maxSize) {
|
|
309
|
+
fileErrors.add('is-sizes');
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (!fileErrors.size && maxTotalSize > 0 && currentTotalSize + file.size > maxTotalSize) {
|
|
313
|
+
fileErrors.add('is-total-size');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
visibleFiles.push(file);
|
|
317
|
+
|
|
318
|
+
if (fileErrors.size) {
|
|
319
|
+
fileErrors.forEach(error => this._errors.add(error));
|
|
320
|
+
this._validationErrorsByKey.set(this._getFileKey(file), fileErrors);
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
validCount += 1;
|
|
325
|
+
currentTotalSize += file.size;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return visibleFiles;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
_isFileValid(file) {
|
|
332
|
+
return !this._validationErrorsByKey.has(this._getFileKey(file));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
_syncSingleInputSubmission() {
|
|
336
|
+
if (Number(this._params?.limits?.count) !== 1) return;
|
|
337
|
+
|
|
338
|
+
const selectedFile = this._files[0];
|
|
339
|
+
const canSubmit = !this._params.ajax && (!selectedFile || this._isFileValid(selectedFile));
|
|
340
|
+
this._preventOriginalInputFromSubmit(canSubmit);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
_getFileValidationMessages(file) {
|
|
344
|
+
const errors = this._validationErrorsByKey.get(this._getFileKey(file));
|
|
345
|
+
if (!errors?.size) return [];
|
|
346
|
+
|
|
347
|
+
const messages = lang_messages(this._params.lang, 'files') || this._getFallbackErrors();
|
|
348
|
+
return Array.from(errors, error => messages[error] || error);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
_getFileValidationLabel(file) {
|
|
352
|
+
const messages = this._getFileValidationMessages(file);
|
|
353
|
+
if (!messages.length) return '';
|
|
354
|
+
|
|
355
|
+
const fileName = this._getShortValidationFileName(file?.name);
|
|
356
|
+
return [fileName, messages.join('. ')].filter(Boolean).join(' — ');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
_getFullFileValidationLabel(file) {
|
|
360
|
+
const messages = this._getFileValidationMessages(file);
|
|
361
|
+
if (!messages.length) return '';
|
|
362
|
+
|
|
363
|
+
const fileName = String(file?.name || '').trim();
|
|
364
|
+
return [fileName, messages.join('. ')].filter(Boolean).join(' — ');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
_getShortValidationFileName(fileName) {
|
|
368
|
+
const name = String(fileName || '').trim();
|
|
369
|
+
if (name.length <= 24) return name;
|
|
370
|
+
|
|
371
|
+
const dotIndex = name.lastIndexOf('.');
|
|
372
|
+
const hasExtension = dotIndex > 0 && name.length - dotIndex <= 10;
|
|
373
|
+
const baseName = hasExtension ? name.slice(0, dotIndex) : name;
|
|
374
|
+
const extension = hasExtension ? name.slice(dotIndex) : '';
|
|
375
|
+
|
|
376
|
+
return `${baseName.slice(0, 5)}...${extension}`;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
_decorateValidationItem(item, file) {
|
|
380
|
+
if (this._isFileValid(file)) return item;
|
|
381
|
+
|
|
382
|
+
Classes.add(item, 'failing');
|
|
383
|
+
Classes.add(item, 'validation-error');
|
|
384
|
+
|
|
385
|
+
return item;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
_forgetFileValidation(file) {
|
|
389
|
+
if (!file) return;
|
|
390
|
+
|
|
391
|
+
this._validationErrorsByKey.delete(this._getFileKey(file));
|
|
392
|
+
this._errors.clear();
|
|
393
|
+
this._validationErrorsByKey.forEach(errors => {
|
|
394
|
+
errors.forEach(error => this._errors.add(error));
|
|
395
|
+
});
|
|
396
|
+
this._renderErrors();
|
|
397
|
+
}
|
|
324
398
|
|
|
325
399
|
_getSizes(size, isArray = false) {
|
|
326
400
|
const totalSize = isArray ? this._files.reduce((acc, f) => acc + f.size, 0) : size;
|
|
@@ -344,8 +418,12 @@ class VGFilesBase extends BaseModule {
|
|
|
344
418
|
}
|
|
345
419
|
}
|
|
346
420
|
|
|
347
|
-
_renderErrors() {
|
|
348
|
-
if (!this._errors.size)
|
|
421
|
+
_renderErrors() {
|
|
422
|
+
if (!this._errors.size) {
|
|
423
|
+
const $errorCont = Selectors.find(`.${this._getClass('errors')}`, this._element);
|
|
424
|
+
if ($errorCont) $errorCont.remove();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
349
427
|
|
|
350
428
|
const messages = lang_messages(this._params.lang, 'files') || this._getFallbackErrors();
|
|
351
429
|
let $errorCont = Selectors.find(`.${this._getClass('errors')}`, this._element);
|
|
@@ -357,10 +435,32 @@ class VGFilesBase extends BaseModule {
|
|
|
357
435
|
$errorCont.innerHTML = '';
|
|
358
436
|
}
|
|
359
437
|
|
|
360
|
-
this.
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
438
|
+
if (this._validationErrorsByKey.size) {
|
|
439
|
+
const renderedErrors = new Set();
|
|
440
|
+
this._files.forEach(file => {
|
|
441
|
+
const msg = this._getFileValidationLabel(file);
|
|
442
|
+
if (!msg) return;
|
|
443
|
+
|
|
444
|
+
const fileErrors = this._validationErrorsByKey.get(this._getFileKey(file));
|
|
445
|
+
fileErrors?.forEach(error => renderedErrors.add(error));
|
|
446
|
+
const fullMsg = this._getFullFileValidationLabel(file);
|
|
447
|
+
const attrs = { class: 'error-item' };
|
|
448
|
+
if (fullMsg && fullMsg !== msg) attrs.title = fullMsg;
|
|
449
|
+
$errorCont.append(this._tpl.span(attrs, msg));
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
this._errors.forEach(errKey => {
|
|
453
|
+
if (renderedErrors.has(errKey)) return;
|
|
454
|
+
const msg = messages[errKey] || errKey;
|
|
455
|
+
$errorCont.append(this._tpl.span({ class: 'error-item' }, msg));
|
|
456
|
+
});
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
this._errors.forEach(errKey => {
|
|
461
|
+
const msg = messages[errKey] || errKey;
|
|
462
|
+
$errorCont.append(this._tpl.span({ class: 'error-item' }, msg));
|
|
463
|
+
});
|
|
364
464
|
}
|
|
365
465
|
|
|
366
466
|
_getFallbackErrors() {
|
|
@@ -466,7 +566,7 @@ class VGFilesBase extends BaseModule {
|
|
|
466
566
|
parts
|
|
467
567
|
);
|
|
468
568
|
|
|
469
|
-
fragment.appendChild($li);
|
|
569
|
+
fragment.appendChild(this._decorateValidationItem($li, file));
|
|
470
570
|
});
|
|
471
571
|
|
|
472
572
|
$list.innerHTML = '';
|
|
@@ -641,10 +741,12 @@ class VGFilesBase extends BaseModule {
|
|
|
641
741
|
liAttrs['data-vg-filepreview-display-name'] = displayName || file.name || '';
|
|
642
742
|
}
|
|
643
743
|
|
|
644
|
-
|
|
645
|
-
this._buildFileDataAttributes(file, liAttrs),
|
|
646
|
-
parts
|
|
647
|
-
);
|
|
744
|
+
const item = this._tpl.li(
|
|
745
|
+
this._buildFileDataAttributes(file, liAttrs),
|
|
746
|
+
parts
|
|
747
|
+
);
|
|
748
|
+
|
|
749
|
+
return this._decorateValidationItem(item, file);
|
|
648
750
|
}
|
|
649
751
|
|
|
650
752
|
_renderTemplatePart(element, file, index = null, options = {}) {
|
|
@@ -926,7 +1028,7 @@ class VGFilesBase extends BaseModule {
|
|
|
926
1028
|
|
|
927
1029
|
if (isSingle) return;
|
|
928
1030
|
|
|
929
|
-
files.forEach((file, index) => {
|
|
1031
|
+
files.filter(file => this._isFileValid(file)).forEach((file, index) => {
|
|
930
1032
|
const input = document.createElement('input');
|
|
931
1033
|
input.type = 'file';
|
|
932
1034
|
input.name = `${baseName}[${index}]`;
|
|
@@ -968,9 +1070,10 @@ class VGFilesBase extends BaseModule {
|
|
|
968
1070
|
if (resetInput) {
|
|
969
1071
|
this._resetFileInput();
|
|
970
1072
|
}
|
|
971
|
-
this._cleanupFakeInputs();
|
|
972
|
-
this._cleanupErrors();
|
|
973
|
-
this.
|
|
1073
|
+
this._cleanupFakeInputs();
|
|
1074
|
+
this._cleanupErrors();
|
|
1075
|
+
this._validationErrorsByKey.clear();
|
|
1076
|
+
this._files = [];
|
|
974
1077
|
|
|
975
1078
|
if (this._nodes.info) {
|
|
976
1079
|
Classes.remove(this._nodes.info, 'show');
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
import { isElement } from "../../../utils/js/functions";
|
|
6
6
|
import EventHandler from "../../../utils/js/dom/event";
|
|
7
7
|
import Selectors from "../../../utils/js/dom/selectors";
|
|
8
|
-
import { Classes } from "../../../utils/js/dom/manipulator";
|
|
9
|
-
import BaseModule from "../../base-module";
|
|
8
|
+
import { Classes } from "../../../utils/js/dom/manipulator";
|
|
9
|
+
import BaseModule from "../../base-module";
|
|
10
|
+
import {isVGFilesSortableDragActive, VG_FILES_SORTABLE_DATA_TYPE} from "./sortable";
|
|
10
11
|
|
|
11
12
|
const CLASS_NAME_CONTAINER = 'vg-files';
|
|
12
13
|
const CLASS_NAME_DROP = `${CLASS_NAME_CONTAINER}-drop`;
|
|
@@ -253,14 +254,20 @@ class VGFilesDroppable extends BaseModule {
|
|
|
253
254
|
}
|
|
254
255
|
}
|
|
255
256
|
|
|
256
|
-
_isSortableDrag(e) {
|
|
257
|
-
if (
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
257
|
+
_isSortableDrag(e) {
|
|
258
|
+
if (isVGFilesSortableDragActive()) return true;
|
|
259
|
+
if (document.querySelector('.dragging')) return true;
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
const dataTransfer = e?.dataTransfer;
|
|
263
|
+
const types = Array.from(dataTransfer?.types || []);
|
|
264
|
+
if (types.includes(VG_FILES_SORTABLE_DATA_TYPE)) return true;
|
|
265
|
+
|
|
266
|
+
return (dataTransfer?.getData?.(VG_FILES_SORTABLE_DATA_TYPE) || '') === '1' ||
|
|
267
|
+
(dataTransfer?.getData?.('text/plain') || '') === 'vgsortable';
|
|
268
|
+
} catch (_) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
264
271
|
}
|
|
265
272
|
|
|
266
273
|
_getVisibleDropZonesInViewport() {
|
|
@@ -1,9 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Описание: управляет сортировкой уже загруженных файлов VGFiles.
|
|
3
|
+
* Возможности: меняет порядок элементов, сохраняет ID на сервере и маркирует внутренний drag для защиты dropzone.
|
|
4
|
+
*/
|
|
5
|
+
import Selectors from "../../../utils/js/dom/selectors";
|
|
6
|
+
import {isElement, normalizeData} from "../../../utils/js/functions";
|
|
7
|
+
import Ajax from "../../../utils/js/components/ajax";
|
|
8
|
+
import VGToast from "../../vgtoast";
|
|
9
|
+
|
|
10
|
+
export const VG_FILES_SORTABLE_DATA_TYPE = 'application/x-vg-files-sortable';
|
|
11
|
+
|
|
12
|
+
let isSortableDragActive = false;
|
|
13
|
+
let sortableDragClearTimer = null;
|
|
14
|
+
|
|
15
|
+
export function isVGFilesSortableDragActive() {
|
|
16
|
+
return isSortableDragActive;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function markSortableDragActive() {
|
|
20
|
+
if (sortableDragClearTimer) {
|
|
21
|
+
clearTimeout(sortableDragClearTimer);
|
|
22
|
+
sortableDragClearTimer = null;
|
|
23
|
+
}
|
|
24
|
+
isSortableDragActive = true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function scheduleSortableDragClear() {
|
|
28
|
+
if (sortableDragClearTimer) clearTimeout(sortableDragClearTimer);
|
|
29
|
+
sortableDragClearTimer = setTimeout(() => {
|
|
30
|
+
isSortableDragActive = false;
|
|
31
|
+
sortableDragClearTimer = null;
|
|
32
|
+
}, 0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
class VGFilesSortable {
|
|
7
36
|
constructor(vgFilesInstance, options = {}) {
|
|
8
37
|
this._vg = vgFilesInstance;
|
|
9
38
|
this._params = {
|
|
@@ -75,10 +104,12 @@ class VGFilesSortable {
|
|
|
75
104
|
const item = e.target.closest(this._params.itemSelector) || e.target.closest('li');
|
|
76
105
|
if (!item) return;
|
|
77
106
|
|
|
78
|
-
this._draggedItem = item;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
e.dataTransfer.
|
|
107
|
+
this._draggedItem = item;
|
|
108
|
+
markSortableDragActive();
|
|
109
|
+
|
|
110
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
111
|
+
e.dataTransfer.setData(VG_FILES_SORTABLE_DATA_TYPE, '1');
|
|
112
|
+
e.dataTransfer.setData('text/plain', 'vgsortable');
|
|
82
113
|
|
|
83
114
|
item.classList.add('dragging');
|
|
84
115
|
requestAnimationFrame(() => item.classList.add('dragging-transparent'));
|
|
@@ -87,9 +118,11 @@ class VGFilesSortable {
|
|
|
87
118
|
_onDragEnd(e) {
|
|
88
119
|
if (this._draggedItem) {
|
|
89
120
|
this._draggedItem.classList.remove('dragging', 'dragging-transparent');
|
|
90
|
-
this._draggedItem = null;
|
|
91
|
-
this._saveOrder();
|
|
92
|
-
}
|
|
121
|
+
this._draggedItem = null;
|
|
122
|
+
this._saveOrder();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
scheduleSortableDragClear();
|
|
93
126
|
}
|
|
94
127
|
|
|
95
128
|
_onDragOver(e) {
|
|
@@ -142,14 +175,15 @@ class VGFilesSortable {
|
|
|
142
175
|
}).filter(id => id !== null);
|
|
143
176
|
}
|
|
144
177
|
|
|
145
|
-
destroy() {
|
|
178
|
+
destroy() {
|
|
146
179
|
this._list.removeEventListener('dragstart', this._boundOnDragStart);
|
|
147
180
|
this._list.removeEventListener('dragend', this._boundOnDragEnd);
|
|
148
181
|
this._list.removeEventListener('dragover', this._boundOnDragOver);
|
|
149
182
|
this._list.removeEventListener('drop', this._boundOnDrop);
|
|
150
183
|
|
|
151
|
-
this._draggedItem = null;
|
|
152
|
-
|
|
153
|
-
}
|
|
184
|
+
this._draggedItem = null;
|
|
185
|
+
scheduleSortableDragClear();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
154
188
|
|
|
155
|
-
export default VGFilesSortable
|
|
189
|
+
export default VGFilesSortable
|
|
@@ -273,8 +273,11 @@ class VGFiles extends VGFilesBase {
|
|
|
273
273
|
this._failingUploadedKeys.clear();
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
-
const notUploadedFiles = files.filter(f =>
|
|
277
|
-
|
|
276
|
+
const notUploadedFiles = files.filter(f =>
|
|
277
|
+
this._isFileValid(f) && !this._uploadedKeys.has(this._getFileKey(f))
|
|
278
|
+
);
|
|
279
|
+
this._setStatItem('failing', this._getFailingCount());
|
|
280
|
+
if (!notUploadedFiles.length) return;
|
|
278
281
|
|
|
279
282
|
if (!this._uploader || this._uploader.isIdle()) {
|
|
280
283
|
this._uploader = new FileUploader({
|
|
@@ -481,7 +484,7 @@ class VGFiles extends VGFilesBase {
|
|
|
481
484
|
Classes.remove($item, CLASS_NAME_PENDING);
|
|
482
485
|
Classes.replace($item, CLASS_NAME_LOADING, CLASS_NAME_FAILING);
|
|
483
486
|
|
|
484
|
-
this._setStatItem('failing', this.
|
|
487
|
+
this._setStatItem('failing', this._getFailingCount());
|
|
485
488
|
this._setStatItem('pending', this._pendingUploadedKeys.size);
|
|
486
489
|
|
|
487
490
|
const button = this._getButtonElement(file);
|
|
@@ -509,7 +512,7 @@ class VGFiles extends VGFilesBase {
|
|
|
509
512
|
this._triggerEvent('upload.allComplete');
|
|
510
513
|
this._updateStat(false);
|
|
511
514
|
|
|
512
|
-
if (!this.
|
|
515
|
+
if (!this._getFailingCount()) {
|
|
513
516
|
if (this._params.sortable?.enabled && this._params.sortable.route) {
|
|
514
517
|
import('./sortable.js').then(module => {
|
|
515
518
|
if (this._sortable && typeof this._sortable.destroy === 'function') {
|
|
@@ -525,7 +528,7 @@ class VGFiles extends VGFilesBase {
|
|
|
525
528
|
|
|
526
529
|
const payload = {
|
|
527
530
|
uploaded: this._uploadedKeys.size,
|
|
528
|
-
failed: this.
|
|
531
|
+
failed: this._getFailingCount(),
|
|
529
532
|
total: this._files.length
|
|
530
533
|
};
|
|
531
534
|
|
|
@@ -693,12 +696,13 @@ class VGFiles extends VGFilesBase {
|
|
|
693
696
|
};
|
|
694
697
|
|
|
695
698
|
const fileToRemove = this._files.find(f => f.name === name && f.size === size);
|
|
696
|
-
if (fileToRemove) {
|
|
697
|
-
const key = this._getFileKey(fileToRemove);
|
|
698
|
-
this._uploadedKeys.delete(key);
|
|
699
|
-
this._pendingUploadedKeys.delete(key);
|
|
700
|
-
this._failingUploadedKeys.delete(key);
|
|
701
|
-
|
|
699
|
+
if (fileToRemove) {
|
|
700
|
+
const key = this._getFileKey(fileToRemove);
|
|
701
|
+
this._uploadedKeys.delete(key);
|
|
702
|
+
this._pendingUploadedKeys.delete(key);
|
|
703
|
+
this._failingUploadedKeys.delete(key);
|
|
704
|
+
this._forgetFileValidation(fileToRemove);
|
|
705
|
+
}
|
|
702
706
|
|
|
703
707
|
this._getItemElement().forEach(el => {
|
|
704
708
|
const btn = Selectors.find('button', el);
|
|
@@ -791,7 +795,7 @@ class VGFiles extends VGFilesBase {
|
|
|
791
795
|
_updateStatsAfterRemove() {
|
|
792
796
|
this._setStatItem('completed', this._uploadedKeys.size);
|
|
793
797
|
this._setStatItem('pending', this._pendingUploadedKeys.size);
|
|
794
|
-
this._setStatItem('failing', this.
|
|
798
|
+
this._setStatItem('failing', this._getFailingCount());
|
|
795
799
|
this._updateStat();
|
|
796
800
|
}
|
|
797
801
|
|
|
@@ -897,24 +901,35 @@ class VGFiles extends VGFilesBase {
|
|
|
897
901
|
if ($value) $value.innerHTML = count;
|
|
898
902
|
}
|
|
899
903
|
|
|
900
|
-
_renderUIStatusDropInfoAjax(files) {
|
|
901
|
-
if (!this._params.ajax) return;
|
|
902
|
-
|
|
903
|
-
files.forEach(file => {
|
|
904
|
+
_renderUIStatusDropInfoAjax(files) {
|
|
905
|
+
if (!this._params.ajax) return;
|
|
906
|
+
|
|
907
|
+
files.forEach(file => {
|
|
904
908
|
const $item = this._getItemElement(file);
|
|
905
909
|
if (!$item) return;
|
|
906
910
|
|
|
907
|
-
const key = this._getFileKey(file);
|
|
908
|
-
if (this.
|
|
911
|
+
const key = this._getFileKey(file);
|
|
912
|
+
if (!this._isFileValid(file)) {
|
|
913
|
+
Classes.remove($item, CLASS_NAME_PENDING);
|
|
914
|
+
Classes.add($item, CLASS_NAME_FAILING);
|
|
915
|
+
} else if (this._uploadedKeys.has(key)) {
|
|
909
916
|
Classes.replace($item, CLASS_NAME_PENDING, CLASS_NAME_COMPLETED);
|
|
910
917
|
Classes.add($item, CLASS_NAME_LOADED);
|
|
911
918
|
} else if (this._failingUploadedKeys.has(key)) {
|
|
912
919
|
Classes.replace($item, CLASS_NAME_PENDING, CLASS_NAME_FAILING);
|
|
913
920
|
} else {
|
|
914
|
-
Classes.add($item, CLASS_NAME_PENDING);
|
|
915
|
-
}
|
|
916
|
-
});
|
|
917
|
-
|
|
921
|
+
Classes.add($item, CLASS_NAME_PENDING);
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
this._setStatItem('failing', this._getFailingCount());
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
_getFailingCount() {
|
|
929
|
+
const keys = new Set(this._failingUploadedKeys);
|
|
930
|
+
this._validationErrorsByKey.forEach((errors, key) => keys.add(key));
|
|
931
|
+
return keys.size;
|
|
932
|
+
}
|
|
918
933
|
|
|
919
934
|
_triggerCallback(name, data) {
|
|
920
935
|
const cb = this._params.callbacks?.[name];
|
|
@@ -9,9 +9,16 @@ $files-map: (
|
|
|
9
9
|
label-padding: 8px 24px,
|
|
10
10
|
label-padding-icon: 8px 16px,
|
|
11
11
|
|
|
12
|
-
// errors
|
|
13
|
-
errors-padding-y: 10px,
|
|
14
|
-
errors-
|
|
12
|
+
// errors
|
|
13
|
+
errors-padding-y: 10px,
|
|
14
|
+
errors-padding-x: 12px,
|
|
15
|
+
errors-font-size: 16px,
|
|
16
|
+
errors-gap: 4px,
|
|
17
|
+
errors-margin-bottom: 8px,
|
|
18
|
+
errors-bg: var(--vg-danger-subtle-bg, rgba(255, 42, 44, .06)),
|
|
19
|
+
errors-border-color: var(--vg-danger-color, var(--vg-danger, #ff2a2c)),
|
|
20
|
+
errors-border-width: 3px,
|
|
21
|
+
errors-border-radius: 6px,
|
|
15
22
|
|
|
16
23
|
// drop zone
|
|
17
24
|
drop-bg: var(--vg-secondary-bg, #{vg.$surface-muted-bg}),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
2
|
*--------------------------------------------------------------------------
|
|
3
3
|
* Модуль: VGFiles
|
|
4
4
|
* Автор: Vegas DEV
|
|
@@ -61,12 +61,18 @@
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
&-errors {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
&-errors {
|
|
65
|
+
display: grid;
|
|
66
|
+
gap: var(--vg-files-errors-gap);
|
|
67
|
+
padding: var(--vg-files-errors-padding-y) var(--vg-files-errors-padding-x);
|
|
68
|
+
margin-bottom: var(--vg-files-errors-margin-bottom);
|
|
69
|
+
border-left: var(--vg-files-errors-border-width) solid var(--vg-files-errors-border-color);
|
|
70
|
+
border-radius: var(--vg-files-errors-border-radius);
|
|
71
|
+
background: var(--vg-files-errors-bg);
|
|
72
|
+
|
|
73
|
+
span {
|
|
74
|
+
font-size: var(--vg-files-errors-font-size);
|
|
75
|
+
color: var(--vg-danger-color, var(--vg-danger, #ff2a2c));
|
|
70
76
|
display: block;
|
|
71
77
|
overflow: hidden;
|
|
72
78
|
text-overflow: ellipsis;
|
|
@@ -517,14 +523,18 @@
|
|
|
517
523
|
}
|
|
518
524
|
}
|
|
519
525
|
|
|
520
|
-
.file-failing-actions {
|
|
526
|
+
.file-failing-actions {
|
|
521
527
|
display: inline-flex;
|
|
522
528
|
align-items: center;
|
|
523
529
|
gap: var(--vg-files-info-failing-actions-gap);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
&-info--list {
|
|
527
|
-
.file.
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
&-info--list {
|
|
533
|
+
.file.validation-error {
|
|
534
|
+
box-shadow: inset 0 0 0 2px var(--vg-danger-color, var(--vg-danger, #ff2a2c));
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.file.failing {
|
|
528
538
|
.file-remove {
|
|
529
539
|
margin-left: var(--vg-files-info-failing-actions-margin-start);
|
|
530
540
|
}
|
|
@@ -634,7 +644,7 @@
|
|
|
634
644
|
}
|
|
635
645
|
}
|
|
636
646
|
|
|
637
|
-
&--list {
|
|
647
|
+
&--list {
|
|
638
648
|
display: flex;
|
|
639
649
|
align-items: center;
|
|
640
650
|
gap: var(--vg-files-drop-list-gap);
|
|
@@ -646,7 +656,7 @@
|
|
|
646
656
|
border-radius: var(--vg-files-drop-radius);
|
|
647
657
|
overflow: hidden;
|
|
648
658
|
|
|
649
|
-
.file {
|
|
659
|
+
.file {
|
|
650
660
|
--vg-button-color: var(--vg-text-color, var(--vg-body-color, #000000));
|
|
651
661
|
border-radius: var(--vg-files-drop-file-radius);
|
|
652
662
|
position: relative;
|
|
@@ -655,7 +665,11 @@
|
|
|
655
665
|
height: var(--vg-files-drop-file-height);
|
|
656
666
|
|
|
657
667
|
@include files.vgfiles-sortable();
|
|
658
|
-
@include files.vgfiles-state();
|
|
668
|
+
@include files.vgfiles-state();
|
|
669
|
+
|
|
670
|
+
&.validation-error {
|
|
671
|
+
box-shadow: inset 0 0 0 2px var(--vg-danger-color, var(--vg-danger, #ff2a2c));
|
|
672
|
+
}
|
|
659
673
|
|
|
660
674
|
.file-image {
|
|
661
675
|
width: 100%;
|