vgapp 1.1.4 → 1.1.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.
- package/CHANGELOG.md +47 -21
- package/agents.md +9 -1
- package/app/modules/vgfilepreview/js/vgfilepreview.js +972 -594
- package/app/modules/vgfilepreview/scss/vgfilepreview.scss +4 -0
- package/app/modules/vgfiles/js/base.js +481 -463
- package/app/modules/vgmodal/js/vgmodal.drag.js +332 -0
- package/app/modules/vgmodal/js/vgmodal.js +105 -3
- package/app/modules/vgmodal/js/vgmodal.resize.js +435 -0
- package/app/modules/vgtoast/js/vgtoast.drag.js +335 -0
- package/app/modules/vgtoast/js/vgtoast.js +198 -76
- package/app/modules/vgtoast/js/vgtoast.resize.js +437 -0
- package/app/utils/js/components/video-metadata.js +140 -0
- package/build/vgapp.css +1 -1
- package/build/vgapp.css.map +1 -1
- package/package.json +1 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import BaseModule from "../../base-module";
|
|
2
2
|
import {mergeDeepObject} from "../../../utils/js/functions";
|
|
3
3
|
import Html from "../../../utils/js/components/templater";
|
|
4
|
-
import {lang_messages} from "../../../utils/js/components/lang";
|
|
5
|
-
import {Classes, Manipulator} from "../../../utils/js/dom/manipulator";
|
|
6
|
-
import Selectors from "../../../utils/js/dom/selectors";
|
|
7
|
-
import {getSVG} from "../../module-fn";
|
|
8
|
-
import VGFilePreview from "../../vgfilepreview";
|
|
9
|
-
import {extractAudioMetadata} from "../../../utils/js/components/audio-metadata";
|
|
4
|
+
import {lang_messages} from "../../../utils/js/components/lang";
|
|
5
|
+
import {Classes, Manipulator} from "../../../utils/js/dom/manipulator";
|
|
6
|
+
import Selectors from "../../../utils/js/dom/selectors";
|
|
7
|
+
import {getSVG} from "../../module-fn";
|
|
8
|
+
import VGFilePreview from "../../vgfilepreview";
|
|
9
|
+
import {extractAudioMetadata} from "../../../utils/js/components/audio-metadata";
|
|
10
10
|
|
|
11
11
|
class VGFilesBase extends BaseModule {
|
|
12
12
|
constructor(element, params = {}, defaults = {}) {
|
|
@@ -21,11 +21,11 @@ class VGFilesBase extends BaseModule {
|
|
|
21
21
|
}
|
|
22
22
|
this._isInitialized = true;
|
|
23
23
|
|
|
24
|
-
this._tpl = Html('dom');
|
|
25
|
-
this._files = [];
|
|
26
|
-
this._errors = new Set();
|
|
27
|
-
this._fileObjectUrls = new Map();
|
|
28
|
-
this._audioMetaPromises = new Map();
|
|
24
|
+
this._tpl = Html('dom');
|
|
25
|
+
this._files = [];
|
|
26
|
+
this._errors = new Set();
|
|
27
|
+
this._fileObjectUrls = new Map();
|
|
28
|
+
this._audioMetaPromises = new Map();
|
|
29
29
|
|
|
30
30
|
this._nodes = {
|
|
31
31
|
stat: Selectors.find(`.${this._getClass('stat')}`, this._element),
|
|
@@ -186,98 +186,98 @@ class VGFilesBase extends BaseModule {
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
append(values, replace = true) {
|
|
190
|
-
const incoming = this._applyRenameToIncomingFiles(Array.from(values));
|
|
191
|
-
let filesToProcess;
|
|
192
|
-
|
|
193
|
-
if (replace) {
|
|
194
|
-
filesToProcess = incoming;
|
|
195
|
-
} else {
|
|
196
|
-
filesToProcess = this._mergeFilesByOrder(incoming, this._files, Boolean(this._params.prepend));
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
this._files = this._filterFiles(filesToProcess);
|
|
200
|
-
|
|
201
|
-
this._renderErrors();
|
|
202
|
-
this._enrichAudioMetadata(this._files);
|
|
203
|
-
|
|
204
|
-
return this._files;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
_mergeFilesByOrder(incoming = [], existing = [], prepend = false) {
|
|
208
|
-
const ordered = prepend
|
|
209
|
-
? [...incoming, ...existing]
|
|
210
|
-
: [...existing, ...incoming];
|
|
211
|
-
|
|
212
|
-
const seen = new Set();
|
|
213
|
-
const result = [];
|
|
214
|
-
|
|
215
|
-
ordered.forEach((file) => {
|
|
216
|
-
const key = this._getFileKey(file);
|
|
217
|
-
if (seen.has(key)) {
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
seen.add(key);
|
|
222
|
-
result.push(file);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
return result;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
_getFileKey(file) {
|
|
229
|
-
return `${file.name}-${file.size}-${file.type}`;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
_getFileCustomData(file) {
|
|
233
|
-
const customData = file?.customData;
|
|
234
|
-
if (!customData || typeof customData !== 'object' || Array.isArray(customData)) return {};
|
|
235
|
-
return customData;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
_toDataAttributeKey(key) {
|
|
239
|
-
if (!key) return '';
|
|
240
|
-
|
|
241
|
-
return String(key)
|
|
242
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
243
|
-
.replace(/[_\s]+/g, '-')
|
|
244
|
-
.replace(/[^a-zA-Z0-9-]/g, '')
|
|
245
|
-
.replace(/-+/g, '-')
|
|
246
|
-
.replace(/^-|-$/g, '')
|
|
247
|
-
.toLowerCase();
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
_toDataAttributeValue(value) {
|
|
251
|
-
if (value === undefined || value === null || value === '') return null;
|
|
252
|
-
if (typeof value === 'object') {
|
|
253
|
-
try {
|
|
254
|
-
return JSON.stringify(value);
|
|
255
|
-
} catch (e) {
|
|
256
|
-
return String(value);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return value;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
_buildFileDataAttributes(file, baseAttrs = {}) {
|
|
263
|
-
const attrs = { ...baseAttrs };
|
|
264
|
-
const customData = this._getFileCustomData(file);
|
|
265
|
-
|
|
266
|
-
Object.entries(customData).forEach(([key, value]) => {
|
|
267
|
-
const attrKey = this._toDataAttributeKey(key);
|
|
268
|
-
if (!attrKey) return;
|
|
269
|
-
|
|
270
|
-
const attrName = `data-${attrKey}`;
|
|
271
|
-
if (Object.prototype.hasOwnProperty.call(attrs, attrName)) return;
|
|
272
|
-
|
|
273
|
-
const attrValue = this._toDataAttributeValue(value);
|
|
274
|
-
if (attrValue === null) return;
|
|
275
|
-
|
|
276
|
-
attrs[attrName] = attrValue;
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
return attrs;
|
|
280
|
-
}
|
|
189
|
+
append(values, replace = true) {
|
|
190
|
+
const incoming = this._applyRenameToIncomingFiles(Array.from(values));
|
|
191
|
+
let filesToProcess;
|
|
192
|
+
|
|
193
|
+
if (replace) {
|
|
194
|
+
filesToProcess = incoming;
|
|
195
|
+
} else {
|
|
196
|
+
filesToProcess = this._mergeFilesByOrder(incoming, this._files, Boolean(this._params.prepend));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this._files = this._filterFiles(filesToProcess);
|
|
200
|
+
|
|
201
|
+
this._renderErrors();
|
|
202
|
+
this._enrichAudioMetadata(this._files);
|
|
203
|
+
|
|
204
|
+
return this._files;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
_mergeFilesByOrder(incoming = [], existing = [], prepend = false) {
|
|
208
|
+
const ordered = prepend
|
|
209
|
+
? [...incoming, ...existing]
|
|
210
|
+
: [...existing, ...incoming];
|
|
211
|
+
|
|
212
|
+
const seen = new Set();
|
|
213
|
+
const result = [];
|
|
214
|
+
|
|
215
|
+
ordered.forEach((file) => {
|
|
216
|
+
const key = this._getFileKey(file);
|
|
217
|
+
if (seen.has(key)) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
seen.add(key);
|
|
222
|
+
result.push(file);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
_getFileKey(file) {
|
|
229
|
+
return `${file.name}-${file.size}-${file.type}`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
_getFileCustomData(file) {
|
|
233
|
+
const customData = file?.customData;
|
|
234
|
+
if (!customData || typeof customData !== 'object' || Array.isArray(customData)) return {};
|
|
235
|
+
return customData;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
_toDataAttributeKey(key) {
|
|
239
|
+
if (!key) return '';
|
|
240
|
+
|
|
241
|
+
return String(key)
|
|
242
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
243
|
+
.replace(/[_\s]+/g, '-')
|
|
244
|
+
.replace(/[^a-zA-Z0-9-]/g, '')
|
|
245
|
+
.replace(/-+/g, '-')
|
|
246
|
+
.replace(/^-|-$/g, '')
|
|
247
|
+
.toLowerCase();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
_toDataAttributeValue(value) {
|
|
251
|
+
if (value === undefined || value === null || value === '') return null;
|
|
252
|
+
if (typeof value === 'object') {
|
|
253
|
+
try {
|
|
254
|
+
return JSON.stringify(value);
|
|
255
|
+
} catch (e) {
|
|
256
|
+
return String(value);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return value;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
_buildFileDataAttributes(file, baseAttrs = {}) {
|
|
263
|
+
const attrs = { ...baseAttrs };
|
|
264
|
+
const customData = this._getFileCustomData(file);
|
|
265
|
+
|
|
266
|
+
Object.entries(customData).forEach(([key, value]) => {
|
|
267
|
+
const attrKey = this._toDataAttributeKey(key);
|
|
268
|
+
if (!attrKey) return;
|
|
269
|
+
|
|
270
|
+
const attrName = `data-${attrKey}`;
|
|
271
|
+
if (Object.prototype.hasOwnProperty.call(attrs, attrName)) return;
|
|
272
|
+
|
|
273
|
+
const attrValue = this._toDataAttributeValue(value);
|
|
274
|
+
if (attrValue === null) return;
|
|
275
|
+
|
|
276
|
+
attrs[attrName] = attrValue;
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
return attrs;
|
|
280
|
+
}
|
|
281
281
|
|
|
282
282
|
_filterFiles(files) {
|
|
283
283
|
this._errors.clear();
|
|
@@ -454,15 +454,15 @@ class VGFilesBase extends BaseModule {
|
|
|
454
454
|
if (part) parts.push(part);
|
|
455
455
|
});
|
|
456
456
|
|
|
457
|
-
const $li = this._tpl.li(
|
|
458
|
-
this._buildFileDataAttributes(file, {
|
|
459
|
-
'data-name': file.name,
|
|
460
|
-
'data-size': file.size ?? 0,
|
|
461
|
-
'data-id': file.id || '',
|
|
462
|
-
class: 'file ' + classes.join(' ')
|
|
463
|
-
}),
|
|
464
|
-
parts
|
|
465
|
-
);
|
|
457
|
+
const $li = this._tpl.li(
|
|
458
|
+
this._buildFileDataAttributes(file, {
|
|
459
|
+
'data-name': file.name,
|
|
460
|
+
'data-size': file.size ?? 0,
|
|
461
|
+
'data-id': file.id || '',
|
|
462
|
+
class: 'file ' + classes.join(' ')
|
|
463
|
+
}),
|
|
464
|
+
parts
|
|
465
|
+
);
|
|
466
466
|
|
|
467
467
|
fragment.appendChild($li);
|
|
468
468
|
});
|
|
@@ -486,8 +486,8 @@ class VGFilesBase extends BaseModule {
|
|
|
486
486
|
Classes.add(this._nodes.drop, 'active');
|
|
487
487
|
}
|
|
488
488
|
|
|
489
|
-
_renderInfoList(files) {
|
|
490
|
-
if (!this._nodes.info) return;
|
|
489
|
+
_renderInfoList(files) {
|
|
490
|
+
if (!this._nodes.info) return;
|
|
491
491
|
|
|
492
492
|
let $list = Selectors.find(`.${this._getClass('info-list')}`, this._element);
|
|
493
493
|
if (!$list) {
|
|
@@ -495,149 +495,150 @@ class VGFilesBase extends BaseModule {
|
|
|
495
495
|
this._nodes.info.appendChild($list);
|
|
496
496
|
}
|
|
497
497
|
|
|
498
|
-
if (!this._params.info) Classes.add($list, 'list-row');
|
|
499
|
-
|
|
500
|
-
const $itemsTemplate = this._parseTemplate().children;
|
|
501
|
-
const $itemsTemplateClasses = this._parseTemplate().liClasses.filter(cls => cls !== 'file');
|
|
502
|
-
const currentChildren = Array.from($list.children || []);
|
|
503
|
-
const existingByKey = new Map();
|
|
504
|
-
currentChildren.forEach((child) => {
|
|
505
|
-
const key = this._getInfoNodeKey(child);
|
|
506
|
-
if (key) {
|
|
507
|
-
if (!child.hasAttribute('data-file-key')) {
|
|
508
|
-
child.setAttribute('data-file-key', key);
|
|
509
|
-
}
|
|
510
|
-
existingByKey.set(key, child);
|
|
511
|
-
}
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
const nextNodes = [];
|
|
515
|
-
|
|
516
|
-
files.forEach((file, i) => {
|
|
517
|
-
const fileKey = this._getFileKey(file);
|
|
518
|
-
const signature = this._getInfoItemSignature(file, i);
|
|
519
|
-
const existingNode = existingByKey.get(fileKey);
|
|
520
|
-
|
|
521
|
-
if (existingNode && existingNode.getAttribute('data-vg-info-signature') === signature) {
|
|
522
|
-
const iterationNode = existingNode.querySelector('.iteration');
|
|
523
|
-
if (iterationNode) {
|
|
524
|
-
iterationNode.textContent = `${i + 1}.`;
|
|
525
|
-
}
|
|
526
|
-
nextNodes.push(existingNode);
|
|
527
|
-
existingByKey.delete(fileKey);
|
|
528
|
-
return;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
if (existingNode) {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
const
|
|
558
|
-
const
|
|
559
|
-
const
|
|
560
|
-
const
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const
|
|
591
|
-
const
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
if (this._params.
|
|
608
|
-
if (this._params.
|
|
609
|
-
if (this._params.
|
|
610
|
-
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
'data-
|
|
622
|
-
'data-
|
|
623
|
-
'data-
|
|
624
|
-
'data-
|
|
625
|
-
'data-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
liAttrs['data-
|
|
632
|
-
liAttrs['data-
|
|
633
|
-
liAttrs['data-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
498
|
+
if (!this._params.info) Classes.add($list, 'list-row');
|
|
499
|
+
|
|
500
|
+
const $itemsTemplate = this._parseTemplate().children;
|
|
501
|
+
const $itemsTemplateClasses = this._parseTemplate().liClasses.filter(cls => cls !== 'file');
|
|
502
|
+
const currentChildren = Array.from($list.children || []);
|
|
503
|
+
const existingByKey = new Map();
|
|
504
|
+
currentChildren.forEach((child) => {
|
|
505
|
+
const key = this._getInfoNodeKey(child);
|
|
506
|
+
if (key) {
|
|
507
|
+
if (!child.hasAttribute('data-file-key')) {
|
|
508
|
+
child.setAttribute('data-file-key', key);
|
|
509
|
+
}
|
|
510
|
+
existingByKey.set(key, child);
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
const nextNodes = [];
|
|
515
|
+
|
|
516
|
+
files.forEach((file, i) => {
|
|
517
|
+
const fileKey = this._getFileKey(file);
|
|
518
|
+
const signature = this._getInfoItemSignature(file, i);
|
|
519
|
+
const existingNode = existingByKey.get(fileKey);
|
|
520
|
+
|
|
521
|
+
if (existingNode && existingNode.getAttribute('data-vg-info-signature') === signature) {
|
|
522
|
+
const iterationNode = existingNode.querySelector('.iteration');
|
|
523
|
+
if (iterationNode) {
|
|
524
|
+
iterationNode.textContent = `${i + 1}.`;
|
|
525
|
+
}
|
|
526
|
+
nextNodes.push(existingNode);
|
|
527
|
+
existingByKey.delete(fileKey);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (existingNode) {
|
|
532
|
+
VGFilePreview.stopActiveInlineAudioIfDetached([existingNode]);
|
|
533
|
+
existingNode.remove();
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const nextNode = this._createInfoListItem(file, i, $itemsTemplate, $itemsTemplateClasses, fileKey, signature);
|
|
537
|
+
nextNodes.push(nextNode);
|
|
538
|
+
existingByKey.delete(fileKey);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
if (existingByKey.size) {
|
|
542
|
+
const removedNodes = Array.from(existingByKey.values());
|
|
543
|
+
VGFilePreview.stopActiveInlineAudioIfDetached(removedNodes);
|
|
544
|
+
removedNodes.forEach((node) => node.remove());
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
nextNodes.forEach((node) => {
|
|
548
|
+
$list.appendChild(node);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
this._initFilePreviewInInfo($list);
|
|
552
|
+
|
|
553
|
+
Classes.add(this._nodes.info, 'show')
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
_getInfoItemSignature(file, index) {
|
|
557
|
+
const displayName = this._resolveDisplayName(file);
|
|
558
|
+
const previewPath = this._resolveFilePreviewPath(file);
|
|
559
|
+
const id = file?.id || '';
|
|
560
|
+
const name = file?.name || '';
|
|
561
|
+
const size = file?.size ?? 0;
|
|
562
|
+
const type = file?.type || '';
|
|
563
|
+
|
|
564
|
+
return `${id}|${name}|${size}|${type}|${displayName}|${previewPath}`;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
_getInfoNodeKey(node) {
|
|
568
|
+
if (!node || typeof node.getAttribute !== 'function') {
|
|
569
|
+
return '';
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const fromAttr = String(node.getAttribute('data-file-key') || '').trim();
|
|
573
|
+
if (fromAttr) {
|
|
574
|
+
return fromAttr;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const name = String(node.getAttribute('data-name') || '').trim();
|
|
578
|
+
const size = String(node.getAttribute('data-size') || '').trim();
|
|
579
|
+
const type = String(node.getAttribute('data-type') || '').trim();
|
|
580
|
+
if (name && size && type) {
|
|
581
|
+
return `${name}-${size}-${type}`;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const raw = String(node.getAttribute('data-file') || '').trim();
|
|
585
|
+
if (!raw) {
|
|
586
|
+
return '';
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
try {
|
|
590
|
+
const parsed = JSON.parse(raw);
|
|
591
|
+
const pName = String(parsed?.name || '').trim();
|
|
592
|
+
const pSize = String(parsed?.size ?? '').trim();
|
|
593
|
+
const pType = String(parsed?.type || '').trim();
|
|
594
|
+
if (pName && pSize && pType) {
|
|
595
|
+
return `${pName}-${pSize}-${pType}`;
|
|
596
|
+
}
|
|
597
|
+
} catch {
|
|
598
|
+
return '';
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
return '';
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
_createInfoListItem(file, i, itemsTemplate, itemTemplateClasses, fileKey, signature) {
|
|
605
|
+
let classes = [...itemTemplateClasses];
|
|
606
|
+
|
|
607
|
+
if (this._params.image) classes.push('with-image');
|
|
608
|
+
if (this._params.info) classes.push('with-info');
|
|
609
|
+
if (this._params.detach) classes.push('with-remove')
|
|
610
|
+
if (this._params.sortable.enabled) classes.push('with-sortable');
|
|
611
|
+
const previewPath = this._resolveFilePreviewPath(file);
|
|
612
|
+
const displayName = this._resolveDisplayName(file);
|
|
613
|
+
|
|
614
|
+
let parts = [];
|
|
615
|
+
itemsTemplate.forEach(tmpl => {
|
|
616
|
+
const part = this._renderTemplatePart(tmpl.element, file, i);
|
|
617
|
+
if (part) parts.push(part);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
const liAttrs = {
|
|
621
|
+
'data-name': file.name,
|
|
622
|
+
'data-size': file.size ?? 0,
|
|
623
|
+
'data-type': file.type || '',
|
|
624
|
+
'data-id': file.id || '',
|
|
625
|
+
'data-file-key': fileKey,
|
|
626
|
+
'data-vg-info-signature': signature,
|
|
627
|
+
class: 'file ' + classes.join(' ') + ' '
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
if (previewPath) {
|
|
631
|
+
liAttrs['data-vg-filepreview'] = previewPath;
|
|
632
|
+
liAttrs['data-fields'] = 'name,size,download';
|
|
633
|
+
liAttrs['data-original-name'] = file.name || '';
|
|
634
|
+
liAttrs['data-vg-filepreview-display-name'] = displayName || file.name || '';
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return this._tpl.li(
|
|
638
|
+
this._buildFileDataAttributes(file, liAttrs),
|
|
639
|
+
parts
|
|
640
|
+
);
|
|
641
|
+
}
|
|
641
642
|
|
|
642
643
|
_renderTemplatePart(element, file, index = null, options = {}) {
|
|
643
644
|
if (!element) return null;
|
|
@@ -691,205 +692,205 @@ class VGFilesBase extends BaseModule {
|
|
|
691
692
|
}
|
|
692
693
|
}
|
|
693
694
|
|
|
694
|
-
_renderUIInfo(file, i) {
|
|
695
|
-
if (this._params.info) {
|
|
696
|
-
const displayName = this._resolveDisplayName(file);
|
|
697
|
-
return this._tpl.div({ class: 'file-info' }, [
|
|
698
|
-
this._tpl.span({ class: 'iteration' }, `${i + 1}.`),
|
|
699
|
-
this._tpl.span({ class: 'name' }, displayName),
|
|
700
|
-
this._tpl.span({ class: 'size' }, `[${this._getSizes(file.size)}]`),
|
|
701
|
-
this._tpl.span({ class: 'download' }, '')
|
|
702
|
-
]);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
_renderUIImage(file) {
|
|
707
|
-
const $container = this._tpl.div({ class: 'file-image' });
|
|
708
|
-
|
|
709
|
-
const src = file?.src || file?.image;
|
|
710
|
-
if (src) {
|
|
711
|
-
$container.appendChild(this._tpl.img(src, file.name || '', { class: 'file-preview' }));
|
|
712
|
-
return $container;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
const customData = this._getFileCustomData(file);
|
|
716
|
-
const audioCover = String(customData.audioCover || '').trim();
|
|
717
|
-
if (audioCover) {
|
|
718
|
-
$container.appendChild(this._tpl.img(audioCover, this._resolveDisplayName(file), { class: 'file-preview' }));
|
|
719
|
-
return $container;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
if (file?.type && file.type.startsWith('image/')) {
|
|
723
|
-
const objectUrl = this._getFileObjectUrl(file);
|
|
724
|
-
if (!objectUrl) {
|
|
725
|
-
return $container;
|
|
726
|
-
}
|
|
727
|
-
$container.appendChild(this._tpl.img(objectUrl, file.name, { class: 'file-preview' }));
|
|
728
|
-
return $container;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
const icon = this._getIconByFileType(file);
|
|
732
|
-
$container.appendChild(this._tpl.i({}, icon, { isHTML: true }));
|
|
733
|
-
return $container;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
_resolveDisplayName(file) {
|
|
737
|
-
const customData = this._getFileCustomData(file);
|
|
738
|
-
const metaTitle = String(customData.audioTitle || '').trim();
|
|
739
|
-
if (metaTitle) {
|
|
740
|
-
return metaTitle;
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
return String(file?.name || '').trim();
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
_getIconByFileType(file) {
|
|
747
|
-
return getSVG(file);
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
_resolveFilePreviewPath(file) {
|
|
751
|
-
const src = String(file?.src || file?.image || '').trim();
|
|
752
|
-
if (src) {
|
|
753
|
-
return src;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
return this._getFileObjectUrl(file);
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
_getFileObjectUrl(file) {
|
|
760
|
-
if (!file || typeof File === 'undefined' || !(file instanceof File)) {
|
|
761
|
-
return '';
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
const key = this._getFileKey(file);
|
|
765
|
-
if (this._fileObjectUrls.has(key)) {
|
|
766
|
-
return this._fileObjectUrls.get(key);
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
const objectUrl = URL.createObjectURL(file);
|
|
770
|
-
this._fileObjectUrls.set(key, objectUrl);
|
|
771
|
-
return objectUrl;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
_isAudioFile(file) {
|
|
775
|
-
if (!file) {
|
|
776
|
-
return false;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
const fileType = String(file.type || '').toLowerCase();
|
|
780
|
-
if (fileType.startsWith('audio/')) {
|
|
781
|
-
return true;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
const name = String(file.name || '').toLowerCase();
|
|
785
|
-
return /\.(mp3|m4a|aac|wav|ogg|flac|opus|wma)$/.test(name);
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
_setFileCustomDataValue(file, key, value) {
|
|
789
|
-
if (!file || !key) {
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
if (!file.customData || typeof file.customData !== 'object' || Array.isArray(file.customData)) {
|
|
794
|
-
Object.defineProperty(file, 'customData', {
|
|
795
|
-
value: {},
|
|
796
|
-
writable: true,
|
|
797
|
-
enumerable: true
|
|
798
|
-
});
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
file.customData[key] = value;
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
_enrichAudioMetadata(files = []) {
|
|
805
|
-
if (!Array.isArray(files) || !files.length) {
|
|
806
|
-
return;
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
const pending = [];
|
|
810
|
-
|
|
811
|
-
files.forEach((file) => {
|
|
812
|
-
if (!this._isAudioFile(file)) {
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
const fileKey = this._getFileKey(file);
|
|
817
|
-
if (this._audioMetaPromises.has(fileKey)) {
|
|
818
|
-
return;
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
const customData = this._getFileCustomData(file);
|
|
822
|
-
if (customData.audioTitle || customData.audioCover) {
|
|
823
|
-
return;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
const task = extractAudioMetadata(file)
|
|
827
|
-
.then((meta) => {
|
|
828
|
-
if (!meta) {
|
|
829
|
-
return false;
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
let changed = false;
|
|
833
|
-
|
|
834
|
-
if (meta.title) {
|
|
835
|
-
this._setFileCustomDataValue(file, 'audioTitle', meta.title);
|
|
836
|
-
changed = true;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
if (meta.pictureBlob) {
|
|
840
|
-
const coverKey = `cover:${fileKey}`;
|
|
841
|
-
let coverUrl = this._fileObjectUrls.get(coverKey);
|
|
842
|
-
if (!coverUrl) {
|
|
843
|
-
coverUrl = URL.createObjectURL(meta.pictureBlob);
|
|
844
|
-
this._fileObjectUrls.set(coverKey, coverUrl);
|
|
845
|
-
}
|
|
846
|
-
this._setFileCustomDataValue(file, 'audioCover', coverUrl);
|
|
847
|
-
changed = true;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
return changed;
|
|
851
|
-
})
|
|
852
|
-
.catch(() => false)
|
|
853
|
-
.then((result) => {
|
|
854
|
-
this._audioMetaPromises.delete(fileKey);
|
|
855
|
-
return result;
|
|
856
|
-
});
|
|
857
|
-
|
|
858
|
-
this._audioMetaPromises.set(fileKey, task);
|
|
859
|
-
pending.push(task);
|
|
860
|
-
});
|
|
861
|
-
|
|
862
|
-
if (!pending.length) {
|
|
863
|
-
return;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
Promise.allSettled(pending).then((results) => {
|
|
867
|
-
const hasChanges = results.some((item) => item.status === 'fulfilled' && item.value === true);
|
|
868
|
-
if (!hasChanges || !this._files.length) {
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
this._renderUI(this._files);
|
|
873
|
-
});
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
_initFilePreviewInInfo(root) {
|
|
877
|
-
if (!this._nodes.info || !root) {
|
|
878
|
-
return;
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
const lang = this._params?.lang || document.documentElement.lang || 'ru';
|
|
882
|
-
const nodes = Selectors.findAll('[data-vg-filepreview]', root) || [];
|
|
883
|
-
|
|
884
|
-
nodes.forEach((node) => {
|
|
885
|
-
VGFilePreview.getOrCreateInstance(node, {
|
|
886
|
-
lang,
|
|
887
|
-
ui: {
|
|
888
|
-
nameOnly: true
|
|
889
|
-
}
|
|
890
|
-
});
|
|
891
|
-
});
|
|
892
|
-
}
|
|
695
|
+
_renderUIInfo(file, i) {
|
|
696
|
+
if (this._params.info) {
|
|
697
|
+
const displayName = this._resolveDisplayName(file);
|
|
698
|
+
return this._tpl.div({ class: 'file-info' }, [
|
|
699
|
+
this._tpl.span({ class: 'iteration' }, `${i + 1}.`),
|
|
700
|
+
this._tpl.span({ class: 'name' }, displayName),
|
|
701
|
+
this._tpl.span({ class: 'size' }, `[${this._getSizes(file.size)}]`),
|
|
702
|
+
this._tpl.span({ class: 'download' }, '')
|
|
703
|
+
]);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
_renderUIImage(file) {
|
|
708
|
+
const $container = this._tpl.div({ class: 'file-image' });
|
|
709
|
+
|
|
710
|
+
const src = file?.src || file?.image;
|
|
711
|
+
if (src) {
|
|
712
|
+
$container.appendChild(this._tpl.img(src, file.name || '', { class: 'file-preview' }));
|
|
713
|
+
return $container;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const customData = this._getFileCustomData(file);
|
|
717
|
+
const audioCover = String(customData.audioCover || '').trim();
|
|
718
|
+
if (audioCover) {
|
|
719
|
+
$container.appendChild(this._tpl.img(audioCover, this._resolveDisplayName(file), { class: 'file-preview' }));
|
|
720
|
+
return $container;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (file?.type && file.type.startsWith('image/')) {
|
|
724
|
+
const objectUrl = this._getFileObjectUrl(file);
|
|
725
|
+
if (!objectUrl) {
|
|
726
|
+
return $container;
|
|
727
|
+
}
|
|
728
|
+
$container.appendChild(this._tpl.img(objectUrl, file.name, { class: 'file-preview' }));
|
|
729
|
+
return $container;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const icon = this._getIconByFileType(file);
|
|
733
|
+
$container.appendChild(this._tpl.i({}, icon, { isHTML: true }));
|
|
734
|
+
return $container;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
_resolveDisplayName(file) {
|
|
738
|
+
const customData = this._getFileCustomData(file);
|
|
739
|
+
const metaTitle = String(customData.audioTitle || '').trim();
|
|
740
|
+
if (metaTitle) {
|
|
741
|
+
return metaTitle;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
return String(file?.name || '').trim();
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
_getIconByFileType(file) {
|
|
748
|
+
return getSVG(file);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
_resolveFilePreviewPath(file) {
|
|
752
|
+
const src = String(file?.src || file?.image || '').trim();
|
|
753
|
+
if (src) {
|
|
754
|
+
return src;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return this._getFileObjectUrl(file);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
_getFileObjectUrl(file) {
|
|
761
|
+
if (!file || typeof File === 'undefined' || !(file instanceof File)) {
|
|
762
|
+
return '';
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
const key = this._getFileKey(file);
|
|
766
|
+
if (this._fileObjectUrls.has(key)) {
|
|
767
|
+
return this._fileObjectUrls.get(key);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const objectUrl = URL.createObjectURL(file);
|
|
771
|
+
this._fileObjectUrls.set(key, objectUrl);
|
|
772
|
+
return objectUrl;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
_isAudioFile(file) {
|
|
776
|
+
if (!file) {
|
|
777
|
+
return false;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const fileType = String(file.type || '').toLowerCase();
|
|
781
|
+
if (fileType.startsWith('audio/')) {
|
|
782
|
+
return true;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const name = String(file.name || '').toLowerCase();
|
|
786
|
+
return /\.(mp3|m4a|aac|wav|ogg|flac|opus|wma)$/.test(name);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
_setFileCustomDataValue(file, key, value) {
|
|
790
|
+
if (!file || !key) {
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
if (!file.customData || typeof file.customData !== 'object' || Array.isArray(file.customData)) {
|
|
795
|
+
Object.defineProperty(file, 'customData', {
|
|
796
|
+
value: {},
|
|
797
|
+
writable: true,
|
|
798
|
+
enumerable: true
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
file.customData[key] = value;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
_enrichAudioMetadata(files = []) {
|
|
806
|
+
if (!Array.isArray(files) || !files.length) {
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const pending = [];
|
|
811
|
+
|
|
812
|
+
files.forEach((file) => {
|
|
813
|
+
if (!this._isAudioFile(file)) {
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const fileKey = this._getFileKey(file);
|
|
818
|
+
if (this._audioMetaPromises.has(fileKey)) {
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const customData = this._getFileCustomData(file);
|
|
823
|
+
if (customData.audioTitle || customData.audioCover) {
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
const task = extractAudioMetadata(file)
|
|
828
|
+
.then((meta) => {
|
|
829
|
+
if (!meta) {
|
|
830
|
+
return false;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
let changed = false;
|
|
834
|
+
|
|
835
|
+
if (meta.title) {
|
|
836
|
+
this._setFileCustomDataValue(file, 'audioTitle', meta.title);
|
|
837
|
+
changed = true;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (meta.pictureBlob) {
|
|
841
|
+
const coverKey = `cover:${fileKey}`;
|
|
842
|
+
let coverUrl = this._fileObjectUrls.get(coverKey);
|
|
843
|
+
if (!coverUrl) {
|
|
844
|
+
coverUrl = URL.createObjectURL(meta.pictureBlob);
|
|
845
|
+
this._fileObjectUrls.set(coverKey, coverUrl);
|
|
846
|
+
}
|
|
847
|
+
this._setFileCustomDataValue(file, 'audioCover', coverUrl);
|
|
848
|
+
changed = true;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
return changed;
|
|
852
|
+
})
|
|
853
|
+
.catch(() => false)
|
|
854
|
+
.then((result) => {
|
|
855
|
+
this._audioMetaPromises.delete(fileKey);
|
|
856
|
+
return result;
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
this._audioMetaPromises.set(fileKey, task);
|
|
860
|
+
pending.push(task);
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
if (!pending.length) {
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
Promise.allSettled(pending).then((results) => {
|
|
868
|
+
const hasChanges = results.some((item) => item.status === 'fulfilled' && item.value === true);
|
|
869
|
+
if (!hasChanges || !this._files.length) {
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
this._renderUI(this._files);
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
_initFilePreviewInInfo(root) {
|
|
878
|
+
if (!this._nodes.info || !root) {
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
const lang = this._params?.lang || document.documentElement.lang || 'ru';
|
|
883
|
+
const nodes = Selectors.findAll('[data-vg-filepreview]', root) || [];
|
|
884
|
+
|
|
885
|
+
nodes.forEach((node) => {
|
|
886
|
+
VGFilePreview.getOrCreateInstance(node, {
|
|
887
|
+
lang,
|
|
888
|
+
ui: {
|
|
889
|
+
nameOnly: true
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
});
|
|
893
|
+
}
|
|
893
894
|
|
|
894
895
|
_updateStat() {
|
|
895
896
|
if (!this._nodes.stat) return;
|
|
@@ -935,6 +936,23 @@ class VGFilesBase extends BaseModule {
|
|
|
935
936
|
}
|
|
936
937
|
|
|
937
938
|
clear(resetInput = true) {
|
|
939
|
+
const detachedNodes = [];
|
|
940
|
+
if (this._nodes.info) {
|
|
941
|
+
const $infoList = Selectors.find(`.${this._getClass('info-list')}`, this._element);
|
|
942
|
+
if ($infoList?.children?.length) {
|
|
943
|
+
detachedNodes.push(...Array.from($infoList.children));
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
if (this._nodes.drop) {
|
|
947
|
+
const $dropList = Selectors.find(`.${this._getClass('drop-list')}`, this._element);
|
|
948
|
+
if ($dropList?.children?.length) {
|
|
949
|
+
detachedNodes.push(...Array.from($dropList.children));
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
if (detachedNodes.length) {
|
|
953
|
+
VGFilePreview.stopActiveInlineAudioIfDetached(detachedNodes);
|
|
954
|
+
}
|
|
955
|
+
|
|
938
956
|
this._revokeUrls();
|
|
939
957
|
if (resetInput) {
|
|
940
958
|
this._resetFileInput();
|
|
@@ -969,13 +987,13 @@ class VGFilesBase extends BaseModule {
|
|
|
969
987
|
}
|
|
970
988
|
}
|
|
971
989
|
|
|
972
|
-
_revokeUrls() {
|
|
973
|
-
this._fileObjectUrls.forEach((url) => {
|
|
974
|
-
URL.revokeObjectURL(url);
|
|
975
|
-
});
|
|
976
|
-
this._fileObjectUrls.clear();
|
|
977
|
-
this._audioMetaPromises.clear();
|
|
978
|
-
}
|
|
990
|
+
_revokeUrls() {
|
|
991
|
+
this._fileObjectUrls.forEach((url) => {
|
|
992
|
+
URL.revokeObjectURL(url);
|
|
993
|
+
});
|
|
994
|
+
this._fileObjectUrls.clear();
|
|
995
|
+
this._audioMetaPromises.clear();
|
|
996
|
+
}
|
|
979
997
|
|
|
980
998
|
_resetFileInput() {
|
|
981
999
|
Selectors.findAll('[data-vg-toggle="files"]', this._element).forEach(input => input.value = '');
|