@underpostnet/underpost 2.98.0 → 2.98.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.
@@ -1,880 +0,0 @@
1
- import { loggerFactory } from './Logger.js';
2
- import { getProxyPath, listenQueryPathInstance, setPath, setQueryParams } from './Router.js';
3
- import { ObjectLayerService } from '../../services/object-layer/object-layer.service.js';
4
- import { NotificationManager } from './NotificationManager.js';
5
- import { htmls, s } from './VanillaJs.js';
6
-
7
- import { darkTheme, ThemeEvents } from './Css.js';
8
- import { ObjectLayerManagement } from '../../services/object-layer/object-layer.management.js';
9
- import { ObjectLayerEngineModal } from './ObjectLayerEngineModal.js';
10
- import { Modal } from './Modal.js';
11
- import { DefaultManagement } from '../../services/default/default.management.js';
12
- import { AgGrid } from './AgGrid.js';
13
-
14
- const logger = loggerFactory(import.meta);
15
-
16
- const ObjectLayerEngineViewer = {
17
- Data: {
18
- objectLayer: null,
19
- frameCounts: null,
20
- currentDirection: 'down',
21
- currentMode: 'idle',
22
- webp: null,
23
- isGenerating: false,
24
- },
25
-
26
- // Map user-friendly direction/mode to numeric direction codes
27
- getDirectionCode: function (direction, mode) {
28
- const key = `${direction}_${mode}`;
29
- const directionCodeMap = {
30
- down_idle: '08',
31
- down_walking: '18',
32
- up_idle: '02',
33
- up_walking: '12',
34
- left_idle: '04',
35
- left_walking: '14',
36
- right_idle: '06',
37
- right_walking: '16',
38
- };
39
- return directionCodeMap[key] || null;
40
- },
41
-
42
- Render: async function ({ Elements }) {
43
- const id = 'object-layer-engine-viewer';
44
-
45
- Modal.Data[`modal-${id}`].onReloadModalListener[id] = async () => {
46
- ObjectLayerEngineViewer.Reload({ Elements });
47
- };
48
-
49
- // Listen for cid query parameter
50
- listenQueryPathInstance(
51
- {
52
- id: `${id}-query-listener`,
53
- routeId: 'object-layer-engine-viewer',
54
- event: async (cid) => {
55
- if (cid) {
56
- await this.loadObjectLayer(cid, Elements);
57
- } else {
58
- this.renderEmpty({ Elements });
59
- }
60
- },
61
- },
62
- 'cid',
63
- );
64
-
65
- return html`
66
- <div class="fl">
67
- <div class="in ${id}" id="${id}">
68
- <div class="in section-mp">
69
- <div class="in">Loading object layer...</div>
70
- </div>
71
- </div>
72
- </div>
73
- `;
74
- },
75
-
76
- renderEmpty: async function ({ Elements }) {
77
- const id = 'object-layer-engine-viewer';
78
- const idModal = 'modal-object-layer-engine-viewer';
79
- const serviceId = 'object-layer-engine-management';
80
- const gridId = `${serviceId}-grid-${idModal}`;
81
- if (s(`.${serviceId}-grid-${idModal}`) && AgGrid.grids[gridId])
82
- await DefaultManagement.loadTable(idModal, { reload: true });
83
- else
84
- htmls(
85
- `#${id}`,
86
- await ObjectLayerManagement.RenderTable({
87
- Elements,
88
- idModal,
89
- }),
90
- );
91
- },
92
-
93
- loadObjectLayer: async function (objectLayerId, Elements) {
94
- const id = 'object-layer-engine-viewer';
95
-
96
- try {
97
- // Load metadata first
98
- const { status: metaStatus, data: metadata } = await ObjectLayerService.getMetadata({ id: objectLayerId });
99
-
100
- if (metaStatus !== 'success' || !metadata) {
101
- throw new Error('Failed to load object layer metadata');
102
- }
103
-
104
- this.Data.objectLayer = metadata;
105
-
106
- // Load frame counts for all directions
107
- const { status: frameStatus, data: frameData } = await ObjectLayerService.getFrameCounts({ id: objectLayerId });
108
-
109
- if (frameStatus !== 'success' || !frameData) {
110
- throw new Error('Failed to load frame counts');
111
- }
112
-
113
- this.Data.frameCounts = frameData.frameCounts;
114
- // Priority order for directions
115
- const directions = ['down', 'up', 'left', 'right'];
116
- // Priority order for modes
117
- const modes = ['idle', 'walking'];
118
- this.Data.currentDirection = 'down';
119
- this.Data.currentMode = 'idle';
120
-
121
- // Render the viewer UI
122
- await this.renderViewer({ Elements });
123
-
124
- // Generate WebP
125
- await this.generateWebp();
126
- } catch (error) {
127
- logger.error('Error loading object layer:', error);
128
- NotificationManager.Push({
129
- html: `Failed to load object layer: ${error.message}`,
130
- status: 'error',
131
- });
132
-
133
- htmls(
134
- `#${id}`,
135
- html`
136
- <div class="in section-mp">
137
- <div class="in">
138
- <h3>Error</h3>
139
- <p>Failed to load object layer. Please try again.</p>
140
- </div>
141
- </div>
142
- `,
143
- );
144
- }
145
- },
146
-
147
- renderViewer: async function ({ Elements }) {
148
- const id = 'object-layer-engine-viewer';
149
- const { objectLayer, frameCounts } = this.Data;
150
-
151
- if (!objectLayer || !frameCounts) return;
152
-
153
- const itemType = objectLayer.data.item.type;
154
- const itemId = objectLayer.data.item.id;
155
- const itemDescription = objectLayer.data.item.description || '';
156
- const itemActivable = objectLayer.data.item.activable || false;
157
-
158
- // Get stats data
159
- const stats = objectLayer.data.stats || {};
160
-
161
- // Helper function to check if direction/mode has frames
162
- const hasFrames = (direction, mode) => {
163
- const numericCode = this.getDirectionCode(direction, mode);
164
- return numericCode && frameCounts[numericCode] && frameCounts[numericCode] > 0;
165
- };
166
-
167
- // Helper function to get frame count
168
- const getFrameCount = (direction, mode) => {
169
- const numericCode = this.getDirectionCode(direction, mode);
170
- return numericCode ? frameCounts[numericCode] || 0 : 0;
171
- };
172
- ThemeEvents[id] = () => {
173
- if (!s(`.style-${id}`)) return;
174
- htmls(
175
- `.style-${id}`,
176
- html` <style>
177
- .object-layer-viewer-container {
178
- max-width: 800px;
179
- margin: 0 auto;
180
- padding: 20px;
181
- font-family: 'retro-font';
182
- }
183
-
184
- .viewer-header {
185
- text-align: center;
186
- margin-bottom: 30px;
187
- padding-bottom: 20px;
188
- border-bottom: 2px solid ${darkTheme ? '#444' : '#ddd'};
189
- }
190
-
191
- .viewer-header h2 {
192
- margin: 0 0 10px 0;
193
- color: ${darkTheme ? '#fff' : '#333'};
194
- }
195
-
196
- .webp-display-area {
197
- background: ${darkTheme ? '#2a2a2a' : '#f5f5f5'};
198
- border: 2px solid ${darkTheme ? '#444' : '#ddd'};
199
- border-radius: 12px;
200
- padding: 30px;
201
- margin-bottom: 30px;
202
- display: flex;
203
- justify-content: center;
204
- align-items: center;
205
- min-height: 300px;
206
- height: auto;
207
- max-height: 600px;
208
- position: relative;
209
- overflow: auto;
210
- }
211
-
212
- .webp-canvas-container {
213
- position: relative;
214
- display: flex;
215
- justify-content: center;
216
- align-items: center;
217
- width: 100%;
218
- height: 100%;
219
- }
220
-
221
- .webp-canvas-container canvas,
222
- .webp-canvas-container img {
223
- image-rendering: pixelated;
224
- image-rendering: -moz-crisp-edges;
225
- image-rendering: crisp-edges;
226
- -ms-interpolation-mode: nearest-neighbor;
227
- background: repeating-conic-gradient(#80808020 0% 25%, #fff0 0% 50%) 50% / 20px 20px;
228
- border-radius: 8px;
229
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
230
- max-width: 100%;
231
- max-height: 540px;
232
- width: auto !important;
233
- height: auto !important;
234
- object-fit: contain;
235
- display: block;
236
- }
237
-
238
- .webp-canvas-container canvas {
239
- background: repeating-conic-gradient(#80808020 0% 25%, #fff0 0% 50%) 50% / 20px 20px;
240
- min-width: 128px;
241
- min-height: 128px;
242
- }
243
-
244
- .webp-info-badge {
245
- position: absolute;
246
- bottom: 10px;
247
- right: 10px;
248
- background: rgba(0, 0, 0, 0.2);
249
- color: ${darkTheme ? 'white' : 'black'};
250
- padding: 6px 12px;
251
- border-radius: 4px;
252
- font-size: 12px;
253
- font-family: monospace;
254
- backdrop-filter: blur(4px);
255
- }
256
-
257
- .webp-info-badge .info-label {
258
- opacity: 0.7;
259
- margin-right: 4px;
260
- }
261
-
262
- .loading-overlay {
263
- position: absolute;
264
- top: 0;
265
- left: 0;
266
- right: 0;
267
- bottom: 0;
268
- background: rgba(0, 0, 0, 0.7);
269
- display: flex;
270
- justify-content: center;
271
- align-items: center;
272
- color: white;
273
- border-radius: 8px;
274
- z-index: 10;
275
- }
276
-
277
- .controls-container {
278
- display: flex;
279
- flex-direction: column;
280
- gap: 20px;
281
- margin-bottom: 20px;
282
- }
283
-
284
- .control-group {
285
- background: ${darkTheme ? '#2a2a2a' : '#fff'};
286
- border: 1px solid ${darkTheme ? '#444' : '#ddd'};
287
- border-radius: 8px;
288
- padding: 15px 20px;
289
- }
290
-
291
- .control-group h4 {
292
- margin: 0 0 15px 0;
293
- color: ${darkTheme ? '#fff' : '#333'};
294
- font-size: 20px;
295
- text-transform: uppercase;
296
- letter-spacing: 1px;
297
- }
298
-
299
- .button-group {
300
- display: flex;
301
- gap: 10px;
302
- flex-wrap: wrap;
303
- }
304
-
305
- .control-btn {
306
- flex: 1;
307
- min-width: 80px;
308
- padding: 12px 20px;
309
- border: 2px solid ${darkTheme ? '#444' : '#ddd'};
310
- background: ${darkTheme ? '#333' : '#f9f9f9'};
311
- color: ${darkTheme ? '#fff' : '#333'};
312
- border-radius: 6px;
313
- cursor: pointer;
314
- transition: all 0.2s ease;
315
- font-size: 14px;
316
- font-weight: 600;
317
- display: flex;
318
- align-items: center;
319
- justify-content: center;
320
- gap: 8px;
321
- }
322
-
323
- .control-btn:hover {
324
- background: ${darkTheme ? '#444' : '#f0f0f0'};
325
- transform: translateY(-2px);
326
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
327
- }
328
-
329
- .control-btn.active {
330
- background: ${darkTheme ? '#4a9eff' : '#2196F3'};
331
- color: white;
332
- border-color: ${darkTheme ? '#4a9eff' : '#2196F3'};
333
- }
334
-
335
- .control-btn i {
336
- font-size: 16px;
337
- }
338
-
339
- .default-viewer-btn {
340
- width: 100%;
341
- padding: 15px;
342
- background: ${darkTheme ? '#4caf50' : '#4CAF50'};
343
- color: white;
344
- border: none;
345
- border-radius: 8px;
346
- cursor: pointer;
347
- font-size: 16px;
348
- font-weight: 600;
349
- transition: all 0.2s ease;
350
- display: flex;
351
- align-items: center;
352
- justify-content: center;
353
- gap: 10px;
354
- }
355
-
356
- .default-viewer-btn:hover {
357
- background: ${darkTheme ? '#45a049' : '#45a049'};
358
- transform: translateY(-2px);
359
- box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
360
- }
361
-
362
- .control-btn:disabled {
363
- background: ${darkTheme ? '#555' : '#ccc'};
364
- cursor: not-allowed;
365
- transform: none;
366
- opacity: 0.5;
367
- }
368
-
369
- .control-btn .frame-count {
370
- font-size: 11px;
371
- opacity: 0.7;
372
- margin-left: 4px;
373
- }
374
-
375
- .default-viewer-btn:disabled {
376
- background: ${darkTheme ? '#555' : '#ccc'};
377
- cursor: not-allowed;
378
- transform: none;
379
- }
380
-
381
- .edit-btn {
382
- background: ${darkTheme ? '#4a9eff' : '#2196F3'};
383
- }
384
-
385
- .edit-btn:hover {
386
- background: ${darkTheme ? '#3a8eff' : '#1186f2'};
387
- }
388
-
389
- @media (max-width: 768px) {
390
- .webp-display-area {
391
- max-height: 500px;
392
- min-height: 300px;
393
- padding: 20px;
394
- }
395
-
396
- .webp-canvas-container canvas,
397
- .webp-canvas-container img {
398
- max-width: 100%;
399
- max-height: 440px;
400
- }
401
- }
402
-
403
- @media (max-width: 600px) {
404
- .webp-display-area {
405
- max-height: 400px;
406
- min-height: 250px;
407
- padding: 15px;
408
- }
409
-
410
- .webp-canvas-container canvas,
411
- .webp-canvas-container img {
412
- max-height: 340px;
413
- }
414
-
415
- .button-group {
416
- flex-direction: column;
417
- }
418
-
419
- .control-btn {
420
- min-width: 100%;
421
- }
422
- }
423
- .item-data-key-label {
424
- font-size: 16px;
425
- color: ${darkTheme ? '#aaa' : '#666'};
426
- text-transform: uppercase;
427
- }
428
- .item-data-value-label {
429
- font-size: 20px;
430
- font-weight: 700;
431
- color: ${darkTheme ? '#aaa' : '#666'};
432
- text-align: center;
433
- }
434
- .item-stat-entry {
435
- display: flex;
436
- flex-direction: column;
437
- gap: 6px;
438
- padding: 12px;
439
- background: ${darkTheme ? '#1a1a1a' : '#f9f9f9'};
440
- border-radius: 6px;
441
- border: 1px solid ${darkTheme ? '#333' : '#e0e0e0'};
442
- }
443
- .no-data-container {
444
- grid-column: 1 / -1;
445
- text-align: center;
446
- color: ${darkTheme ? '#666' : '#999'};
447
- padding: 20px;
448
- }
449
-
450
- @media (max-width: 850px) {
451
- .object-layer-viewer-container {
452
- padding: 5px;
453
- }
454
- }
455
- </style>`,
456
- );
457
- };
458
- htmls(
459
- `#${id}`,
460
- html`
461
- <div class="hide style-${id}"></div>
462
-
463
- <div class="object-layer-viewer-container">
464
- <!-- Item Data Section -->
465
- <div class="control-group" style="margin-bottom: 20px;">
466
- <h4><i class="fa-solid fa-cube"></i> Item Data</h4>
467
- <div
468
- style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; padding: 10px 0;"
469
- >
470
- <div style="display: flex; flex-direction: column; gap: 4px;">
471
- <span class="item-data-key-label">Item ID</span>
472
- <span style="font-weight: 600;">${itemId}</span>
473
- </div>
474
- <div style="display: flex; flex-direction: column; gap: 4px;">
475
- <span class="item-data-key-label">Type</span>
476
- <span style="font-weight: 600;">${itemType}</span>
477
- </div>
478
- ${itemDescription
479
- ? html`<div style="display: flex; flex-direction: column; gap: 4px;">
480
- <span class="item-data-key-label">Description</span>
481
- <span style="font-weight: 600;">${itemDescription}</span>
482
- </div>`
483
- : ''}
484
- <div style="display: flex; flex-direction: column; gap: 4px;">
485
- <span class="item-data-key-label">Activable</span>
486
- <span style="font-weight: 600;">${itemActivable ? 'Yes' : 'No'}</span>
487
- </div>
488
- </div>
489
- </div>
490
-
491
- <!-- Stats Data Section -->
492
- <div class="control-group" style="margin-bottom: 20px;">
493
- <h4><i class="fa-solid fa-chart-bar"></i> Stats Data</h4>
494
- <div
495
- style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; padding: 10px 0;"
496
- >
497
- ${Object.keys(stats).length > 0
498
- ? Object.entries(stats)
499
- .map(([statKey, statValue]) => {
500
- const statInfo = ObjectLayerEngineModal.statDescriptions[statKey];
501
- if (!statInfo) return '';
502
- return html`
503
- <div class="item-stat-entry">
504
- <div style="display: flex; align-items: center; gap: 8px;">
505
- <i class="${statInfo.icon}" id="stat-icon-${statKey}-${id}"></i>
506
- <span class="item-data-key-label">${statInfo.title}</span>
507
- </div>
508
- <span class="item-data-value-label">${statValue}</span>
509
- </div>
510
- `;
511
- })
512
- .join('')
513
- : html`<div class="no-data-container">No stats data available</div>`}
514
- </div>
515
- </div>
516
-
517
- <div class="webp-display-area">
518
- <div class="webp-canvas-container" id="webp-canvas-container">
519
- <div style="text-align: center; color: ${darkTheme ? '#aaa' : '#666'};">
520
- <i class="fa-solid fa-image" style="font-size: 48px; opacity: 0.3; margin-bottom: 16px;"></i>
521
- <p style="margin: 0; font-size: 14px;">WebP preview will appear here</p>
522
- </div>
523
- <div id="webp-loading-overlay" class="loading-overlay" style="display: none;">
524
- <div>
525
- <i class="fa-solid fa-spinner fa-spin"></i>
526
- <span style="margin-left: 10px;">Generating WebP...</span>
527
- </div>
528
- </div>
529
- </div>
530
- </div>
531
-
532
- <div class="controls-container">
533
- <div class="control-group">
534
- <h4><i class="fa-solid fa-compass"></i> Direction</h4>
535
- <div class="button-group">
536
- <button
537
- class="control-btn ${this.Data.currentDirection === 'up' ? 'active' : ''}"
538
- data-direction="up"
539
- ${!hasFrames('up', this.Data.currentMode) ? 'disabled' : ''}
540
- >
541
- <i class="fa-solid fa-arrow-up"></i>
542
- <span>Up</span>
543
- ${hasFrames('up', this.Data.currentMode)
544
- ? html`<span class="frame-count">(${getFrameCount('up', this.Data.currentMode)})</span>`
545
- : ''}
546
- </button>
547
- <button
548
- class="control-btn ${this.Data.currentDirection === 'down' ? 'active' : ''}"
549
- data-direction="down"
550
- ${!hasFrames('down', this.Data.currentMode) ? 'disabled' : ''}
551
- >
552
- <i class="fa-solid fa-arrow-down"></i>
553
- <span>Down</span>
554
- ${hasFrames('down', this.Data.currentMode)
555
- ? html`<span class="frame-count">(${getFrameCount('down', this.Data.currentMode)})</span>`
556
- : ''}
557
- </button>
558
- <button
559
- class="control-btn ${this.Data.currentDirection === 'left' ? 'active' : ''}"
560
- data-direction="left"
561
- ${!hasFrames('left', this.Data.currentMode) ? 'disabled' : ''}
562
- >
563
- <i class="fa-solid fa-arrow-left"></i>
564
- <span>Left</span>
565
- ${hasFrames('left', this.Data.currentMode)
566
- ? html`<span class="frame-count">(${getFrameCount('left', this.Data.currentMode)})</span>`
567
- : ''}
568
- </button>
569
- <button
570
- class="control-btn ${this.Data.currentDirection === 'right' ? 'active' : ''}"
571
- data-direction="right"
572
- ${!hasFrames('right', this.Data.currentMode) ? 'disabled' : ''}
573
- >
574
- <i class="fa-solid fa-arrow-right"></i>
575
- <span>Right</span>
576
- ${hasFrames('right', this.Data.currentMode)
577
- ? html`<span class="frame-count">(${getFrameCount('right', this.Data.currentMode)})</span>`
578
- : ''}
579
- </button>
580
- </div>
581
- </div>
582
-
583
- <div class="control-group">
584
- <h4><i class="fa-solid fa-person-running"></i> Mode</h4>
585
- <div class="button-group">
586
- <button
587
- class="control-btn ${this.Data.currentMode === 'idle' ? 'active' : ''}"
588
- data-mode="idle"
589
- ${!hasFrames(this.Data.currentDirection, 'idle') ? 'disabled' : ''}
590
- >
591
- <i class="fa-solid fa-user"></i>
592
- <span>Idle</span>
593
- ${hasFrames(this.Data.currentDirection, 'idle')
594
- ? html`<span class="frame-count">(${getFrameCount(this.Data.currentDirection, 'idle')})</span>`
595
- : ''}
596
- </button>
597
- <button
598
- class="control-btn ${this.Data.currentMode === 'walking' ? 'active' : ''}"
599
- data-mode="walking"
600
- ${!hasFrames(this.Data.currentDirection, 'walking') ? 'disabled' : ''}
601
- >
602
- <i class="fa-solid fa-person-walking"></i>
603
- <span>Walking</span>
604
- ${hasFrames(this.Data.currentDirection, 'walking')
605
- ? html`<span class="frame-count">(${getFrameCount(this.Data.currentDirection, 'walking')})</span>`
606
- : ''}
607
- </button>
608
- </div>
609
- </div>
610
- </div>
611
-
612
- <div style="display: flex; gap: 10px; margin-top: 20px;">
613
- <button class="default-viewer-btn" id="return-to-list-btn">
614
- <i class="fa-solid fa-arrow-left"></i>
615
- <span>Return to List</span>
616
- </button>
617
- <button class="default-viewer-btn" id="download-webp-btn">
618
- <i class="fa-solid fa-download"></i>
619
- <span>Download WebP</span>
620
- </button>
621
- <button class="default-viewer-btn edit-btn" id="edit-object-layer-btn">
622
- <i class="fa-solid fa-edit"></i>
623
- <span>Edit</span>
624
- </button>
625
- </div>
626
- </div>
627
- `,
628
- );
629
- ThemeEvents[id]();
630
- // Attach event listeners
631
- this.attachEventListeners({ Elements });
632
- },
633
-
634
- attachEventListeners: function ({ Elements }) {
635
- // Direction buttons
636
- const directionButtons = document.querySelectorAll('[data-direction]');
637
- directionButtons.forEach((btn) => {
638
- btn.addEventListener('click', async (e) => {
639
- if (e.currentTarget.disabled) return;
640
- const direction = e.currentTarget.getAttribute('data-direction');
641
- if (direction !== this.Data.currentDirection) {
642
- this.Data.currentDirection = direction;
643
- await this.renderViewer({ Elements });
644
- await this.attachEventListeners({ Elements });
645
- await this.generateWebp();
646
- }
647
- });
648
- });
649
-
650
- // Mode buttons
651
- const modeButtons = document.querySelectorAll('[data-mode]');
652
- modeButtons.forEach((btn) => {
653
- btn.addEventListener('click', async (e) => {
654
- if (e.currentTarget.disabled) return;
655
- const mode = e.currentTarget.getAttribute('data-mode');
656
- if (mode !== this.Data.currentMode) {
657
- this.Data.currentMode = mode;
658
- await this.renderViewer({ Elements });
659
- await this.attachEventListeners({ Elements });
660
- await this.generateWebp();
661
- }
662
- });
663
- });
664
-
665
- // Download button
666
- const downloadBtn = s('#download-webp-btn');
667
- if (downloadBtn) {
668
- downloadBtn.addEventListener('click', () => {
669
- this.downloadWebp();
670
- });
671
- }
672
-
673
- // Return to list button
674
- const listBtn = s('#return-to-list-btn');
675
- if (listBtn) {
676
- listBtn.addEventListener('click', () => {
677
- setPath(`${getProxyPath()}object-layer-engine-viewer`);
678
- setQueryParams({ cid: null });
679
- ObjectLayerEngineViewer.renderEmpty({ Elements });
680
- });
681
- }
682
-
683
- // Edit button
684
- const editBtn = s('#edit-object-layer-btn');
685
- if (editBtn) {
686
- editBtn.addEventListener('click', () => {
687
- this.toEngine();
688
- });
689
- }
690
- },
691
-
692
- generateWebp: async function () {
693
- if (this.Data.isGenerating) return;
694
-
695
- const { objectLayer, frameCounts, currentDirection, currentMode } = this.Data;
696
- if (!objectLayer || !frameCounts) return;
697
-
698
- // Get numeric direction code
699
- const numericCode = this.getDirectionCode(currentDirection, currentMode);
700
- if (!numericCode) {
701
- NotificationManager.Push({
702
- html: `Invalid direction/mode combination: ${currentDirection} ${currentMode}`,
703
- status: 'error',
704
- });
705
- return;
706
- }
707
-
708
- const frameCount = frameCounts[numericCode];
709
-
710
- if (!frameCount || frameCount === 0) {
711
- NotificationManager.Push({
712
- html: `No frames available for ${currentDirection} ${currentMode}`,
713
- status: 'warning',
714
- });
715
- return;
716
- }
717
-
718
- const itemType = objectLayer.data.item.type;
719
- const itemId = objectLayer.data.item.id;
720
- const frameDuration = objectLayer.data.render.frame_duration || 100;
721
-
722
- this.Data.isGenerating = true;
723
- this.showLoading(true);
724
-
725
- // Update loading overlay text
726
- const loadingOverlay = s('#webp-loading-overlay');
727
- if (loadingOverlay) {
728
- const loadingText = loadingOverlay.querySelector('span');
729
- if (loadingText) {
730
- loadingText.textContent = `Loading WebP animation for ${currentDirection} ${currentMode}...`;
731
- }
732
- }
733
-
734
- try {
735
- // Call the WebP generation API endpoint
736
- const { status, data } = await ObjectLayerService.generateWebp({
737
- itemType,
738
- itemId,
739
- directionCode: numericCode,
740
- });
741
-
742
- if (status === 'success' && data) {
743
- // Store the blob URL
744
- this.Data.webp = data;
745
-
746
- // Display the WebP in the viewer
747
- const container = s('#webp-canvas-container');
748
- if (container) {
749
- // Clear container
750
- container.innerHTML = '';
751
-
752
- // Create and append image
753
- const img = document.createElement('img');
754
- img.src = data;
755
- img.alt = 'WebP Animation';
756
- container.appendChild(img);
757
-
758
- // Create and append info badge
759
- const infoBadge = document.createElement('div');
760
- infoBadge.className = 'webp-info-badge';
761
- infoBadge.innerHTML = html`
762
- <span class="info-label" style="margin-left: 8px;">Frames:</span>
763
- <span>${frameCount}</span><br />
764
- <span class="info-label" style="margin-left: 8px;">Duration:</span>
765
- <span>${frameDuration}ms</span><br />
766
- <span class="info-label" style="margin-left: 8px;">Direction:</span>
767
- <span>${currentDirection}</span><br />
768
- <span class="info-label" style="margin-left: 8px;">Mode:</span>
769
- <span>${currentMode}</span><br />
770
- <span class="info-label" style="margin-left: 8px;">Code:</span>
771
- <span>${numericCode}</span>
772
- `;
773
- const displayArea = s('.webp-display-area');
774
- if (displayArea) {
775
- displayArea.appendChild(infoBadge);
776
- }
777
- }
778
-
779
- // NotificationManager.Push({
780
- // html: `WebP generated successfully (${frameCount} frames, ${frameDuration}ms duration)`,
781
- // status: 'success',
782
- // });
783
- } else {
784
- throw new Error('Failed to generate WebP');
785
- }
786
-
787
- this.Data.isGenerating = false;
788
- this.showLoading(false);
789
- } catch (error) {
790
- logger.error('Error generating WebP:', error);
791
- NotificationManager.Push({
792
- html: `Failed to generate WebP: ${error.message}`,
793
- status: 'error',
794
- });
795
- this.Data.isGenerating = false;
796
- this.showLoading(false);
797
- }
798
- },
799
-
800
- showLoading: function (show) {
801
- const overlay = s('#webp-loading-overlay');
802
- if (overlay) {
803
- overlay.style.display = show ? 'flex' : 'none';
804
- if (!show) {
805
- // Reset loading text when hiding
806
- const loadingText = overlay.querySelector('span');
807
- if (loadingText) {
808
- loadingText.textContent = 'Generating WebP...';
809
- }
810
- }
811
- }
812
-
813
- const downloadBtn = s('#download-webp-btn');
814
- if (downloadBtn) {
815
- downloadBtn.disabled = show;
816
- }
817
-
818
- // Remove old info badge if exists
819
- const oldBadge = s('.webp-info-badge');
820
- if (oldBadge && show) {
821
- oldBadge.remove();
822
- }
823
- },
824
-
825
- downloadWebp: function () {
826
- if (!this.Data.webp) {
827
- NotificationManager.Push({
828
- html: 'No WebP available to download',
829
- status: 'warning',
830
- });
831
- return;
832
- }
833
-
834
- const { objectLayer, currentDirection, currentMode } = this.Data;
835
- const numericCode = this.getDirectionCode(currentDirection, currentMode);
836
- const filename = `${objectLayer.data.item.id}_${currentDirection}_${currentMode}_${numericCode}.webp`;
837
-
838
- // Create a temporary anchor element to trigger download
839
- const a = document.createElement('a');
840
- a.href = this.Data.webp;
841
- a.download = filename;
842
- document.body.appendChild(a);
843
- a.click();
844
- document.body.removeChild(a);
845
-
846
- NotificationManager.Push({
847
- html: `WebP downloaded: ${filename}`,
848
- status: 'success',
849
- });
850
- },
851
-
852
- toEngine: function () {
853
- const { objectLayer } = this.Data;
854
- if (!objectLayer || !objectLayer._id) return;
855
-
856
- // Navigate to editor route first
857
- setPath(`${getProxyPath()}object-layer-engine`);
858
- // Then add query param without replacing history
859
- setQueryParams({ cid: objectLayer._id }, { replace: true });
860
-
861
- if (s(`.modal-object-layer-engine`)) {
862
- ObjectLayerEngineModal.Reload();
863
- } else {
864
- s(`.main-btn-object-layer-engine`)?.click();
865
- }
866
- },
867
-
868
- Reload: async function ({ Elements }) {
869
- const queryParams = new URLSearchParams(window.location.search);
870
- const cid = queryParams.get('cid');
871
-
872
- if (cid) {
873
- await this.loadObjectLayer(cid, Elements);
874
- } else {
875
- this.renderEmpty({ Elements });
876
- }
877
- },
878
- };
879
-
880
- export { ObjectLayerEngineViewer };