vgapp 1.1.2 → 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 +13 -13
  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 +39 -39
  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
@@ -0,0 +1,272 @@
1
+ import VGModal from "../../../vgmodal";
2
+
3
+ class VideoModal {
4
+ constructor() {
5
+ this._modalId = 'vg-filepreview-video-modal';
6
+ this._playlist = [];
7
+ this._currentIndex = -1;
8
+ this._labels = {};
9
+ this._onKeyDown = (event) => this._handleHotkeys(event);
10
+ }
11
+
12
+ static getInstance() {
13
+ if (!VideoModal._instance) {
14
+ VideoModal._instance = new VideoModal();
15
+ }
16
+
17
+ return VideoModal._instance;
18
+ }
19
+
20
+ open(payload = {}) {
21
+ const src = String(payload.src || '').trim();
22
+ if (!src) {
23
+ return;
24
+ }
25
+
26
+ this._ensureModal();
27
+ if (!this._modal || !this._video) {
28
+ return;
29
+ }
30
+
31
+ const playlist = payload?.playlist && typeof payload.playlist === 'object' ? payload.playlist : null;
32
+ this._labels = payload?.labels && typeof payload.labels === 'object' ? payload.labels : {};
33
+ if (playlist?.tracks?.length) {
34
+ this._playlist = playlist.tracks;
35
+ const currentBySrc = this._playlist.findIndex((track) => String(track.src || '').trim() === src);
36
+ this._currentIndex = currentBySrc >= 0 ? currentBySrc : Number(playlist.currentIndex || 0);
37
+ } else {
38
+ this._playlist = [{ src, title: String(payload.title || '').trim() }];
39
+ this._currentIndex = 0;
40
+ }
41
+
42
+ const defaultTitle = String(payload.defaultTitle || '').trim();
43
+ const title = String(payload.title || '').trim();
44
+ this._title.textContent = title || defaultTitle;
45
+ this._syncNavigation();
46
+ this._video.src = src;
47
+ this._video.load();
48
+
49
+ this._modal.show();
50
+ this._video.play().catch(() => {});
51
+ }
52
+
53
+ close() {
54
+ if (!this._modal) {
55
+ return;
56
+ }
57
+
58
+ this._modal.hide();
59
+ this._stop();
60
+ }
61
+
62
+ _ensureModal() {
63
+ if (this._modal && this._root) {
64
+ return;
65
+ }
66
+
67
+ this._initModal();
68
+ }
69
+
70
+ _initModal() {
71
+ const params = {
72
+ centered: true,
73
+ dismiss: true,
74
+ backdrop: true,
75
+ keyboard: true,
76
+ sizes: {
77
+ width: 'fit-content',
78
+ },
79
+ animation: {
80
+ enable: false
81
+ }
82
+ };
83
+
84
+ const existed = document.getElementById(this._modalId);
85
+ if (existed) {
86
+ this._root = existed;
87
+ this._modal = VGModal.getOrCreateInstance(existed, params);
88
+ this._bindElements(existed);
89
+ this._bindEvents(existed);
90
+ return;
91
+ }
92
+
93
+ this._modal = VGModal.build(this._modalId, params, (modalInstance) => {
94
+ const element = modalInstance._element;
95
+ this._root = element;
96
+ element.classList.add('vg-filepreview-video-modal');
97
+
98
+ const body = element.querySelector('.vg-modal-body');
99
+ const content = element.querySelector('.vg-modal-content');
100
+ if (!body || !content) {
101
+ return;
102
+ }
103
+
104
+ body.classList.add('vg-filepreview-image-modal__body');
105
+ body.innerHTML = '';
106
+
107
+ let header = element.querySelector('.vg-modal-header');
108
+ if (!header) {
109
+ header = document.createElement('div');
110
+ header.className = 'vg-modal-header';
111
+ content.prepend(header);
112
+ }
113
+
114
+ this._title = document.createElement('div');
115
+ this._title.className = 'vg-modal-title';
116
+ this._title.textContent = '';
117
+
118
+ const navigation = document.createElement('div');
119
+ navigation.className = 'vg-filepreview-video-modal__navigation';
120
+
121
+ this._prevButton = document.createElement('button');
122
+ this._prevButton.type = 'button';
123
+ this._prevButton.className = 'vg-filepreview-video-modal__nav-btn';
124
+ this._prevButton.addEventListener('click', () => this._goPrev());
125
+
126
+ this._nextButton = document.createElement('button');
127
+ this._nextButton.type = 'button';
128
+ this._nextButton.className = 'vg-filepreview-video-modal__nav-btn';
129
+ this._nextButton.addEventListener('click', () => this._goNext());
130
+
131
+ navigation.appendChild(this._prevButton);
132
+ navigation.appendChild(this._nextButton);
133
+
134
+ this._video = document.createElement('video');
135
+ this._video.className = 'vg-filepreview-video-modal__video';
136
+ this._video.controls = true;
137
+ this._video.preload = 'metadata';
138
+ this._video.playsInline = true;
139
+ this._video.addEventListener('ended', () => this._goNext(true));
140
+
141
+ header.appendChild(this._title);
142
+ //header.appendChild(navigation);
143
+ body.appendChild(this._video);
144
+
145
+ this._bindEvents(element);
146
+ });
147
+ }
148
+
149
+ _bindElements(root) {
150
+ this._title = root.querySelector('.vg-filepreview-video-modal__title');
151
+ this._video = root.querySelector('.vg-filepreview-video-modal__video');
152
+ }
153
+
154
+ _bindEvents(root) {
155
+ if (!root || root.hasAttribute('data-vg-filepreview-video-bind')) {
156
+ return;
157
+ }
158
+
159
+ root.setAttribute('data-vg-filepreview-video-bind', 'true');
160
+ document.addEventListener('keydown', this._onKeyDown);
161
+ root.addEventListener('vg.modal.hidden', () => {
162
+ this._stop();
163
+ this._destroyModal();
164
+ });
165
+ }
166
+
167
+ _stop() {
168
+ if (!this._video) {
169
+ return;
170
+ }
171
+
172
+ this._video.pause();
173
+ this._video.currentTime = 0;
174
+ }
175
+
176
+ _destroyModal() {
177
+ document.removeEventListener('keydown', this._onKeyDown);
178
+ if (this._modal && typeof this._modal.dispose === 'function') {
179
+ this._modal.dispose();
180
+ }
181
+
182
+ if (this._root && this._root.parentNode) {
183
+ this._root.parentNode.removeChild(this._root);
184
+ }
185
+
186
+ this._root = null;
187
+ this._modal = null;
188
+ this._title = null;
189
+ this._prevButton = null;
190
+ this._nextButton = null;
191
+ this._video = null;
192
+ this._playlist = [];
193
+ this._currentIndex = -1;
194
+ this._labels = {};
195
+ }
196
+
197
+ _syncNavigation() {
198
+ if (!this._prevButton || !this._nextButton) {
199
+ return;
200
+ }
201
+
202
+ this._prevButton.textContent = this._labels.prev || '';
203
+ this._nextButton.textContent = this._labels.next || '';
204
+ const hasPlaylist = this._playlist.length > 1;
205
+ this._prevButton.disabled = !hasPlaylist;
206
+ this._nextButton.disabled = !hasPlaylist;
207
+ }
208
+
209
+ _goPrev() {
210
+ if (!this._playlist.length) {
211
+ return;
212
+ }
213
+
214
+ this._currentIndex = this._currentIndex <= 0 ? this._playlist.length - 1 : this._currentIndex - 1;
215
+ this._openCurrentTrack();
216
+ }
217
+
218
+ _goNext(fromEnded = false) {
219
+ if (!this._playlist.length) {
220
+ return;
221
+ }
222
+
223
+ if (this._playlist.length === 1 && !fromEnded) {
224
+ return;
225
+ }
226
+
227
+ this._currentIndex = (this._currentIndex + 1) % this._playlist.length;
228
+ this._openCurrentTrack();
229
+ }
230
+
231
+ _openCurrentTrack() {
232
+ const current = this._playlist[this._currentIndex];
233
+ if (!current?.src) {
234
+ return;
235
+ }
236
+
237
+ this.open({
238
+ src: current.src,
239
+ title: current.title || '',
240
+ defaultTitle: current.title || '',
241
+ playlist: {
242
+ tracks: this._playlist,
243
+ currentIndex: this._currentIndex
244
+ },
245
+ labels: this._labels
246
+ });
247
+ }
248
+
249
+ _handleHotkeys(event) {
250
+ if (!this._root || !this._root.classList.contains('show')) {
251
+ return;
252
+ }
253
+
254
+ const tag = String(event?.target?.tagName || '').toUpperCase();
255
+ if (['INPUT', 'TEXTAREA', 'SELECT'].includes(tag)) {
256
+ return;
257
+ }
258
+
259
+ if (event.key === 'ArrowLeft') {
260
+ event.preventDefault();
261
+ this._goPrev();
262
+ }
263
+
264
+ if (event.key === 'ArrowRight') {
265
+ event.preventDefault();
266
+ this._goNext();
267
+ }
268
+ }
269
+
270
+ }
271
+
272
+ export default VideoModal;
@@ -0,0 +1,80 @@
1
+ import VideoModal from "./video-modal";
2
+ import { buildMediaPlaylist } from "./playlist";
3
+
4
+ const VIDEO_EXTENSIONS = new Set([
5
+ '.mp4',
6
+ '.webm',
7
+ '.mov',
8
+ '.mkv',
9
+ '.avi',
10
+ '.m4v'
11
+ ]);
12
+
13
+ class VideoFilePreviewRenderer {
14
+ constructor() {
15
+ this.name = 'video';
16
+ this._modal = VideoModal.getInstance();
17
+ }
18
+
19
+ canRender(context = {}) {
20
+ const ext = String(context?.fileMeta?.ext || '').toLowerCase();
21
+ return VIDEO_EXTENSIONS.has(ext);
22
+ }
23
+
24
+ render(context = {}) {
25
+ const container = context?.previewContainer;
26
+ const nameOnly = Boolean(context?.ui?.nameOnly);
27
+ const i18n = context?.i18n;
28
+
29
+ const src = context?.fileUrl?.href || context?.filePath || '';
30
+ if (!src) {
31
+ return false;
32
+ }
33
+
34
+ const playlist = buildMediaPlaylist(context?.element, (ext) => VIDEO_EXTENSIONS.has(ext));
35
+ const labels = {
36
+ prev: i18n?.button('prev') || '',
37
+ next: i18n?.button('next') || ''
38
+ };
39
+
40
+ const openVideo = (event) => {
41
+ if (event) {
42
+ event.preventDefault();
43
+ }
44
+
45
+ this._modal.open({
46
+ src,
47
+ title: context?.fileMeta?.name || i18n?.message('video_title') || '',
48
+ defaultTitle: i18n?.message('video_title') || '',
49
+ playlist,
50
+ labels
51
+ });
52
+ };
53
+
54
+ const titleLink = context?.element?.querySelector('.name');
55
+ if (titleLink && !titleLink.hasAttribute('data-vg-filepreview-video-bind')) {
56
+ titleLink.setAttribute('data-vg-filepreview-video-bind', 'true');
57
+ titleLink.classList.add('is-preview-action');
58
+ titleLink.addEventListener('click', openVideo);
59
+ }
60
+
61
+ if (nameOnly) {
62
+ return Boolean(titleLink);
63
+ }
64
+
65
+ if (!container) {
66
+ return false;
67
+ }
68
+
69
+ const trigger = document.createElement('button');
70
+ trigger.type = 'button';
71
+ trigger.className = 'vg-filepreview-video-trigger';
72
+ trigger.textContent = i18n?.button('open_video') || '';
73
+ trigger.addEventListener('click', openVideo);
74
+ container.appendChild(trigger);
75
+
76
+ return true;
77
+ }
78
+ }
79
+
80
+ export default VideoFilePreviewRenderer;