underpost 2.89.35 → 2.89.44
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/README.md +4 -2
- package/bin/deploy.js +22 -15
- package/cli.md +23 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +10 -6
- package/manifests/deployment/dd-test-development/proxy.yaml +2 -0
- package/manifests/deployment/kafka/deployment.yaml +0 -2
- package/manifests/deployment/spark/spark-pi-py.yaml +0 -1
- package/manifests/deployment/tensorflow/tf-gpu-test.yaml +0 -2
- package/manifests/envoy-service-nodeport.yaml +0 -1
- package/manifests/kubeadm-calico-config.yaml +10 -115
- package/manifests/letsencrypt-prod.yaml +0 -1
- package/manifests/mariadb/statefulset.yaml +1 -1
- package/manifests/mongodb/statefulset.yaml +11 -11
- package/manifests/mongodb-4.4/service-deployment.yaml +1 -3
- package/manifests/mysql/pv-pvc.yaml +1 -1
- package/manifests/mysql/statefulset.yaml +1 -1
- package/manifests/valkey/service.yaml +0 -1
- package/manifests/valkey/statefulset.yaml +2 -3
- package/package.json +1 -1
- package/scripts/device-scan.sh +43 -21
- package/scripts/rpmfusion-ffmpeg-setup.sh +1 -0
- package/src/api/user/user.model.js +10 -1
- package/src/cli/cluster.js +51 -26
- package/src/cli/deploy.js +73 -39
- package/src/cli/index.js +22 -1
- package/src/cli/monitor.js +9 -5
- package/src/cli/repository.js +1 -1
- package/src/cli/run.js +38 -21
- package/src/client/components/core/Logger.js +1 -1
- package/src/client/components/core/Modal.js +5 -0
- package/src/client/components/core/ObjectLayerEngineModal.js +334 -71
- package/src/client/components/core/ObjectLayerEngineViewer.js +170 -403
- package/src/client/components/core/Router.js +10 -1
- package/src/client/services/default/default.management.js +25 -5
- package/src/index.js +1 -1
- package/src/server/client-build.js +5 -4
- package/src/server/conf.js +1 -1
- package/src/server/start.js +1 -1
- package/manifests/kubelet-config.yaml +0 -65
- package/manifests/mongodb/backup-access.yaml +0 -16
- package/manifests/mongodb/backup-cronjob.yaml +0 -42
- package/manifests/mongodb/backup-pv-pvc.yaml +0 -22
- package/manifests/mongodb/configmap.yaml +0 -26
|
@@ -3,10 +3,13 @@ import { getProxyPath, listenQueryPathInstance, setPath, setQueryParams } from '
|
|
|
3
3
|
import { ObjectLayerService } from '../../services/object-layer/object-layer.service.js';
|
|
4
4
|
import { NotificationManager } from './NotificationManager.js';
|
|
5
5
|
import { htmls, s } from './VanillaJs.js';
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
import { darkTheme, ThemeEvents } from './Css.js';
|
|
8
8
|
import { ObjectLayerManagement } from '../../services/object-layer/object-layer.management.js';
|
|
9
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';
|
|
10
13
|
|
|
11
14
|
const logger = loggerFactory(import.meta);
|
|
12
15
|
|
|
@@ -16,12 +19,8 @@ const ObjectLayerEngineViewer = {
|
|
|
16
19
|
frameCounts: null,
|
|
17
20
|
currentDirection: 'down',
|
|
18
21
|
currentMode: 'idle',
|
|
19
|
-
|
|
20
|
-
gifWorkerBlob: null,
|
|
22
|
+
webp: null,
|
|
21
23
|
isGenerating: false,
|
|
22
|
-
// Binary transparency settings for GIF export
|
|
23
|
-
gifTransparencyPlaceholder: { r: 100, g: 100, b: 100 }, // magenta - unlikely to exist in sprites
|
|
24
|
-
transparencyThreshold: 16, // alpha threshold (0-255) for binary transparency
|
|
25
24
|
},
|
|
26
25
|
|
|
27
26
|
// Map user-friendly direction/mode to numeric direction codes
|
|
@@ -40,24 +39,13 @@ const ObjectLayerEngineViewer = {
|
|
|
40
39
|
return directionCodeMap[key] || null;
|
|
41
40
|
},
|
|
42
41
|
|
|
43
|
-
// Get all possible direction names for a direction code
|
|
44
|
-
getDirectionsFromDirectionCode: function (directionCode) {
|
|
45
|
-
const directionMap = {
|
|
46
|
-
'08': ['down_idle', 'none_idle', 'default_idle'],
|
|
47
|
-
18: ['down_walking'],
|
|
48
|
-
'02': ['up_idle'],
|
|
49
|
-
12: ['up_walking'],
|
|
50
|
-
'04': ['left_idle', 'up_left_idle', 'down_left_idle'],
|
|
51
|
-
14: ['left_walking', 'up_left_walking', 'down_left_walking'],
|
|
52
|
-
'06': ['right_idle', 'up_right_idle', 'down_right_idle'],
|
|
53
|
-
16: ['right_walking', 'up_right_walking', 'down_right_walking'],
|
|
54
|
-
};
|
|
55
|
-
return directionMap[directionCode] || [];
|
|
56
|
-
},
|
|
57
|
-
|
|
58
42
|
Render: async function ({ Elements }) {
|
|
59
43
|
const id = 'object-layer-engine-viewer';
|
|
60
44
|
|
|
45
|
+
Modal.Data[`modal-${id}`].onReloadModalListener[id] = async () => {
|
|
46
|
+
ObjectLayerEngineViewer.Reload({ Elements });
|
|
47
|
+
};
|
|
48
|
+
|
|
61
49
|
// Listen for cid query parameter
|
|
62
50
|
listenQueryPathInstance(
|
|
63
51
|
{
|
|
@@ -65,7 +53,7 @@ const ObjectLayerEngineViewer = {
|
|
|
65
53
|
routeId: 'object-layer-engine-viewer',
|
|
66
54
|
event: async (cid) => {
|
|
67
55
|
if (cid) {
|
|
68
|
-
await this.loadObjectLayer(cid);
|
|
56
|
+
await this.loadObjectLayer(cid, Elements);
|
|
69
57
|
} else {
|
|
70
58
|
this.renderEmpty({ Elements });
|
|
71
59
|
}
|
|
@@ -74,25 +62,6 @@ const ObjectLayerEngineViewer = {
|
|
|
74
62
|
'cid',
|
|
75
63
|
);
|
|
76
64
|
|
|
77
|
-
setTimeout(async () => {
|
|
78
|
-
htmls(
|
|
79
|
-
`#${id}`,
|
|
80
|
-
html` <div class="inl section-mp">
|
|
81
|
-
<div class="in">
|
|
82
|
-
<div class="fl">
|
|
83
|
-
<div class="in fll">
|
|
84
|
-
${await BtnIcon.Render({
|
|
85
|
-
class: 'section-mp main-button',
|
|
86
|
-
label: html`<i class="fa-solid fa-arrow-left"></i> ${' Back'}`,
|
|
87
|
-
attrs: `data-id="btn-back"`,
|
|
88
|
-
})}
|
|
89
|
-
</div>
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
</div>`,
|
|
93
|
-
);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
65
|
return html`
|
|
97
66
|
<div class="fl">
|
|
98
67
|
<div class="in ${id}" id="${id}">
|
|
@@ -106,16 +75,22 @@ const ObjectLayerEngineViewer = {
|
|
|
106
75
|
|
|
107
76
|
renderEmpty: async function ({ Elements }) {
|
|
108
77
|
const id = 'object-layer-engine-viewer';
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
+
);
|
|
116
91
|
},
|
|
117
92
|
|
|
118
|
-
loadObjectLayer: async function (objectLayerId) {
|
|
93
|
+
loadObjectLayer: async function (objectLayerId, Elements) {
|
|
119
94
|
const id = 'object-layer-engine-viewer';
|
|
120
95
|
|
|
121
96
|
try {
|
|
@@ -136,18 +111,18 @@ const ObjectLayerEngineViewer = {
|
|
|
136
111
|
}
|
|
137
112
|
|
|
138
113
|
this.Data.frameCounts = frameData.frameCounts;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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';
|
|
142
120
|
|
|
143
121
|
// Render the viewer UI
|
|
144
|
-
await this.renderViewer();
|
|
145
|
-
|
|
146
|
-
// Initialize gif.js worker
|
|
147
|
-
await this.initGifJs();
|
|
122
|
+
await this.renderViewer({ Elements });
|
|
148
123
|
|
|
149
|
-
// Generate
|
|
150
|
-
await this.
|
|
124
|
+
// Generate WebP
|
|
125
|
+
await this.generateWebp();
|
|
151
126
|
} catch (error) {
|
|
152
127
|
logger.error('Error loading object layer:', error);
|
|
153
128
|
NotificationManager.Push({
|
|
@@ -169,7 +144,7 @@ const ObjectLayerEngineViewer = {
|
|
|
169
144
|
}
|
|
170
145
|
},
|
|
171
146
|
|
|
172
|
-
renderViewer: async function () {
|
|
147
|
+
renderViewer: async function ({ Elements }) {
|
|
173
148
|
const id = 'object-layer-engine-viewer';
|
|
174
149
|
const { objectLayer, frameCounts } = this.Data;
|
|
175
150
|
|
|
@@ -179,8 +154,6 @@ const ObjectLayerEngineViewer = {
|
|
|
179
154
|
const itemId = objectLayer.data.item.id;
|
|
180
155
|
const itemDescription = objectLayer.data.item.description || '';
|
|
181
156
|
const itemActivable = objectLayer.data.item.activable || false;
|
|
182
|
-
const frameDuration = objectLayer.data.render.frame_duration || 100;
|
|
183
|
-
const isStateless = objectLayer.data.render.is_stateless || false;
|
|
184
157
|
|
|
185
158
|
// Get stats data
|
|
186
159
|
const stats = objectLayer.data.stats || {};
|
|
@@ -220,7 +193,7 @@ const ObjectLayerEngineViewer = {
|
|
|
220
193
|
color: ${darkTheme ? '#fff' : '#333'};
|
|
221
194
|
}
|
|
222
195
|
|
|
223
|
-
.
|
|
196
|
+
.webp-display-area {
|
|
224
197
|
background: ${darkTheme ? '#2a2a2a' : '#f5f5f5'};
|
|
225
198
|
border: 2px solid ${darkTheme ? '#444' : '#ddd'};
|
|
226
199
|
border-radius: 12px;
|
|
@@ -236,7 +209,7 @@ const ObjectLayerEngineViewer = {
|
|
|
236
209
|
overflow: auto;
|
|
237
210
|
}
|
|
238
211
|
|
|
239
|
-
.
|
|
212
|
+
.webp-canvas-container {
|
|
240
213
|
position: relative;
|
|
241
214
|
display: flex;
|
|
242
215
|
justify-content: center;
|
|
@@ -245,8 +218,8 @@ const ObjectLayerEngineViewer = {
|
|
|
245
218
|
height: 100%;
|
|
246
219
|
}
|
|
247
220
|
|
|
248
|
-
.
|
|
249
|
-
.
|
|
221
|
+
.webp-canvas-container canvas,
|
|
222
|
+
.webp-canvas-container img {
|
|
250
223
|
image-rendering: pixelated;
|
|
251
224
|
image-rendering: -moz-crisp-edges;
|
|
252
225
|
image-rendering: crisp-edges;
|
|
@@ -262,13 +235,13 @@ const ObjectLayerEngineViewer = {
|
|
|
262
235
|
display: block;
|
|
263
236
|
}
|
|
264
237
|
|
|
265
|
-
.
|
|
238
|
+
.webp-canvas-container canvas {
|
|
266
239
|
background: repeating-conic-gradient(#80808020 0% 25%, #fff0 0% 50%) 50% / 20px 20px;
|
|
267
240
|
min-width: 128px;
|
|
268
241
|
min-height: 128px;
|
|
269
242
|
}
|
|
270
243
|
|
|
271
|
-
.
|
|
244
|
+
.webp-info-badge {
|
|
272
245
|
position: absolute;
|
|
273
246
|
bottom: 10px;
|
|
274
247
|
right: 10px;
|
|
@@ -281,7 +254,7 @@ const ObjectLayerEngineViewer = {
|
|
|
281
254
|
backdrop-filter: blur(4px);
|
|
282
255
|
}
|
|
283
256
|
|
|
284
|
-
.
|
|
257
|
+
.webp-info-badge .info-label {
|
|
285
258
|
opacity: 0.7;
|
|
286
259
|
margin-right: 4px;
|
|
287
260
|
}
|
|
@@ -363,7 +336,7 @@ const ObjectLayerEngineViewer = {
|
|
|
363
336
|
font-size: 16px;
|
|
364
337
|
}
|
|
365
338
|
|
|
366
|
-
.
|
|
339
|
+
.default-viewer-btn {
|
|
367
340
|
width: 100%;
|
|
368
341
|
padding: 15px;
|
|
369
342
|
background: ${darkTheme ? '#4caf50' : '#4CAF50'};
|
|
@@ -380,7 +353,7 @@ const ObjectLayerEngineViewer = {
|
|
|
380
353
|
gap: 10px;
|
|
381
354
|
}
|
|
382
355
|
|
|
383
|
-
.
|
|
356
|
+
.default-viewer-btn:hover {
|
|
384
357
|
background: ${darkTheme ? '#45a049' : '#45a049'};
|
|
385
358
|
transform: translateY(-2px);
|
|
386
359
|
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
|
|
@@ -399,7 +372,7 @@ const ObjectLayerEngineViewer = {
|
|
|
399
372
|
margin-left: 4px;
|
|
400
373
|
}
|
|
401
374
|
|
|
402
|
-
.
|
|
375
|
+
.default-viewer-btn:disabled {
|
|
403
376
|
background: ${darkTheme ? '#555' : '#ccc'};
|
|
404
377
|
cursor: not-allowed;
|
|
405
378
|
transform: none;
|
|
@@ -414,28 +387,28 @@ const ObjectLayerEngineViewer = {
|
|
|
414
387
|
}
|
|
415
388
|
|
|
416
389
|
@media (max-width: 768px) {
|
|
417
|
-
.
|
|
390
|
+
.webp-display-area {
|
|
418
391
|
max-height: 500px;
|
|
419
392
|
min-height: 300px;
|
|
420
393
|
padding: 20px;
|
|
421
394
|
}
|
|
422
395
|
|
|
423
|
-
.
|
|
424
|
-
.
|
|
396
|
+
.webp-canvas-container canvas,
|
|
397
|
+
.webp-canvas-container img {
|
|
425
398
|
max-width: 100%;
|
|
426
399
|
max-height: 440px;
|
|
427
400
|
}
|
|
428
401
|
}
|
|
429
402
|
|
|
430
403
|
@media (max-width: 600px) {
|
|
431
|
-
.
|
|
404
|
+
.webp-display-area {
|
|
432
405
|
max-height: 400px;
|
|
433
406
|
min-height: 250px;
|
|
434
407
|
padding: 15px;
|
|
435
408
|
}
|
|
436
409
|
|
|
437
|
-
.
|
|
438
|
-
.
|
|
410
|
+
.webp-canvas-container canvas,
|
|
411
|
+
.webp-canvas-container img {
|
|
439
412
|
max-height: 340px;
|
|
440
413
|
}
|
|
441
414
|
|
|
@@ -473,6 +446,12 @@ const ObjectLayerEngineViewer = {
|
|
|
473
446
|
color: ${darkTheme ? '#666' : '#999'};
|
|
474
447
|
padding: 20px;
|
|
475
448
|
}
|
|
449
|
+
|
|
450
|
+
@media (max-width: 850px) {
|
|
451
|
+
.object-layer-viewer-container {
|
|
452
|
+
padding: 5px;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
476
455
|
</style>`,
|
|
477
456
|
);
|
|
478
457
|
};
|
|
@@ -535,16 +514,16 @@ const ObjectLayerEngineViewer = {
|
|
|
535
514
|
</div>
|
|
536
515
|
</div>
|
|
537
516
|
|
|
538
|
-
<div class="
|
|
539
|
-
<div class="
|
|
517
|
+
<div class="webp-display-area">
|
|
518
|
+
<div class="webp-canvas-container" id="webp-canvas-container">
|
|
540
519
|
<div style="text-align: center; color: ${darkTheme ? '#aaa' : '#666'};">
|
|
541
520
|
<i class="fa-solid fa-image" style="font-size: 48px; opacity: 0.3; margin-bottom: 16px;"></i>
|
|
542
|
-
<p style="margin: 0; font-size: 14px;">
|
|
521
|
+
<p style="margin: 0; font-size: 14px;">WebP preview will appear here</p>
|
|
543
522
|
</div>
|
|
544
|
-
<div id="
|
|
523
|
+
<div id="webp-loading-overlay" class="loading-overlay" style="display: none;">
|
|
545
524
|
<div>
|
|
546
525
|
<i class="fa-solid fa-spinner fa-spin"></i>
|
|
547
|
-
<span style="margin-left: 10px;">Generating
|
|
526
|
+
<span style="margin-left: 10px;">Generating WebP...</span>
|
|
548
527
|
</div>
|
|
549
528
|
</div>
|
|
550
529
|
</div>
|
|
@@ -631,11 +610,15 @@ const ObjectLayerEngineViewer = {
|
|
|
631
610
|
</div>
|
|
632
611
|
|
|
633
612
|
<div style="display: flex; gap: 10px; margin-top: 20px;">
|
|
634
|
-
<button class="
|
|
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">
|
|
635
618
|
<i class="fa-solid fa-download"></i>
|
|
636
|
-
<span>Download
|
|
619
|
+
<span>Download WebP</span>
|
|
637
620
|
</button>
|
|
638
|
-
<button class="
|
|
621
|
+
<button class="default-viewer-btn edit-btn" id="edit-object-layer-btn">
|
|
639
622
|
<i class="fa-solid fa-edit"></i>
|
|
640
623
|
<span>Edit</span>
|
|
641
624
|
</button>
|
|
@@ -645,10 +628,10 @@ const ObjectLayerEngineViewer = {
|
|
|
645
628
|
);
|
|
646
629
|
ThemeEvents[id]();
|
|
647
630
|
// Attach event listeners
|
|
648
|
-
this.attachEventListeners();
|
|
631
|
+
this.attachEventListeners({ Elements });
|
|
649
632
|
},
|
|
650
633
|
|
|
651
|
-
attachEventListeners: function () {
|
|
634
|
+
attachEventListeners: function ({ Elements }) {
|
|
652
635
|
// Direction buttons
|
|
653
636
|
const directionButtons = document.querySelectorAll('[data-direction]');
|
|
654
637
|
directionButtons.forEach((btn) => {
|
|
@@ -657,9 +640,9 @@ const ObjectLayerEngineViewer = {
|
|
|
657
640
|
const direction = e.currentTarget.getAttribute('data-direction');
|
|
658
641
|
if (direction !== this.Data.currentDirection) {
|
|
659
642
|
this.Data.currentDirection = direction;
|
|
660
|
-
await this.renderViewer();
|
|
661
|
-
await this.attachEventListeners();
|
|
662
|
-
await this.
|
|
643
|
+
await this.renderViewer({ Elements });
|
|
644
|
+
await this.attachEventListeners({ Elements });
|
|
645
|
+
await this.generateWebp();
|
|
663
646
|
}
|
|
664
647
|
});
|
|
665
648
|
});
|
|
@@ -672,104 +655,41 @@ const ObjectLayerEngineViewer = {
|
|
|
672
655
|
const mode = e.currentTarget.getAttribute('data-mode');
|
|
673
656
|
if (mode !== this.Data.currentMode) {
|
|
674
657
|
this.Data.currentMode = mode;
|
|
675
|
-
await this.renderViewer();
|
|
676
|
-
await this.attachEventListeners();
|
|
677
|
-
await this.
|
|
658
|
+
await this.renderViewer({ Elements });
|
|
659
|
+
await this.attachEventListeners({ Elements });
|
|
660
|
+
await this.generateWebp();
|
|
678
661
|
}
|
|
679
662
|
});
|
|
680
663
|
});
|
|
681
664
|
|
|
682
665
|
// Download button
|
|
683
|
-
const downloadBtn = s('#download-
|
|
666
|
+
const downloadBtn = s('#download-webp-btn');
|
|
684
667
|
if (downloadBtn) {
|
|
685
668
|
downloadBtn.addEventListener('click', () => {
|
|
686
|
-
this.
|
|
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 });
|
|
687
680
|
});
|
|
688
681
|
}
|
|
689
682
|
|
|
683
|
+
// Edit button
|
|
690
684
|
const editBtn = s('#edit-object-layer-btn');
|
|
691
685
|
if (editBtn) {
|
|
692
686
|
editBtn.addEventListener('click', () => {
|
|
693
687
|
this.toEngine();
|
|
694
688
|
});
|
|
695
689
|
}
|
|
696
|
-
|
|
697
|
-
// Back button
|
|
698
|
-
setTimeout(() => {
|
|
699
|
-
const backBtn = s('[data-id="btn-back"]');
|
|
700
|
-
if (backBtn) {
|
|
701
|
-
backBtn.addEventListener('click', () => {
|
|
702
|
-
window.history.back();
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
}, 100);
|
|
706
|
-
},
|
|
707
|
-
|
|
708
|
-
selectFirstAvailableDirectionMode: function () {
|
|
709
|
-
const { frameCounts } = this.Data;
|
|
710
|
-
if (!frameCounts) return;
|
|
711
|
-
|
|
712
|
-
// Priority order for directions
|
|
713
|
-
const directions = ['down', 'up', 'left', 'right'];
|
|
714
|
-
// Priority order for modes
|
|
715
|
-
const modes = ['idle', 'walking'];
|
|
716
|
-
|
|
717
|
-
// Try to find first available combination using numeric codes
|
|
718
|
-
for (const mode of modes) {
|
|
719
|
-
for (const direction of directions) {
|
|
720
|
-
const numericCode = this.getDirectionCode(direction, mode);
|
|
721
|
-
if (numericCode && frameCounts[numericCode] && frameCounts[numericCode] > 0) {
|
|
722
|
-
this.Data.currentDirection = direction;
|
|
723
|
-
this.Data.currentMode = mode;
|
|
724
|
-
logger.info(`Auto-selected: ${direction} ${mode} (code: ${numericCode}, ${frameCounts[numericCode]} frames)`);
|
|
725
|
-
return;
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
// If no frames found, log warning
|
|
731
|
-
logger.warn('No frames found for any direction/mode combination');
|
|
732
|
-
},
|
|
733
|
-
|
|
734
|
-
initGifJs: async function () {
|
|
735
|
-
if (this.Data.gifWorkerBlob) return; // Already initialized
|
|
736
|
-
|
|
737
|
-
try {
|
|
738
|
-
// Load gif.js library
|
|
739
|
-
await this.loadScript('https://cdn.jsdelivr.net/npm/gif.js@0.2.0/dist/gif.min.js');
|
|
740
|
-
|
|
741
|
-
// Fetch worker script
|
|
742
|
-
const response = await fetch('https://cdn.jsdelivr.net/npm/gif.js@0.2.0/dist/gif.worker.js');
|
|
743
|
-
if (!response.ok) {
|
|
744
|
-
throw new Error('Failed to fetch gif.worker.js');
|
|
745
|
-
}
|
|
746
|
-
const workerBlob = await response.blob();
|
|
747
|
-
this.Data.gifWorkerBlob = URL.createObjectURL(workerBlob);
|
|
748
|
-
|
|
749
|
-
logger.info('gif.js initialized successfully');
|
|
750
|
-
} catch (error) {
|
|
751
|
-
logger.error('Error initializing gif.js:', error);
|
|
752
|
-
throw error;
|
|
753
|
-
}
|
|
754
690
|
},
|
|
755
691
|
|
|
756
|
-
|
|
757
|
-
return new Promise((resolve, reject) => {
|
|
758
|
-
// Check if already loaded
|
|
759
|
-
if (document.querySelector(`script[src="${src}"]`)) {
|
|
760
|
-
resolve();
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
const script = document.createElement('script');
|
|
765
|
-
script.src = src;
|
|
766
|
-
script.onload = resolve;
|
|
767
|
-
script.onerror = reject;
|
|
768
|
-
document.head.appendChild(script);
|
|
769
|
-
});
|
|
770
|
-
},
|
|
771
|
-
|
|
772
|
-
generateGif: async function () {
|
|
692
|
+
generateWebp: async function () {
|
|
773
693
|
if (this.Data.isGenerating) return;
|
|
774
694
|
|
|
775
695
|
const { objectLayer, frameCounts, currentDirection, currentMode } = this.Data;
|
|
@@ -802,128 +722,74 @@ const ObjectLayerEngineViewer = {
|
|
|
802
722
|
this.Data.isGenerating = true;
|
|
803
723
|
this.showLoading(true);
|
|
804
724
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
// Update loading message
|
|
813
|
-
const loadingOverlay = s('#gif-loading-overlay');
|
|
814
|
-
if (loadingOverlay) {
|
|
815
|
-
loadingOverlay.querySelector('span').textContent = `Loading frames... (0/${frames.length})`;
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
// Load all frames to find maximum dimensions
|
|
819
|
-
const loadedImages = [];
|
|
820
|
-
let maxWidth = 0;
|
|
821
|
-
let maxHeight = 0;
|
|
822
|
-
|
|
823
|
-
for (let i = 0; i < frames.length; i++) {
|
|
824
|
-
const img = await this.loadImage(frames[i]);
|
|
825
|
-
loadedImages.push(img);
|
|
826
|
-
maxWidth = Math.max(maxWidth, img.naturalWidth);
|
|
827
|
-
maxHeight = Math.max(maxHeight, img.naturalHeight);
|
|
828
|
-
|
|
829
|
-
// Update progress
|
|
830
|
-
if (loadingOverlay && (i === 0 || i % 5 === 0)) {
|
|
831
|
-
loadingOverlay.querySelector('span').textContent = `Loading frames... (${i + 1}/${frames.length})`;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// Update loading message for GIF generation
|
|
836
|
-
if (loadingOverlay) {
|
|
837
|
-
loadingOverlay.querySelector('span').textContent = 'Generating GIF...';
|
|
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}...`;
|
|
838
731
|
}
|
|
732
|
+
}
|
|
839
733
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
// Create new GIF instance with binary transparency
|
|
847
|
-
const gif = new GIF({
|
|
848
|
-
workers: 2,
|
|
849
|
-
workerScript: this.Data.gifWorkerBlob,
|
|
850
|
-
quality: 10,
|
|
851
|
-
width: maxWidth,
|
|
852
|
-
height: maxHeight,
|
|
853
|
-
transparent: transparentColorHex, // Use magenta as transparent color
|
|
854
|
-
repeat: 0,
|
|
734
|
+
try {
|
|
735
|
+
// Call the WebP generation API endpoint
|
|
736
|
+
const { status, data } = await ObjectLayerService.generateWebp({
|
|
737
|
+
itemType,
|
|
738
|
+
itemId,
|
|
739
|
+
directionCode: numericCode,
|
|
855
740
|
});
|
|
856
741
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
//
|
|
862
|
-
const
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
data[p + 1] = placeholder.g; // G
|
|
892
|
-
data[p + 2] = placeholder.b; // B
|
|
893
|
-
data[p + 3] = 255; // A (fully opaque)
|
|
894
|
-
}
|
|
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);
|
|
895
776
|
}
|
|
896
|
-
|
|
897
|
-
ctx.putImageData(imageData, 0, 0);
|
|
898
|
-
} catch (err) {
|
|
899
|
-
logger.warn(
|
|
900
|
-
'Could not access image data for transparency threshold (CORS issue). Transparency may not work correctly.',
|
|
901
|
-
err,
|
|
902
|
-
);
|
|
903
777
|
}
|
|
904
778
|
|
|
905
|
-
//
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
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');
|
|
911
785
|
}
|
|
912
786
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
this.displayGif(blob, maxWidth, maxHeight, frameDuration, frameCount);
|
|
916
|
-
this.Data.gif = blob;
|
|
917
|
-
this.Data.isGenerating = false;
|
|
918
|
-
this.showLoading(false);
|
|
919
|
-
});
|
|
920
|
-
|
|
921
|
-
// Render the GIF
|
|
922
|
-
gif.render();
|
|
787
|
+
this.Data.isGenerating = false;
|
|
788
|
+
this.showLoading(false);
|
|
923
789
|
} catch (error) {
|
|
924
|
-
logger.error('Error generating
|
|
790
|
+
logger.error('Error generating WebP:', error);
|
|
925
791
|
NotificationManager.Push({
|
|
926
|
-
html: `Failed to generate
|
|
792
|
+
html: `Failed to generate WebP: ${error.message}`,
|
|
927
793
|
status: 'error',
|
|
928
794
|
});
|
|
929
795
|
this.Data.isGenerating = false;
|
|
@@ -931,133 +797,35 @@ const ObjectLayerEngineViewer = {
|
|
|
931
797
|
}
|
|
932
798
|
},
|
|
933
799
|
|
|
934
|
-
loadImage: function (src) {
|
|
935
|
-
return new Promise((resolve, reject) => {
|
|
936
|
-
const img = new Image();
|
|
937
|
-
img.crossOrigin = 'anonymous';
|
|
938
|
-
img.onload = () => resolve(img);
|
|
939
|
-
img.onerror = reject;
|
|
940
|
-
img.src = src;
|
|
941
|
-
});
|
|
942
|
-
},
|
|
943
|
-
|
|
944
|
-
displayGif: function (blob, originalWidth, originalHeight, frameDuration, frameCount) {
|
|
945
|
-
const container = s('#gif-canvas-container');
|
|
946
|
-
if (!container) return;
|
|
947
|
-
|
|
948
|
-
const url = URL.createObjectURL(blob);
|
|
949
|
-
|
|
950
|
-
// Create img element for the animated GIF
|
|
951
|
-
const gifImg = document.createElement('img');
|
|
952
|
-
gifImg.src = url;
|
|
953
|
-
|
|
954
|
-
gifImg.onload = () => {
|
|
955
|
-
// Use provided dimensions or get from image
|
|
956
|
-
const naturalWidth = originalWidth || gifImg.naturalWidth;
|
|
957
|
-
const naturalHeight = originalHeight || gifImg.naturalHeight;
|
|
958
|
-
|
|
959
|
-
// Calculate intelligent scaling based on container and image size
|
|
960
|
-
const containerEl = s('.gif-display-area');
|
|
961
|
-
const containerWidth = containerEl ? containerEl.clientWidth - 60 : 400; // subtract padding
|
|
962
|
-
const containerHeight = containerEl ? containerEl.clientHeight - 60 : 400;
|
|
963
|
-
|
|
964
|
-
// Calculate scale to fit container while maintaining aspect ratio
|
|
965
|
-
const scaleToFitWidth = containerWidth / naturalWidth;
|
|
966
|
-
const scaleToFitHeight = containerHeight / naturalHeight;
|
|
967
|
-
const scaleToFit = Math.min(scaleToFitWidth, scaleToFitHeight);
|
|
968
|
-
|
|
969
|
-
// For pixel art, use integer scaling for better visuals
|
|
970
|
-
// Minimum 2x for small sprites, but respect container size
|
|
971
|
-
let scale = Math.max(1, Math.floor(scaleToFit));
|
|
972
|
-
|
|
973
|
-
// For very small sprites (< 100px), try to scale up more
|
|
974
|
-
if (Math.max(naturalWidth, naturalHeight) < 100) {
|
|
975
|
-
scale = Math.min(4, Math.floor(scaleToFit));
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
// Make sure scaled image fits in container
|
|
979
|
-
const displayWidth = naturalWidth * scale;
|
|
980
|
-
const displayHeight = naturalHeight * scale;
|
|
981
|
-
|
|
982
|
-
if (displayWidth > containerWidth || displayHeight > containerHeight) {
|
|
983
|
-
scale = Math.max(1, scale - 1);
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
gifImg.style.width = `${naturalWidth * scale}px !important`;
|
|
987
|
-
gifImg.style.height = `${naturalHeight * scale}px !important`;
|
|
988
|
-
gifImg.style.maxWidth = '100%';
|
|
989
|
-
gifImg.style.maxHeight = '540px';
|
|
990
|
-
|
|
991
|
-
// Force pixel-perfect rendering (no antialiasing/blur)
|
|
992
|
-
// gifImg.style.imageRendering = 'pixelated';
|
|
993
|
-
// gifImg.style.imageRendering = '-moz-crisp-edges';
|
|
994
|
-
// gifImg.style.imageRendering = 'crisp-edges';
|
|
995
|
-
// gifImg.style.msInterpolationMode = 'nearest-neighbor';
|
|
996
|
-
|
|
997
|
-
// Prevent any browser scaling optimizations
|
|
998
|
-
// gifImg.style.transform = 'translateZ(0)'; // Force GPU rendering
|
|
999
|
-
// gifImg.style.backfaceVisibility = 'hidden'; // Prevent subpixel rendering
|
|
1000
|
-
|
|
1001
|
-
// Clear container and add the GIF
|
|
1002
|
-
container.innerHTML = '';
|
|
1003
|
-
container.appendChild(gifImg);
|
|
1004
|
-
|
|
1005
|
-
// Re-add loading overlay
|
|
1006
|
-
const overlay = document.createElement('div');
|
|
1007
|
-
overlay.id = 'gif-loading-overlay';
|
|
1008
|
-
overlay.className = 'loading-overlay';
|
|
1009
|
-
overlay.style.display = 'none';
|
|
1010
|
-
overlay.innerHTML = html`
|
|
1011
|
-
<div>
|
|
1012
|
-
<i class="fa-solid fa-spinner fa-spin"></i>
|
|
1013
|
-
<span style="margin-left: 10px;">Generating GIF...</span>
|
|
1014
|
-
</div>
|
|
1015
|
-
`;
|
|
1016
|
-
container.appendChild(overlay);
|
|
1017
|
-
|
|
1018
|
-
// Add info badge with dimensions and scale
|
|
1019
|
-
const infoBadge = document.createElement('div');
|
|
1020
|
-
infoBadge.className = 'gif-info-badge';
|
|
1021
|
-
const displayW = Math.round(naturalWidth * scale);
|
|
1022
|
-
const displayH = Math.round(naturalHeight * scale);
|
|
1023
|
-
infoBadge.innerHTML = html`
|
|
1024
|
-
<span class="info-label">Dimensions:</span> ${naturalWidth}x${naturalHeight}px<br />
|
|
1025
|
-
<span class="info-label">Display:</span> ${displayW}x${displayH}px<br />
|
|
1026
|
-
${scale > 1 ? `<span class="info-label">Scale:</span> ${scale}x<br />` : ''}
|
|
1027
|
-
<span class="info-label">Frames:</span> ${frameCount}<br />
|
|
1028
|
-
<span class="info-label">Frame Duration:</span> ${frameDuration}ms<br />
|
|
1029
|
-
<span class="info-label">Total Duration:</span> ${(frameDuration * frameCount) / 1000}s
|
|
1030
|
-
`;
|
|
1031
|
-
s(`.gif-display-area`).appendChild(infoBadge);
|
|
1032
|
-
|
|
1033
|
-
logger.info(`Displaying GIF: ${naturalWidth}x${naturalHeight} at ${scale}x scale (${displayW}x${displayH})`);
|
|
1034
|
-
};
|
|
1035
|
-
|
|
1036
|
-
gifImg.onerror = () => {
|
|
1037
|
-
logger.error('Failed to load GIF image');
|
|
1038
|
-
NotificationManager.Push({
|
|
1039
|
-
html: 'Failed to display GIF',
|
|
1040
|
-
status: 'error',
|
|
1041
|
-
});
|
|
1042
|
-
};
|
|
1043
|
-
},
|
|
1044
|
-
|
|
1045
800
|
showLoading: function (show) {
|
|
1046
|
-
const overlay = s('#
|
|
801
|
+
const overlay = s('#webp-loading-overlay');
|
|
1047
802
|
if (overlay) {
|
|
1048
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
|
+
}
|
|
1049
811
|
}
|
|
1050
812
|
|
|
1051
|
-
const downloadBtn = s('#download-
|
|
813
|
+
const downloadBtn = s('#download-webp-btn');
|
|
1052
814
|
if (downloadBtn) {
|
|
1053
815
|
downloadBtn.disabled = show;
|
|
1054
816
|
}
|
|
817
|
+
|
|
818
|
+
// Remove old info badge if exists
|
|
819
|
+
const oldBadge = s('.webp-info-badge');
|
|
820
|
+
if (oldBadge && show) {
|
|
821
|
+
oldBadge.remove();
|
|
822
|
+
}
|
|
1055
823
|
},
|
|
1056
824
|
|
|
1057
|
-
|
|
1058
|
-
if (!this.Data.
|
|
825
|
+
downloadWebp: function () {
|
|
826
|
+
if (!this.Data.webp) {
|
|
1059
827
|
NotificationManager.Push({
|
|
1060
|
-
html: 'No
|
|
828
|
+
html: 'No WebP available to download',
|
|
1061
829
|
status: 'warning',
|
|
1062
830
|
});
|
|
1063
831
|
return;
|
|
@@ -1065,19 +833,18 @@ const ObjectLayerEngineViewer = {
|
|
|
1065
833
|
|
|
1066
834
|
const { objectLayer, currentDirection, currentMode } = this.Data;
|
|
1067
835
|
const numericCode = this.getDirectionCode(currentDirection, currentMode);
|
|
1068
|
-
const filename = `${objectLayer.data.item.id}_${currentDirection}_${currentMode}_${numericCode}.
|
|
836
|
+
const filename = `${objectLayer.data.item.id}_${currentDirection}_${currentMode}_${numericCode}.webp`;
|
|
1069
837
|
|
|
1070
|
-
|
|
838
|
+
// Create a temporary anchor element to trigger download
|
|
1071
839
|
const a = document.createElement('a');
|
|
1072
|
-
a.href =
|
|
840
|
+
a.href = this.Data.webp;
|
|
1073
841
|
a.download = filename;
|
|
1074
842
|
document.body.appendChild(a);
|
|
1075
843
|
a.click();
|
|
1076
844
|
document.body.removeChild(a);
|
|
1077
|
-
URL.revokeObjectURL(url);
|
|
1078
845
|
|
|
1079
846
|
NotificationManager.Push({
|
|
1080
|
-
html: `
|
|
847
|
+
html: `WebP downloaded: ${filename}`,
|
|
1081
848
|
status: 'success',
|
|
1082
849
|
});
|
|
1083
850
|
},
|
|
@@ -1103,7 +870,7 @@ const ObjectLayerEngineViewer = {
|
|
|
1103
870
|
const cid = queryParams.get('cid');
|
|
1104
871
|
|
|
1105
872
|
if (cid) {
|
|
1106
|
-
await this.loadObjectLayer(cid);
|
|
873
|
+
await this.loadObjectLayer(cid, Elements);
|
|
1107
874
|
} else {
|
|
1108
875
|
this.renderEmpty({ Elements });
|
|
1109
876
|
}
|