vgapp 1.1.1 → 1.1.3

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +19 -16
  3. package/app/langs/en/buttons.json +17 -2
  4. package/app/langs/en/messages.json +36 -1
  5. package/app/langs/ru/buttons.json +17 -2
  6. package/app/langs/ru/messages.json +69 -34
  7. package/app/modules/module-fn.js +15 -9
  8. package/app/modules/vgfilepreview/index.js +3 -0
  9. package/app/modules/vgfilepreview/js/i18n.js +56 -0
  10. package/app/modules/vgfilepreview/js/renderers/image-modal.js +145 -0
  11. package/app/modules/vgfilepreview/js/renderers/image.js +92 -0
  12. package/app/modules/vgfilepreview/js/renderers/index.js +19 -0
  13. package/app/modules/vgfilepreview/js/renderers/office-modal.js +168 -0
  14. package/app/modules/vgfilepreview/js/renderers/office.js +79 -0
  15. package/app/modules/vgfilepreview/js/renderers/pdf-modal.js +260 -0
  16. package/app/modules/vgfilepreview/js/renderers/pdf.js +76 -0
  17. package/app/modules/vgfilepreview/js/renderers/playlist.js +71 -0
  18. package/app/modules/vgfilepreview/js/renderers/text-modal.js +343 -0
  19. package/app/modules/vgfilepreview/js/renderers/text.js +83 -0
  20. package/app/modules/vgfilepreview/js/renderers/video-modal.js +272 -0
  21. package/app/modules/vgfilepreview/js/renderers/video.js +80 -0
  22. package/app/modules/vgfilepreview/js/renderers/zip-modal.js +522 -0
  23. package/app/modules/vgfilepreview/js/renderers/zip.js +89 -0
  24. package/app/modules/vgfilepreview/js/vgfilepreview.js +532 -0
  25. package/app/modules/vgfilepreview/readme.md +68 -0
  26. package/app/modules/vgfilepreview/scss/_variables.scss +113 -0
  27. package/app/modules/vgfilepreview/scss/vgfilepreview.scss +460 -0
  28. package/app/modules/vgfiles/js/base.js +463 -175
  29. package/app/modules/vgfiles/js/droppable.js +260 -260
  30. package/app/modules/vgfiles/js/render.js +153 -153
  31. package/app/modules/vgfiles/js/vgfiles.js +41 -29
  32. package/app/modules/vgfiles/readme.md +116 -217
  33. package/app/modules/vgfiles/scss/_variables.scss +18 -10
  34. package/app/modules/vgfiles/scss/vgfiles.scss +153 -59
  35. package/app/modules/vgformsender/js/vgformsender.js +7 -2
  36. package/app/modules/vgmodal/js/vgmodal.js +12 -0
  37. package/app/modules/vgnav/js/vgnav.js +135 -135
  38. package/app/modules/vgnav/readme.md +67 -67
  39. package/app/modules/vgnestable/README.md +307 -307
  40. package/app/modules/vgnestable/scss/_variables.scss +60 -60
  41. package/app/modules/vgnestable/scss/vgnestable.scss +163 -163
  42. package/app/modules/vgselect/js/vgselect.js +62 -59
  43. package/app/modules/vgselect/scss/vgselect.scss +22 -22
  44. package/app/modules/vgspy/readme.md +28 -28
  45. package/app/utils/js/components/audio-metadata.js +240 -0
  46. package/app/utils/js/components/file-icon.js +109 -0
  47. package/app/utils/js/components/file-preview.js +304 -0
  48. package/app/utils/js/components/sanitize.js +150 -150
  49. package/build/vgapp.css +1 -1
  50. package/build/vgapp.css.map +1 -1
  51. package/index.js +1 -0
  52. package/index.scss +9 -6
  53. package/package.json +1 -1
@@ -1,224 +1,123 @@
1
- # VGFiles — Модуль загрузки и управления файлами
2
-
3
- `VGFiles` — это расширяемый и гибкий JavaScript-модуль для управления файлами на веб-странице, поддерживающий как локальную работу с файлами, так и асинхронную загрузку через AJAX. Модуль интегрируется с DOM-элементами, предоставляет визуальный интерфейс, поддерживает драг-н-дроп, валидацию, сортировку, отслеживание статусов и обработку событий.
4
-
5
- ---
6
-
7
- ## 🔧 Возможности
8
-
9
- ### Основные функции
10
- - **Добавление файлов** через `<input type="file">` или **drag & drop**.
11
- - **Просмотр списков файлов** с превью (если тип изображение).
12
- - **Удаление отдельных файлов** и **очистка всего списка**.
13
- - **Интеграция с сервером** через AJAX-запросы (загрузка, удаление, сортировка).
14
- - **Автоматическая валидация** по типу, количеству, размеру файлов.
15
- - **Многоязычная поддержка** (настраиваемые кнопки и сообщения).
16
-
17
- ---
18
-
19
- ## 📦 Установка и инициализация
20
-
21
- Модуль автоматически инициализируется на элементах с классом `.vg-files` при загрузке DOM:
22
-
23
- Или вручную:
24
-
25
- ```js
26
- const files = new VGFiles(document.querySelector('.vg-files'), {
27
- ajax: true,
28
- uploads: {
29
- route: '/api/upload',
30
- mode: 'sequential'
31
- },
32
- limits: {
33
- count: 10,
34
- sizes: 5, // MB
35
- total: 50
36
- }
37
- });
38
- ```
39
-
40
- ---
41
-
42
- ## ⚙️ Параметры конфигурации
43
-
44
- | Параметр | Тип | По умолчанию | Описание |
45
- |--------|------|-------------|---------|
46
- | `init` | `boolean` | `true` | Автоматическая инициализация |
47
- | `allowed` | `boolean` | `false` | Разрешить дроп файлов |
48
- | `lang` | `string` | `'ru'` | Язык интерфейса (поддерживается через `lang_buttons`, `lang_messages`) |
49
- | `limits.count` | `number` | `0` | Макс. количество файлов (0 — без ограничений) |
50
- | `limits.sizes` | `number` | `10` | Макс. размер одного файла (в МБ) |
51
- | `limits.total` | `number` | `0` | Общий лимит размера (в МБ, 0 — без ограничений) |
52
- | `image` | `boolean` | `false` | Отображать превью изображений |
53
- | `detach` | `boolean` | `true` | Откреплять файлы после загрузки |
54
- | `info` | `boolean` | `true` | Показывать кнопку удаления |
55
- | `types` | `array` | `[]` | Разрешённые MIME-типы (например, `['image/png', 'application/pdf']`) |
56
- | `ajax` | `boolean` | `false` | Использовать AJAX-загрузку |
57
- | `prepend` | `boolean` | `true` | Добавлять новые файлы в начало списка |
58
- | `uploads.route` | `string` | `''` | URL для загрузки файлов |
59
- | `uploads.mode` | `'sequential' \| 'parallel'` | `'sequential'` | Режим отправки файлов |
60
- | `uploads.maxParallel` | `number` | `3` | Макс. файлов при параллельной загрузке |
61
- | `uploads.maxConcurrent` | `number` | `1` | Макс. одновременных запросов |
62
- | `uploads.retryAttempts` | `number` | `1` | Кол-во попыток повтора при ошибке |
63
- | `uploads.retryDelay` | `number` | `1000` | Задержка между попытками (мс) |
64
- | `removes.single.route` | `string` | `''` | URL для удаления одного файла |
65
- | `removes.all.route` | `string` | `''` | URL для удаления всех файлов |
66
- | `removes.all.alert` | `boolean` | `true` | Показывать подтверждение при удалении всех файлов |
67
- | `removes.all.toast` | `boolean` | `true` | Показывать уведомление после удаления всех файлов |
68
- | `removes.all.confirm` | `function \| null` | `null` | Кастомный confirm-обработчик для удаления всех файлов |
69
- | `removes.single.alert` | `boolean` | `true` | Показывать подтверждение при удалении |
70
- | `removes.single.toast` | `boolean` | `true` | Показывать уведомление после удаления |
71
- | `removes.single.confirm` | `function \| null` | `null` | Кастомный confirm-обработчик для удаления одного файла |
72
- | `sortable.enabled` | `boolean` | `false` | Включить сортировку |
73
- | `sortable.route` | `string` | `''` | URL для сохранения порядка файлов |
74
- | `callbacks` | `object` | `null` | Колбэки на события (см. ниже) |
75
-
76
- ---
77
-
78
- ## 📢 События и колбэки
79
-
80
- Модуль поддерживает как DOM-события, так и JS-колбэки:
81
-
82
- ### DOM-события
83
- - `vg.files.change` — изменение списка файлов
84
- - `vg.files.upload.allComplete` — все файлы загружены
85
- - `vg.files.remove` — файл удалён
86
-
87
- ### Колбэки (`callbacks`)
88
- | Колбэк | Параметры | Описание |
89
- |-------|----------|--------|
90
- | `onInit` | `(data)` | Инициализация завершена |
91
- | `onChange` | `{ files, input, inputFiles }` | Изменён список файлов (`inputFiles` — снимок исходных `input.files`) |
92
- | `onUploadStart` | `{ files, total }` | Началась загрузка |
93
- | `onUploadProgress` | `{ file, progress, bytesSent, totalBytes }` | Прогресс загрузки |
94
- | `onUploadComplete` | `{ file, response, status, id }` | Файл успешно загружен |
95
- | `onUploadError` | `{ file }` | Ошибка загрузки |
96
- | `onUploadAllComplete` | `{ uploaded, failed, total }` | Все файлы загружены |
97
- | `onRemoveFile` | `{ button, name, size, id, remaining }` | Удалён файл |
98
- | `onClear` | `{}` | Очищен весь список |
99
- | `onReload` | `{ button, file }` | Повторная попытка загрузки |
100
-
101
- ---
102
-
103
- ## 📊 Статусы файлов
104
-
105
- Модуль отслеживает статусы в реальном времени:
106
- - **`pending`** — ожидает загрузки
107
- - **`loading`** — идёт загрузка
108
- - **`completed`** — успешно загружен
109
- - **`failing`** — ошибка загрузки
110
-
111
- ---
112
-
113
- ## 🔁 Внешние (уже загруженные) файлы
114
-
115
- Модуль может отображать уже загруженные файлы, переданные через `data-file`:
116
- -- TODO описать полностью
117
-
118
- ## 🧹 Очистка и удаление
119
-
120
- - **Удаление одного файла**: кнопка с `data-vg-dismiss="file"` → вызов `removeFile()`.
121
- - **Очистка всех**: кнопка с `data-vg-dismiss="vg-files"` → `clear()`.
122
-
123
- Поддержка подтверждения через `VGAlert` и уведомлений через `VGToast`.
124
-
125
- ### Кастомный confirm для удаления
126
-
127
- Если задан `removes.single.confirm` или `removes.all.confirm`, модуль вызовет вашу функцию вместо дефолтного `VGAlert`.
1
+ # VGFiles
2
+
3
+ `VGFiles` — модуль загрузки и управления файлами с поддержкой локального режима и AJAX.
4
+
5
+ ## Новые фичи
6
+
7
+ - Поддержка `replace` для single-mode (`limits.count = 1`): новый файл заменяет предыдущий без накопления.
8
+ - Переименование входящих файлов через `rename: true | (file, index) => string`.
9
+ - `smartdrop`: глобальная подсветка dropzone, если на экране только одна видимая зона дропа.
10
+ - Расширенный lifecycle для внешних файлов из `data-file`: парсинг `customData`, `src/image`, бинарных метаданных.
11
+ - Интеграция с `VGFilePreview` в info-списке (`ui.nameOnly: true`) для inline-аудио, скачивания и предпросмотра по клику.
12
+ - Авто-обогащение аудиофайлов метаданными (title/cover) через `extractAudioMetadata`.
13
+ - Гибкие confirm-хуки для удаления (`removes.single.confirm`, `removes.all.confirm`) с контрактом `accepted/data`.
14
+ - Повторная загрузка упавших файлов через `data-vg-reload="file"` и кнопки retry.
15
+ - Проброс `customData` файла в `data-*` атрибуты элементов списка.
16
+
17
+ ## Базовые возможности
18
+
19
+ - Выбор файлов через `<input type="file">` и drag&drop.
20
+ - Лимиты: количество, размер файла, общий размер, MIME-типы.
21
+ - Режимы загрузки: `sequential` и `parallel`.
22
+ - Автоматическая/ручная инициализация.
23
+ - Сортировка загруженных файлов с сохранением порядка на сервере.
24
+ - Удаление одного файла и очистка всего списка.
25
+
26
+ ## Инициализация
128
27
 
129
28
  ```js
130
- new VGFiles(document.querySelector('.vg-files'), {
29
+ const files = new VGFiles(document.querySelector('.vg-files'), {
131
30
  ajax: true,
132
- removes: {
133
- single: {
134
- route: '/api/file/remove',
135
- alert: true,
136
- confirm: async ({ message }) => {
137
- const accepted = window.confirm(`${message.title}\n${message.description}`);
138
- return { accepted };
139
- }
140
- }
31
+ smartdrop: true,
32
+ rename: true,
33
+ replace: true,
34
+ uploads: {
35
+ route: '/api/upload',
36
+ mode: 'sequential'
37
+ },
38
+ limits: {
39
+ count: 10,
40
+ sizes: 5,
41
+ total: 50
141
42
  }
142
43
  });
143
44
  ```
144
45
 
145
- Контракт функции confirm:
146
- - Вход: объект `{ type, trigger, lang, ajax, buttons, message }`.
147
- - Выход:
148
- `true` или `{ accepted: true }` — подтвердить;
149
- `false` или `{ accepted: false }` — отменить;
150
- `{ accepted: true, data }` — подтвердить и передать готовый ответ удаления (если запрос уже выполнен внутри confirm).
151
-
152
- ---
153
-
154
- ## 🧩 Расширяемость
155
-
156
- - **FileUploader** управляет загрузкой с поддержкой retry.
157
- - **VGFilesTemplateRender** — рендеринг шаблонов.
158
- - **Sortable (сортировка)** — подключается при необходимости.
159
- - Поддержка кастомных иконок через `getSVG()`.
160
-
161
- ---
162
-
163
- ## 🧰 API Методы
164
-
165
- | Метод | Описание |
166
- |------|--------|
167
- | `upload(file)` | Загрузить один файл |
168
- | `uploadAll(files)` | Загрузить все неотправленные файлы |
169
- | `reload(button)` | Повторить загрузку файла |
170
- | `removeFile(button)` | Удалить файл |
171
- | `clear(full, uiOnly)` | Очистить список |
172
- | `dispose()` | Полная деинициализация |
173
- | `_triggerCallback(name, data)` | Вызов колбэка |
174
- | `_route(params, cb)` | Выполнить AJAX-запрос |
175
-
176
- ---
177
-
178
- ## 🌐 AJAX-интеграция
179
-
180
- Поддерживает:
181
- - Загрузку `POST /upload`
182
- - Удаление `DELETE /files/{id}` или `POST /clear` с `ids[]`
183
- - Сортировку `POST /sort` с массивом `ids`
184
-
185
- ---
186
-
187
- ## 🛑 Ограничения и валидация
188
-
189
- - Проверка по размеру (одиночному и общему).
190
- - Проверка по количеству файлов.
191
- - Проверка MIME-типов.
192
- - Отображение ошибок через `VGAlert` или консоль.
193
-
194
- ---
195
-
196
- ## 🧾 Поддержка браузеров
197
-
198
- - Современные браузеры (Chrome, Firefox, Safari, Edge)
199
- - Требуется поддержка: `File`, `FormData`, `ES6+`, `CustomEvent`
200
-
201
- ---
202
-
203
- ## 📚 Зависимости
204
-
205
- - `VGFilesBase`, `VGFilesDroppable`, `VGFilesTemplateRender`, `FileUploader`
206
- - `VGAlert`, `VGToast` — модули уведомлений
207
- - `EventHandler`, `Selectors`, `Manipulator`, `Classes` — утилиты DOM
208
- - `getSVG`, `lang_buttons`, `lang_messages` — компоненты интерфейса
209
-
210
- ---
211
-
212
- **Гибкий | Расширяемый | Локализуемый | Готов к production**
213
-
214
- ---
215
-
216
- ## 📝 Лицензия
217
-
218
- MIT. Свободно использовать и модифицировать.
219
-
220
- ---
221
-
222
- 📌 *Разработано в рамках фронтенд-системы VG Modules.*
223
- > 🚀 Автор: VEGAS STUDIO (vegas-dev.com)
224
- > 📍 Поддерживается в проектах VEGAS
46
+ ## Ключевые параметры
47
+
48
+ - `allowed` — разрешить drag-drop режим и особенности drop-зоны.
49
+ - `ajax` включить серверную загрузку.
50
+ - `replace` (`default: true`)замена файла в single-mode.
51
+ - `rename` (`default: false`)переименование файлов до добавления в список.
52
+ - `smartdrop` (`default: false`) — глобальная логика подсказки drop-зоны.
53
+ - `prepend` (`default: true`) — добавление новых файлов в начало.
54
+ - `uploads.mode` — `'sequential' | 'parallel'`.
55
+ - `uploads.maxParallel`, `uploads.maxConcurrent`, `uploads.retryAttempts`, `uploads.retryDelay`.
56
+ - `removes.single.confirm`, `removes.all.confirm` — кастомные confirm-обработчики.
57
+ - `sortable.enabled`, `sortable.route`, `sortable.handle`, `sortable.lists`.
58
+
59
+ ## Внешние (уже загруженные) файлы
60
+
61
+ Модуль может стартовать с предзаполненным списком через `data-file` в `<li>`.
62
+
63
+ Поддерживаемые поля объекта:
64
+
65
+ - `id`, `name`, `size`, `type`, `src` (обязательный набор для валидного внешнего файла)
66
+ - `image` (опционально)
67
+ - `lastModified` / `last-modified` (опционально)
68
+ - любые дополнительные поля -> попадают в `customData`
69
+
70
+ `customData` дальше пробрасывается в `data-*` атрибуты элементов файла и может использоваться в шаблонах/интеграциях.
71
+
72
+ ## AJAX-события и callbacks
73
+
74
+ DOM-события:
75
+
76
+ - `vg.files.change`
77
+ - `vg.files.upload.start`
78
+ - `vg.files.upload.progress`
79
+ - `vg.files.upload.complete`
80
+ - `vg.files.upload.error`
81
+ - `vg.files.upload.allComplete`
82
+ - `vg.files.remove`
83
+ - `vg.files.reload`
84
+
85
+ Callbacks (`params.callbacks`):
86
+
87
+ - `onInit`
88
+ - `onChange` (`{ files, input, inputFiles }`)
89
+ - `onUploadStart`
90
+ - `onUploadProgress`
91
+ - `onUploadComplete`
92
+ - `onUploadError`
93
+ - `onUploadAllComplete`
94
+ - `onRemoveFile`
95
+ - `onClear`
96
+ - `onReload`
97
+
98
+ ## Удаление с кастомным confirm
99
+
100
+ Если передан `removes.single.confirm` или `removes.all.confirm`, модуль вызывает вашу функцию вместо стандартного `VGAlert`.
101
+
102
+ Контракт результата:
103
+
104
+ - `true` или `{ accepted: true }` — подтвердить.
105
+ - `false` или `{ accepted: false }` — отменить.
106
+ - `{ accepted: true, data }` — подтвердить и использовать готовый ответ удаления (без дополнительного AJAX-запроса).
107
+
108
+ ## Статусы файлов
109
+
110
+ - `pending` — ожидает загрузки.
111
+ - `loading` — идет загрузка.
112
+ - `completed` — успешно загружен.
113
+ - `failing` ошибка загрузки (доступен reload).
114
+
115
+ ## Методы API
116
+
117
+ - `upload(file)`
118
+ - `uploadAll(files)`
119
+ - `reload(button)`
120
+ - `removeFile(button)`
121
+ - `clear(resetInput, uiOnly)`
122
+ - `dispose()`
123
+
@@ -70,19 +70,27 @@ $files-map: (
70
70
  info-radius: 4px,
71
71
  info-list-max-height: 360px,
72
72
 
73
- info-gap: 8px,
74
- info-padding: 8px,
75
- info-border-bottom-color: var(--vg-secondary-bg),
76
- info-hover-bg: rgba(var(--vg-secondary-bg-rgb), .4),
77
-
78
- info-image-radius: 4px,
73
+ info-gap: 8px,
74
+ info-padding: 8px,
75
+ info-border-bottom-color: var(--vg-secondary-bg),
76
+ info-hover-bg: rgba(var(--vg-secondary-bg-rgb), .4),
77
+ info-audio-progress-track: rgba(var(--vg-secondary-bg-rgb), .3),
78
+ info-audio-progress-fill: #000,
79
+ info-audio-progress-opacity: .33,
80
+ info-audio-row-min-height: 64px,
81
+ info-audio-image-size: 48px,
82
+
83
+ info-image-radius: 4px,
79
84
  info-iteration-font-size: 16px,
80
85
  info-name-font-size: 16px,
81
86
  info-size-font-size: 14px,
82
87
 
83
- info-remove-icon-size: 16px,
84
- info-row-remove-size: 22px,
85
- info-row-remove-icon-size: 10px,
88
+ info-remove-icon-size: 16px,
89
+ info-row-remove-size: 22px,
90
+ info-row-remove-icon-size: 10px,
91
+ info-failing-actions-gap: 6px,
92
+ info-failing-actions-margin-start: 8px,
93
+ drop-failing-actions-gap: 6px,
86
94
 
87
95
  // legacy (оставлено для совместимости со старым API темы)
88
96
  info-mb: 10px,
@@ -104,4 +112,4 @@ $files-map: (
104
112
  info-list-span-me: 6px,
105
113
  info-list-button-fs: 12px,
106
114
  info-list-button-color: #999999,
107
- );
115
+ );
@@ -261,15 +261,15 @@
261
261
  }
262
262
  }
263
263
 
264
- .file {
265
- --vg-button-color: var(--vg-secondary);
266
- display: flex;
267
- align-items: center;
268
- gap: var(--vg-files-info-gap);
269
- padding: var(--vg-files-info-padding);
270
- border-bottom: 1px solid var(--vg-files-info-border-bottom-color);
271
- transition: all .2s ease-in-out;
272
- position: relative;
264
+ .file {
265
+ --vg-button-color: var(--vg-secondary);
266
+ display: flex;
267
+ align-items: center;
268
+ gap: var(--vg-files-info-gap);
269
+ padding: var(--vg-files-info-padding);
270
+ border-bottom: 1px solid var(--vg-files-info-border-bottom-color);
271
+ transition: all .2s ease-in-out;
272
+ position: relative;
273
273
 
274
274
  @include vgfiles-sortable();
275
275
  @include vgfiles-state();
@@ -278,13 +278,45 @@
278
278
  border-bottom: none;
279
279
  }
280
280
 
281
- &:hover {
282
- background-color: var(--vg-files-info-hover-bg);
283
- }
284
-
285
- > * {
286
- transition: all .2s ease-in-out;
287
- }
281
+ &:hover {
282
+ background-color: var(--vg-files-info-hover-bg);
283
+ }
284
+
285
+ &[data-vg-filepreview-renderer="audio"] {
286
+ --vg-filepreview-audio-inline-progress: 0%;
287
+ position: relative;
288
+ overflow: hidden;
289
+ isolation: isolate;
290
+ min-height: var(--vg-files-info-audio-row-min-height);
291
+ background-color: var(--vg-files-info-audio-progress-track);
292
+
293
+ &::before {
294
+ content: '';
295
+ position: absolute;
296
+ inset: 0 auto 0 0;
297
+ width: var(--vg-filepreview-audio-inline-progress);
298
+ background: var(--vg-files-info-audio-progress-fill);
299
+ opacity: var(--vg-files-info-audio-progress-opacity);
300
+ pointer-events: none;
301
+ z-index: 0;
302
+ transition: width .12s linear;
303
+ }
304
+
305
+ > * {
306
+ position: relative;
307
+ z-index: 1;
308
+ }
309
+
310
+ .file-image {
311
+ flex: 0 0 var(--vg-files-info-audio-image-size);
312
+ width: var(--vg-files-info-audio-image-size);
313
+ height: var(--vg-files-info-audio-image-size);
314
+ }
315
+ }
316
+
317
+ > * {
318
+ transition: all .2s ease-in-out;
319
+ }
288
320
 
289
321
  &-image {
290
322
  flex: 0 0 var(--vg-files-image-width);
@@ -330,26 +362,63 @@
330
362
  display: none;
331
363
  }
332
364
 
333
- .name {
334
- display: block;
335
- overflow: hidden;
336
- text-overflow: ellipsis;
337
- white-space: nowrap;
338
- font-size: var(--vg-files-info-name-font-size);
339
- }
340
-
341
- .size {
342
- color: var(--vg-secondary);
343
- font-size: var(--vg-files-info-size-font-size);
344
- white-space: nowrap;
345
- margin-left: auto;
346
- }
347
- }
348
-
349
- &-remove {
350
- flex: 0 0 var(--vg-files-remove-width);
351
- width: var(--vg-files-remove-width);
352
- margin-left: auto;
365
+ .name {
366
+ display: block;
367
+ overflow: hidden;
368
+ text-overflow: ellipsis;
369
+ white-space: nowrap;
370
+ font-size: var(--vg-files-info-name-font-size);
371
+
372
+ &.vg-filepreview-audio-inline {
373
+ display: inline-flex;
374
+ gap: var(--vg-filepreview-audio-inline-gap);
375
+ align-items: center;
376
+ }
377
+
378
+ &.is-preview-action {
379
+ cursor: pointer;
380
+ }
381
+ }
382
+
383
+ .size {
384
+ color: var(--vg-secondary);
385
+ font-size: var(--vg-files-info-size-font-size);
386
+ white-space: nowrap;
387
+ margin-left: auto;
388
+ }
389
+
390
+ .download {
391
+ margin-left: 8px;
392
+
393
+ .vg-filepreview-download-trigger {
394
+ padding: 0;
395
+ background: transparent;
396
+ color: var(--vg-secondary);
397
+ border-radius: 0;
398
+ font-size: var(--vg-files-info-size-font-size);
399
+ font-weight: 500;
400
+ gap: 4px;
401
+
402
+ &:hover {
403
+ color: var(--vg-body-color);
404
+ }
405
+
406
+ &__icon svg {
407
+ width: 14px;
408
+ height: 14px;
409
+ }
410
+
411
+ &__text {
412
+ display: none;
413
+ }
414
+ }
415
+ }
416
+ }
417
+
418
+ &-remove {
419
+ flex: 0 0 var(--vg-files-remove-width);
420
+ width: var(--vg-files-remove-width);
421
+ margin-left: auto;
353
422
 
354
423
  button {
355
424
  width: var(--vg-files-remove-width);
@@ -382,14 +451,21 @@
382
451
  fill: var(--vg-button-color);
383
452
  }
384
453
  }
385
- }
386
- }
387
- }
388
-
389
- &:not(.with-image) {
390
- .file-info {
391
- .iteration {
392
- display: block;
454
+ }
455
+ }
456
+ }
457
+
458
+ &.failing {
459
+ .file-remove {
460
+ flex: 0 0 auto;
461
+ width: auto;
462
+ }
463
+ }
464
+
465
+ &:not(.with-image) {
466
+ .file-info {
467
+ .iteration {
468
+ display: block;
393
469
  }
394
470
  }
395
471
  }
@@ -436,12 +512,26 @@
436
512
  }
437
513
  }
438
514
  }
439
- }
440
- }
441
- }
442
- }
443
-
444
- &-drop {
515
+ }
516
+ }
517
+ }
518
+ }
519
+
520
+ .file-failing-actions {
521
+ display: inline-flex;
522
+ align-items: center;
523
+ gap: var(--vg-files-info-failing-actions-gap);
524
+ }
525
+
526
+ &-info--list {
527
+ .file.failing {
528
+ .file-remove {
529
+ margin-left: var(--vg-files-info-failing-actions-margin-start);
530
+ }
531
+ }
532
+ }
533
+
534
+ &-drop {
445
535
  background-color: var(--vg-files-drop-bg);
446
536
  border: var(--vg-files-drop-border-width) var(--vg-files-drop-border-style) var(--vg-files-drop-border-color);
447
537
  cursor: pointer;
@@ -465,8 +555,8 @@
465
555
  }
466
556
  }
467
557
 
468
- &:hover {
469
- animation: stripes var(--vg-files-drop-stripes-duration) linear infinite;
558
+ &:hover {
559
+ animation: stripes var(--vg-files-drop-stripes-duration) linear infinite;
470
560
  background-image: linear-gradient(
471
561
  -45deg,
472
562
  rgba(var(--vg-secondary-rgb), var(--vg-files-drop-stripes-alpha)) 25%,
@@ -486,9 +576,9 @@
486
576
  box-shadow: var(--vg-files-drop-hover-shadow);
487
577
  }
488
578
 
489
- > * {
490
- display: block;
491
- }
579
+ > * {
580
+ display: block;
581
+ }
492
582
 
493
583
  &-message {
494
584
  width: 100%;
@@ -621,10 +711,14 @@
621
711
  flex-direction: column;
622
712
  gap: 8px;
623
713
 
624
- .file-remove {
625
- button {
626
- background: var(--vg-body-bg);
627
- color: var(--vg-body-color);
714
+ .file-remove {
715
+ .file-failing-actions {
716
+ gap: var(--vg-files-drop-failing-actions-gap);
717
+ }
718
+
719
+ button {
720
+ background: var(--vg-body-bg);
721
+ color: var(--vg-body-color);
628
722
  border: none;
629
723
  cursor: pointer;
630
724
  outline: none;
@@ -276,8 +276,13 @@ class VGFormSender extends BaseModule {
276
276
  request(event, data = null) {
277
277
  const _this = this;
278
278
  const mergeFormData = (target, source) => {
279
+ const replacedKeys = new Set();
279
280
  source.forEach((value, key) => {
280
- target.set(key, value);
281
+ if (!replacedKeys.has(key)) {
282
+ target.delete(key);
283
+ replacedKeys.add(key);
284
+ }
285
+ target.append(key, value);
281
286
  });
282
287
  return target;
283
288
  }
@@ -777,4 +782,4 @@ EventHandler.on(document, EVENT_SUBMIT_DATA_API, function (event) {
777
782
  }
778
783
  })
779
784
 
780
- export default VGFormSender;
785
+ export default VGFormSender;