cyberia 2.89.2 → 2.89.45
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/.env.development +2 -0
- package/.env.production +2 -0
- package/.env.test +2 -0
- package/.github/workflows/engine-cyberia.cd.yml +4 -0
- package/.github/workflows/release.cd.yml +2 -0
- package/bin/cyberia.js +10 -7
- package/bin/deploy.js +22 -15
- package/bin/index.js +10 -7
- package/cli.md +105 -54
- package/deployment.yaml +34 -6
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +18 -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/pv-pvc-dd.yaml +34 -0
- package/manifests/valkey/service.yaml +0 -1
- package/manifests/valkey/statefulset.yaml +2 -3
- package/package.json +1 -1
- package/proxy.yaml +6 -0
- package/scripts/device-scan.sh +43 -21
- package/scripts/gen-fqdns.sh +14 -0
- package/scripts/ip-info.sh +118 -0
- package/scripts/rpmfusion-ffmpeg-setup.sh +1 -0
- package/src/api/object-layer/object-layer.controller.js +19 -0
- package/src/api/object-layer/object-layer.router.js +4 -0
- package/src/api/object-layer/object-layer.service.js +111 -0
- package/src/api/user/user.model.js +10 -1
- package/src/cli/cluster.js +88 -75
- package/src/cli/deploy.js +165 -85
- package/src/cli/index.js +44 -3
- package/src/cli/monitor.js +12 -6
- package/src/cli/repository.js +13 -1
- package/src/cli/run.js +127 -60
- 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 +336 -72
- package/src/client/components/core/ObjectLayerEngineViewer.js +239 -420
- package/src/client/components/core/Router.js +10 -1
- package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +1 -1
- package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +1 -1
- package/src/client/components/cyberia-portal/MenuCyberiaPortal.js +1 -1
- package/src/client/components/cyberia-portal/ObjectLayerCyberiaPortal.js +44 -4
- package/src/client/services/default/default.management.js +25 -5
- package/src/client/services/object-layer/object-layer.management.js +8 -8
- package/src/client/services/object-layer/object-layer.service.js +34 -0
- 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 +3 -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
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { loggerFactory } from './Logger.js';
|
|
2
|
-
import { getProxyPath, listenQueryPathInstance } from './Router.js';
|
|
2
|
+
import { getProxyPath, listenQueryPathInstance, setPath, setQueryParams } from './Router.js';
|
|
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
|
-
import {
|
|
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';
|
|
9
13
|
|
|
10
14
|
const logger = loggerFactory(import.meta);
|
|
11
15
|
|
|
@@ -15,12 +19,8 @@ const ObjectLayerEngineViewer = {
|
|
|
15
19
|
frameCounts: null,
|
|
16
20
|
currentDirection: 'down',
|
|
17
21
|
currentMode: 'idle',
|
|
18
|
-
|
|
19
|
-
gifWorkerBlob: null,
|
|
22
|
+
webp: null,
|
|
20
23
|
isGenerating: false,
|
|
21
|
-
// Binary transparency settings for GIF export
|
|
22
|
-
gifTransparencyPlaceholder: { r: 100, g: 100, b: 100 }, // magenta - unlikely to exist in sprites
|
|
23
|
-
transparencyThreshold: 16, // alpha threshold (0-255) for binary transparency
|
|
24
24
|
},
|
|
25
25
|
|
|
26
26
|
// Map user-friendly direction/mode to numeric direction codes
|
|
@@ -39,24 +39,13 @@ const ObjectLayerEngineViewer = {
|
|
|
39
39
|
return directionCodeMap[key] || null;
|
|
40
40
|
},
|
|
41
41
|
|
|
42
|
-
// Get all possible direction names for a direction code
|
|
43
|
-
getDirectionsFromDirectionCode: function (directionCode) {
|
|
44
|
-
const directionMap = {
|
|
45
|
-
'08': ['down_idle', 'none_idle', 'default_idle'],
|
|
46
|
-
18: ['down_walking'],
|
|
47
|
-
'02': ['up_idle'],
|
|
48
|
-
12: ['up_walking'],
|
|
49
|
-
'04': ['left_idle', 'up_left_idle', 'down_left_idle'],
|
|
50
|
-
14: ['left_walking', 'up_left_walking', 'down_left_walking'],
|
|
51
|
-
'06': ['right_idle', 'up_right_idle', 'down_right_idle'],
|
|
52
|
-
16: ['right_walking', 'up_right_walking', 'down_right_walking'],
|
|
53
|
-
};
|
|
54
|
-
return directionMap[directionCode] || [];
|
|
55
|
-
},
|
|
56
|
-
|
|
57
42
|
Render: async function ({ Elements }) {
|
|
58
43
|
const id = 'object-layer-engine-viewer';
|
|
59
44
|
|
|
45
|
+
Modal.Data[`modal-${id}`].onReloadModalListener[id] = async () => {
|
|
46
|
+
ObjectLayerEngineViewer.Reload({ Elements });
|
|
47
|
+
};
|
|
48
|
+
|
|
60
49
|
// Listen for cid query parameter
|
|
61
50
|
listenQueryPathInstance(
|
|
62
51
|
{
|
|
@@ -64,34 +53,15 @@ const ObjectLayerEngineViewer = {
|
|
|
64
53
|
routeId: 'object-layer-engine-viewer',
|
|
65
54
|
event: async (cid) => {
|
|
66
55
|
if (cid) {
|
|
67
|
-
await this.loadObjectLayer(cid);
|
|
56
|
+
await this.loadObjectLayer(cid, Elements);
|
|
68
57
|
} else {
|
|
69
|
-
this.renderEmpty();
|
|
58
|
+
this.renderEmpty({ Elements });
|
|
70
59
|
}
|
|
71
60
|
},
|
|
72
61
|
},
|
|
73
62
|
'cid',
|
|
74
63
|
);
|
|
75
64
|
|
|
76
|
-
setTimeout(async () => {
|
|
77
|
-
htmls(
|
|
78
|
-
`#${id}`,
|
|
79
|
-
html` <div class="inl section-mp">
|
|
80
|
-
<div class="in">
|
|
81
|
-
<div class="fl">
|
|
82
|
-
<div class="in fll">
|
|
83
|
-
${await BtnIcon.Render({
|
|
84
|
-
class: 'section-mp main-button',
|
|
85
|
-
label: html`<i class="fa-solid fa-arrow-left"></i> ${' Back'}`,
|
|
86
|
-
attrs: `data-id="btn-back"`,
|
|
87
|
-
})}
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
90
|
-
</div>
|
|
91
|
-
</div>`,
|
|
92
|
-
);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
65
|
return html`
|
|
96
66
|
<div class="fl">
|
|
97
67
|
<div class="in ${id}" id="${id}">
|
|
@@ -103,12 +73,24 @@ const ObjectLayerEngineViewer = {
|
|
|
103
73
|
`;
|
|
104
74
|
},
|
|
105
75
|
|
|
106
|
-
renderEmpty: async function () {
|
|
76
|
+
renderEmpty: async function ({ Elements }) {
|
|
107
77
|
const id = 'object-layer-engine-viewer';
|
|
108
|
-
|
|
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
|
+
);
|
|
109
91
|
},
|
|
110
92
|
|
|
111
|
-
loadObjectLayer: async function (objectLayerId) {
|
|
93
|
+
loadObjectLayer: async function (objectLayerId, Elements) {
|
|
112
94
|
const id = 'object-layer-engine-viewer';
|
|
113
95
|
|
|
114
96
|
try {
|
|
@@ -129,18 +111,18 @@ const ObjectLayerEngineViewer = {
|
|
|
129
111
|
}
|
|
130
112
|
|
|
131
113
|
this.Data.frameCounts = frameData.frameCounts;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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';
|
|
135
120
|
|
|
136
121
|
// Render the viewer UI
|
|
137
|
-
await this.renderViewer();
|
|
138
|
-
|
|
139
|
-
// Initialize gif.js worker
|
|
140
|
-
await this.initGifJs();
|
|
122
|
+
await this.renderViewer({ Elements });
|
|
141
123
|
|
|
142
|
-
// Generate
|
|
143
|
-
await this.
|
|
124
|
+
// Generate WebP
|
|
125
|
+
await this.generateWebp();
|
|
144
126
|
} catch (error) {
|
|
145
127
|
logger.error('Error loading object layer:', error);
|
|
146
128
|
NotificationManager.Push({
|
|
@@ -162,7 +144,7 @@ const ObjectLayerEngineViewer = {
|
|
|
162
144
|
}
|
|
163
145
|
},
|
|
164
146
|
|
|
165
|
-
renderViewer: async function () {
|
|
147
|
+
renderViewer: async function ({ Elements }) {
|
|
166
148
|
const id = 'object-layer-engine-viewer';
|
|
167
149
|
const { objectLayer, frameCounts } = this.Data;
|
|
168
150
|
|
|
@@ -172,8 +154,6 @@ const ObjectLayerEngineViewer = {
|
|
|
172
154
|
const itemId = objectLayer.data.item.id;
|
|
173
155
|
const itemDescription = objectLayer.data.item.description || '';
|
|
174
156
|
const itemActivable = objectLayer.data.item.activable || false;
|
|
175
|
-
const frameDuration = objectLayer.data.render.frame_duration || 100;
|
|
176
|
-
const isStateless = objectLayer.data.render.is_stateless || false;
|
|
177
157
|
|
|
178
158
|
// Get stats data
|
|
179
159
|
const stats = objectLayer.data.stats || {};
|
|
@@ -213,7 +193,7 @@ const ObjectLayerEngineViewer = {
|
|
|
213
193
|
color: ${darkTheme ? '#fff' : '#333'};
|
|
214
194
|
}
|
|
215
195
|
|
|
216
|
-
.
|
|
196
|
+
.webp-display-area {
|
|
217
197
|
background: ${darkTheme ? '#2a2a2a' : '#f5f5f5'};
|
|
218
198
|
border: 2px solid ${darkTheme ? '#444' : '#ddd'};
|
|
219
199
|
border-radius: 12px;
|
|
@@ -229,7 +209,7 @@ const ObjectLayerEngineViewer = {
|
|
|
229
209
|
overflow: auto;
|
|
230
210
|
}
|
|
231
211
|
|
|
232
|
-
.
|
|
212
|
+
.webp-canvas-container {
|
|
233
213
|
position: relative;
|
|
234
214
|
display: flex;
|
|
235
215
|
justify-content: center;
|
|
@@ -238,8 +218,8 @@ const ObjectLayerEngineViewer = {
|
|
|
238
218
|
height: 100%;
|
|
239
219
|
}
|
|
240
220
|
|
|
241
|
-
.
|
|
242
|
-
.
|
|
221
|
+
.webp-canvas-container canvas,
|
|
222
|
+
.webp-canvas-container img {
|
|
243
223
|
image-rendering: pixelated;
|
|
244
224
|
image-rendering: -moz-crisp-edges;
|
|
245
225
|
image-rendering: crisp-edges;
|
|
@@ -255,13 +235,13 @@ const ObjectLayerEngineViewer = {
|
|
|
255
235
|
display: block;
|
|
256
236
|
}
|
|
257
237
|
|
|
258
|
-
.
|
|
238
|
+
.webp-canvas-container canvas {
|
|
259
239
|
background: repeating-conic-gradient(#80808020 0% 25%, #fff0 0% 50%) 50% / 20px 20px;
|
|
260
240
|
min-width: 128px;
|
|
261
241
|
min-height: 128px;
|
|
262
242
|
}
|
|
263
243
|
|
|
264
|
-
.
|
|
244
|
+
.webp-info-badge {
|
|
265
245
|
position: absolute;
|
|
266
246
|
bottom: 10px;
|
|
267
247
|
right: 10px;
|
|
@@ -274,7 +254,7 @@ const ObjectLayerEngineViewer = {
|
|
|
274
254
|
backdrop-filter: blur(4px);
|
|
275
255
|
}
|
|
276
256
|
|
|
277
|
-
.
|
|
257
|
+
.webp-info-badge .info-label {
|
|
278
258
|
opacity: 0.7;
|
|
279
259
|
margin-right: 4px;
|
|
280
260
|
}
|
|
@@ -356,7 +336,7 @@ const ObjectLayerEngineViewer = {
|
|
|
356
336
|
font-size: 16px;
|
|
357
337
|
}
|
|
358
338
|
|
|
359
|
-
.
|
|
339
|
+
.default-viewer-btn {
|
|
360
340
|
width: 100%;
|
|
361
341
|
padding: 15px;
|
|
362
342
|
background: ${darkTheme ? '#4caf50' : '#4CAF50'};
|
|
@@ -373,7 +353,7 @@ const ObjectLayerEngineViewer = {
|
|
|
373
353
|
gap: 10px;
|
|
374
354
|
}
|
|
375
355
|
|
|
376
|
-
.
|
|
356
|
+
.default-viewer-btn:hover {
|
|
377
357
|
background: ${darkTheme ? '#45a049' : '#45a049'};
|
|
378
358
|
transform: translateY(-2px);
|
|
379
359
|
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
|
|
@@ -392,35 +372,43 @@ const ObjectLayerEngineViewer = {
|
|
|
392
372
|
margin-left: 4px;
|
|
393
373
|
}
|
|
394
374
|
|
|
395
|
-
.
|
|
375
|
+
.default-viewer-btn:disabled {
|
|
396
376
|
background: ${darkTheme ? '#555' : '#ccc'};
|
|
397
377
|
cursor: not-allowed;
|
|
398
378
|
transform: none;
|
|
399
379
|
}
|
|
400
380
|
|
|
381
|
+
.edit-btn {
|
|
382
|
+
background: ${darkTheme ? '#4a9eff' : '#2196F3'};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.edit-btn:hover {
|
|
386
|
+
background: ${darkTheme ? '#3a8eff' : '#1186f2'};
|
|
387
|
+
}
|
|
388
|
+
|
|
401
389
|
@media (max-width: 768px) {
|
|
402
|
-
.
|
|
390
|
+
.webp-display-area {
|
|
403
391
|
max-height: 500px;
|
|
404
392
|
min-height: 300px;
|
|
405
393
|
padding: 20px;
|
|
406
394
|
}
|
|
407
395
|
|
|
408
|
-
.
|
|
409
|
-
.
|
|
396
|
+
.webp-canvas-container canvas,
|
|
397
|
+
.webp-canvas-container img {
|
|
410
398
|
max-width: 100%;
|
|
411
399
|
max-height: 440px;
|
|
412
400
|
}
|
|
413
401
|
}
|
|
414
402
|
|
|
415
403
|
@media (max-width: 600px) {
|
|
416
|
-
.
|
|
404
|
+
.webp-display-area {
|
|
417
405
|
max-height: 400px;
|
|
418
406
|
min-height: 250px;
|
|
419
407
|
padding: 15px;
|
|
420
408
|
}
|
|
421
409
|
|
|
422
|
-
.
|
|
423
|
-
.
|
|
410
|
+
.webp-canvas-container canvas,
|
|
411
|
+
.webp-canvas-container img {
|
|
424
412
|
max-height: 340px;
|
|
425
413
|
}
|
|
426
414
|
|
|
@@ -440,7 +428,8 @@ const ObjectLayerEngineViewer = {
|
|
|
440
428
|
.item-data-value-label {
|
|
441
429
|
font-size: 20px;
|
|
442
430
|
font-weight: 700;
|
|
443
|
-
color: ${darkTheme ? '#
|
|
431
|
+
color: ${darkTheme ? '#aaa' : '#666'};
|
|
432
|
+
text-align: center;
|
|
444
433
|
}
|
|
445
434
|
.item-stat-entry {
|
|
446
435
|
display: flex;
|
|
@@ -457,6 +446,12 @@ const ObjectLayerEngineViewer = {
|
|
|
457
446
|
color: ${darkTheme ? '#666' : '#999'};
|
|
458
447
|
padding: 20px;
|
|
459
448
|
}
|
|
449
|
+
|
|
450
|
+
@media (max-width: 850px) {
|
|
451
|
+
.object-layer-viewer-container {
|
|
452
|
+
padding: 5px;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
460
455
|
</style>`,
|
|
461
456
|
);
|
|
462
457
|
};
|
|
@@ -493,16 +488,42 @@ const ObjectLayerEngineViewer = {
|
|
|
493
488
|
</div>
|
|
494
489
|
</div>
|
|
495
490
|
|
|
496
|
-
|
|
497
|
-
|
|
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">
|
|
498
519
|
<div style="text-align: center; color: ${darkTheme ? '#aaa' : '#666'};">
|
|
499
520
|
<i class="fa-solid fa-image" style="font-size: 48px; opacity: 0.3; margin-bottom: 16px;"></i>
|
|
500
|
-
<p style="margin: 0; font-size: 14px;">
|
|
521
|
+
<p style="margin: 0; font-size: 14px;">WebP preview will appear here</p>
|
|
501
522
|
</div>
|
|
502
|
-
<div id="
|
|
523
|
+
<div id="webp-loading-overlay" class="loading-overlay" style="display: none;">
|
|
503
524
|
<div>
|
|
504
525
|
<i class="fa-solid fa-spinner fa-spin"></i>
|
|
505
|
-
<span style="margin-left: 10px;">Generating
|
|
526
|
+
<span style="margin-left: 10px;">Generating WebP...</span>
|
|
506
527
|
</div>
|
|
507
528
|
</div>
|
|
508
529
|
</div>
|
|
@@ -587,39 +608,30 @@ const ObjectLayerEngineViewer = {
|
|
|
587
608
|
</div>
|
|
588
609
|
</div>
|
|
589
610
|
</div>
|
|
590
|
-
|
|
591
|
-
<div
|
|
592
|
-
<
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
>
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
`,
|
|
605
|
-
)
|
|
606
|
-
.join('')
|
|
607
|
-
: html`<div class="no-data-container">No stats data available</div>`}
|
|
608
|
-
</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>
|
|
609
625
|
</div>
|
|
610
|
-
<button class="download-btn" id="download-gif-btn">
|
|
611
|
-
<i class="fa-solid fa-download"></i>
|
|
612
|
-
<span>Download GIF</span>
|
|
613
|
-
</button>
|
|
614
626
|
</div>
|
|
615
627
|
`,
|
|
616
628
|
);
|
|
617
629
|
ThemeEvents[id]();
|
|
618
630
|
// Attach event listeners
|
|
619
|
-
this.attachEventListeners();
|
|
631
|
+
this.attachEventListeners({ Elements });
|
|
620
632
|
},
|
|
621
633
|
|
|
622
|
-
attachEventListeners: function () {
|
|
634
|
+
attachEventListeners: function ({ Elements }) {
|
|
623
635
|
// Direction buttons
|
|
624
636
|
const directionButtons = document.querySelectorAll('[data-direction]');
|
|
625
637
|
directionButtons.forEach((btn) => {
|
|
@@ -628,9 +640,9 @@ const ObjectLayerEngineViewer = {
|
|
|
628
640
|
const direction = e.currentTarget.getAttribute('data-direction');
|
|
629
641
|
if (direction !== this.Data.currentDirection) {
|
|
630
642
|
this.Data.currentDirection = direction;
|
|
631
|
-
await this.renderViewer();
|
|
632
|
-
await this.attachEventListeners();
|
|
633
|
-
await this.
|
|
643
|
+
await this.renderViewer({ Elements });
|
|
644
|
+
await this.attachEventListeners({ Elements });
|
|
645
|
+
await this.generateWebp();
|
|
634
646
|
}
|
|
635
647
|
});
|
|
636
648
|
});
|
|
@@ -643,97 +655,41 @@ const ObjectLayerEngineViewer = {
|
|
|
643
655
|
const mode = e.currentTarget.getAttribute('data-mode');
|
|
644
656
|
if (mode !== this.Data.currentMode) {
|
|
645
657
|
this.Data.currentMode = mode;
|
|
646
|
-
await this.renderViewer();
|
|
647
|
-
await this.attachEventListeners();
|
|
648
|
-
await this.
|
|
658
|
+
await this.renderViewer({ Elements });
|
|
659
|
+
await this.attachEventListeners({ Elements });
|
|
660
|
+
await this.generateWebp();
|
|
649
661
|
}
|
|
650
662
|
});
|
|
651
663
|
});
|
|
652
664
|
|
|
653
665
|
// Download button
|
|
654
|
-
const downloadBtn = s('#download-
|
|
666
|
+
const downloadBtn = s('#download-webp-btn');
|
|
655
667
|
if (downloadBtn) {
|
|
656
668
|
downloadBtn.addEventListener('click', () => {
|
|
657
|
-
this.
|
|
669
|
+
this.downloadWebp();
|
|
658
670
|
});
|
|
659
671
|
}
|
|
660
672
|
|
|
661
|
-
//
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
});
|
|
668
|
-
}
|
|
669
|
-
}, 100);
|
|
670
|
-
},
|
|
671
|
-
|
|
672
|
-
selectFirstAvailableDirectionMode: function () {
|
|
673
|
-
const { frameCounts } = this.Data;
|
|
674
|
-
if (!frameCounts) return;
|
|
675
|
-
|
|
676
|
-
// Priority order for directions
|
|
677
|
-
const directions = ['down', 'up', 'left', 'right'];
|
|
678
|
-
// Priority order for modes
|
|
679
|
-
const modes = ['idle', 'walking'];
|
|
680
|
-
|
|
681
|
-
// Try to find first available combination using numeric codes
|
|
682
|
-
for (const mode of modes) {
|
|
683
|
-
for (const direction of directions) {
|
|
684
|
-
const numericCode = this.getDirectionCode(direction, mode);
|
|
685
|
-
if (numericCode && frameCounts[numericCode] && frameCounts[numericCode] > 0) {
|
|
686
|
-
this.Data.currentDirection = direction;
|
|
687
|
-
this.Data.currentMode = mode;
|
|
688
|
-
logger.info(`Auto-selected: ${direction} ${mode} (code: ${numericCode}, ${frameCounts[numericCode]} frames)`);
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
}
|
|
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
|
+
});
|
|
692
681
|
}
|
|
693
682
|
|
|
694
|
-
//
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
try {
|
|
702
|
-
// Load gif.js library
|
|
703
|
-
await this.loadScript('https://cdn.jsdelivr.net/npm/gif.js@0.2.0/dist/gif.min.js');
|
|
704
|
-
|
|
705
|
-
// Fetch worker script
|
|
706
|
-
const response = await fetch('https://cdn.jsdelivr.net/npm/gif.js@0.2.0/dist/gif.worker.js');
|
|
707
|
-
if (!response.ok) {
|
|
708
|
-
throw new Error('Failed to fetch gif.worker.js');
|
|
709
|
-
}
|
|
710
|
-
const workerBlob = await response.blob();
|
|
711
|
-
this.Data.gifWorkerBlob = URL.createObjectURL(workerBlob);
|
|
712
|
-
|
|
713
|
-
logger.info('gif.js initialized successfully');
|
|
714
|
-
} catch (error) {
|
|
715
|
-
logger.error('Error initializing gif.js:', error);
|
|
716
|
-
throw error;
|
|
683
|
+
// Edit button
|
|
684
|
+
const editBtn = s('#edit-object-layer-btn');
|
|
685
|
+
if (editBtn) {
|
|
686
|
+
editBtn.addEventListener('click', () => {
|
|
687
|
+
this.toEngine();
|
|
688
|
+
});
|
|
717
689
|
}
|
|
718
690
|
},
|
|
719
691
|
|
|
720
|
-
|
|
721
|
-
return new Promise((resolve, reject) => {
|
|
722
|
-
// Check if already loaded
|
|
723
|
-
if (document.querySelector(`script[src="${src}"]`)) {
|
|
724
|
-
resolve();
|
|
725
|
-
return;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
const script = document.createElement('script');
|
|
729
|
-
script.src = src;
|
|
730
|
-
script.onload = resolve;
|
|
731
|
-
script.onerror = reject;
|
|
732
|
-
document.head.appendChild(script);
|
|
733
|
-
});
|
|
734
|
-
},
|
|
735
|
-
|
|
736
|
-
generateGif: async function () {
|
|
692
|
+
generateWebp: async function () {
|
|
737
693
|
if (this.Data.isGenerating) return;
|
|
738
694
|
|
|
739
695
|
const { objectLayer, frameCounts, currentDirection, currentMode } = this.Data;
|
|
@@ -766,128 +722,74 @@ const ObjectLayerEngineViewer = {
|
|
|
766
722
|
this.Data.isGenerating = true;
|
|
767
723
|
this.showLoading(true);
|
|
768
724
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
// Update loading message
|
|
777
|
-
const loadingOverlay = s('#gif-loading-overlay');
|
|
778
|
-
if (loadingOverlay) {
|
|
779
|
-
loadingOverlay.querySelector('span').textContent = `Loading frames... (0/${frames.length})`;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
// Load all frames to find maximum dimensions
|
|
783
|
-
const loadedImages = [];
|
|
784
|
-
let maxWidth = 0;
|
|
785
|
-
let maxHeight = 0;
|
|
786
|
-
|
|
787
|
-
for (let i = 0; i < frames.length; i++) {
|
|
788
|
-
const img = await this.loadImage(frames[i]);
|
|
789
|
-
loadedImages.push(img);
|
|
790
|
-
maxWidth = Math.max(maxWidth, img.naturalWidth);
|
|
791
|
-
maxHeight = Math.max(maxHeight, img.naturalHeight);
|
|
792
|
-
|
|
793
|
-
// Update progress
|
|
794
|
-
if (loadingOverlay && (i === 0 || i % 5 === 0)) {
|
|
795
|
-
loadingOverlay.querySelector('span').textContent = `Loading frames... (${i + 1}/${frames.length})`;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// Update loading message for GIF generation
|
|
800
|
-
if (loadingOverlay) {
|
|
801
|
-
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}...`;
|
|
802
731
|
}
|
|
732
|
+
}
|
|
803
733
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
// Create new GIF instance with binary transparency
|
|
811
|
-
const gif = new GIF({
|
|
812
|
-
workers: 2,
|
|
813
|
-
workerScript: this.Data.gifWorkerBlob,
|
|
814
|
-
quality: 10,
|
|
815
|
-
width: maxWidth,
|
|
816
|
-
height: maxHeight,
|
|
817
|
-
transparent: transparentColorHex, // Use magenta as transparent color
|
|
818
|
-
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,
|
|
819
740
|
});
|
|
820
741
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
//
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
data[p + 1] = placeholder.g; // G
|
|
856
|
-
data[p + 2] = placeholder.b; // B
|
|
857
|
-
data[p + 3] = 255; // A (fully opaque)
|
|
858
|
-
}
|
|
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);
|
|
859
776
|
}
|
|
860
|
-
|
|
861
|
-
ctx.putImageData(imageData, 0, 0);
|
|
862
|
-
} catch (err) {
|
|
863
|
-
logger.warn(
|
|
864
|
-
'Could not access image data for transparency threshold (CORS issue). Transparency may not work correctly.',
|
|
865
|
-
err,
|
|
866
|
-
);
|
|
867
777
|
}
|
|
868
778
|
|
|
869
|
-
//
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
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');
|
|
875
785
|
}
|
|
876
786
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
this.displayGif(blob, maxWidth, maxHeight, frameDuration, frameCount);
|
|
880
|
-
this.Data.gif = blob;
|
|
881
|
-
this.Data.isGenerating = false;
|
|
882
|
-
this.showLoading(false);
|
|
883
|
-
});
|
|
884
|
-
|
|
885
|
-
// Render the GIF
|
|
886
|
-
gif.render();
|
|
787
|
+
this.Data.isGenerating = false;
|
|
788
|
+
this.showLoading(false);
|
|
887
789
|
} catch (error) {
|
|
888
|
-
logger.error('Error generating
|
|
790
|
+
logger.error('Error generating WebP:', error);
|
|
889
791
|
NotificationManager.Push({
|
|
890
|
-
html: `Failed to generate
|
|
792
|
+
html: `Failed to generate WebP: ${error.message}`,
|
|
891
793
|
status: 'error',
|
|
892
794
|
});
|
|
893
795
|
this.Data.isGenerating = false;
|
|
@@ -895,133 +797,35 @@ const ObjectLayerEngineViewer = {
|
|
|
895
797
|
}
|
|
896
798
|
},
|
|
897
799
|
|
|
898
|
-
loadImage: function (src) {
|
|
899
|
-
return new Promise((resolve, reject) => {
|
|
900
|
-
const img = new Image();
|
|
901
|
-
img.crossOrigin = 'anonymous';
|
|
902
|
-
img.onload = () => resolve(img);
|
|
903
|
-
img.onerror = reject;
|
|
904
|
-
img.src = src;
|
|
905
|
-
});
|
|
906
|
-
},
|
|
907
|
-
|
|
908
|
-
displayGif: function (blob, originalWidth, originalHeight, frameDuration, frameCount) {
|
|
909
|
-
const container = s('#gif-canvas-container');
|
|
910
|
-
if (!container) return;
|
|
911
|
-
|
|
912
|
-
const url = URL.createObjectURL(blob);
|
|
913
|
-
|
|
914
|
-
// Create img element for the animated GIF
|
|
915
|
-
const gifImg = document.createElement('img');
|
|
916
|
-
gifImg.src = url;
|
|
917
|
-
|
|
918
|
-
gifImg.onload = () => {
|
|
919
|
-
// Use provided dimensions or get from image
|
|
920
|
-
const naturalWidth = originalWidth || gifImg.naturalWidth;
|
|
921
|
-
const naturalHeight = originalHeight || gifImg.naturalHeight;
|
|
922
|
-
|
|
923
|
-
// Calculate intelligent scaling based on container and image size
|
|
924
|
-
const containerEl = s('.gif-display-area');
|
|
925
|
-
const containerWidth = containerEl ? containerEl.clientWidth - 60 : 400; // subtract padding
|
|
926
|
-
const containerHeight = containerEl ? containerEl.clientHeight - 60 : 400;
|
|
927
|
-
|
|
928
|
-
// Calculate scale to fit container while maintaining aspect ratio
|
|
929
|
-
const scaleToFitWidth = containerWidth / naturalWidth;
|
|
930
|
-
const scaleToFitHeight = containerHeight / naturalHeight;
|
|
931
|
-
const scaleToFit = Math.min(scaleToFitWidth, scaleToFitHeight);
|
|
932
|
-
|
|
933
|
-
// For pixel art, use integer scaling for better visuals
|
|
934
|
-
// Minimum 2x for small sprites, but respect container size
|
|
935
|
-
let scale = Math.max(1, Math.floor(scaleToFit));
|
|
936
|
-
|
|
937
|
-
// For very small sprites (< 100px), try to scale up more
|
|
938
|
-
if (Math.max(naturalWidth, naturalHeight) < 100) {
|
|
939
|
-
scale = Math.min(4, Math.floor(scaleToFit));
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
// Make sure scaled image fits in container
|
|
943
|
-
const displayWidth = naturalWidth * scale;
|
|
944
|
-
const displayHeight = naturalHeight * scale;
|
|
945
|
-
|
|
946
|
-
if (displayWidth > containerWidth || displayHeight > containerHeight) {
|
|
947
|
-
scale = Math.max(1, scale - 1);
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
gifImg.style.width = `${naturalWidth * scale}px !important`;
|
|
951
|
-
gifImg.style.height = `${naturalHeight * scale}px !important`;
|
|
952
|
-
gifImg.style.maxWidth = '100%';
|
|
953
|
-
gifImg.style.maxHeight = '540px';
|
|
954
|
-
|
|
955
|
-
// Force pixel-perfect rendering (no antialiasing/blur)
|
|
956
|
-
// gifImg.style.imageRendering = 'pixelated';
|
|
957
|
-
// gifImg.style.imageRendering = '-moz-crisp-edges';
|
|
958
|
-
// gifImg.style.imageRendering = 'crisp-edges';
|
|
959
|
-
// gifImg.style.msInterpolationMode = 'nearest-neighbor';
|
|
960
|
-
|
|
961
|
-
// Prevent any browser scaling optimizations
|
|
962
|
-
// gifImg.style.transform = 'translateZ(0)'; // Force GPU rendering
|
|
963
|
-
// gifImg.style.backfaceVisibility = 'hidden'; // Prevent subpixel rendering
|
|
964
|
-
|
|
965
|
-
// Clear container and add the GIF
|
|
966
|
-
container.innerHTML = '';
|
|
967
|
-
container.appendChild(gifImg);
|
|
968
|
-
|
|
969
|
-
// Re-add loading overlay
|
|
970
|
-
const overlay = document.createElement('div');
|
|
971
|
-
overlay.id = 'gif-loading-overlay';
|
|
972
|
-
overlay.className = 'loading-overlay';
|
|
973
|
-
overlay.style.display = 'none';
|
|
974
|
-
overlay.innerHTML = html`
|
|
975
|
-
<div>
|
|
976
|
-
<i class="fa-solid fa-spinner fa-spin"></i>
|
|
977
|
-
<span style="margin-left: 10px;">Generating GIF...</span>
|
|
978
|
-
</div>
|
|
979
|
-
`;
|
|
980
|
-
container.appendChild(overlay);
|
|
981
|
-
|
|
982
|
-
// Add info badge with dimensions and scale
|
|
983
|
-
const infoBadge = document.createElement('div');
|
|
984
|
-
infoBadge.className = 'gif-info-badge';
|
|
985
|
-
const displayW = Math.round(naturalWidth * scale);
|
|
986
|
-
const displayH = Math.round(naturalHeight * scale);
|
|
987
|
-
infoBadge.innerHTML = html`
|
|
988
|
-
<span class="info-label">Dimensions:</span> ${naturalWidth}x${naturalHeight}px<br />
|
|
989
|
-
<span class="info-label">Display:</span> ${displayW}x${displayH}px<br />
|
|
990
|
-
${scale > 1 ? `<span class="info-label">Scale:</span> ${scale}x<br />` : ''}
|
|
991
|
-
<span class="info-label">Frames:</span> ${frameCount}<br />
|
|
992
|
-
<span class="info-label">Frame Duration:</span> ${frameDuration}ms<br />
|
|
993
|
-
<span class="info-label">Total Duration:</span> ${(frameDuration * frameCount) / 1000}s
|
|
994
|
-
`;
|
|
995
|
-
s(`.gif-display-area`).appendChild(infoBadge);
|
|
996
|
-
|
|
997
|
-
logger.info(`Displaying GIF: ${naturalWidth}x${naturalHeight} at ${scale}x scale (${displayW}x${displayH})`);
|
|
998
|
-
};
|
|
999
|
-
|
|
1000
|
-
gifImg.onerror = () => {
|
|
1001
|
-
logger.error('Failed to load GIF image');
|
|
1002
|
-
NotificationManager.Push({
|
|
1003
|
-
html: 'Failed to display GIF',
|
|
1004
|
-
status: 'error',
|
|
1005
|
-
});
|
|
1006
|
-
};
|
|
1007
|
-
},
|
|
1008
|
-
|
|
1009
800
|
showLoading: function (show) {
|
|
1010
|
-
const overlay = s('#
|
|
801
|
+
const overlay = s('#webp-loading-overlay');
|
|
1011
802
|
if (overlay) {
|
|
1012
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
|
+
}
|
|
1013
811
|
}
|
|
1014
812
|
|
|
1015
|
-
const downloadBtn = s('#download-
|
|
813
|
+
const downloadBtn = s('#download-webp-btn');
|
|
1016
814
|
if (downloadBtn) {
|
|
1017
815
|
downloadBtn.disabled = show;
|
|
1018
816
|
}
|
|
817
|
+
|
|
818
|
+
// Remove old info badge if exists
|
|
819
|
+
const oldBadge = s('.webp-info-badge');
|
|
820
|
+
if (oldBadge && show) {
|
|
821
|
+
oldBadge.remove();
|
|
822
|
+
}
|
|
1019
823
|
},
|
|
1020
824
|
|
|
1021
|
-
|
|
1022
|
-
if (!this.Data.
|
|
825
|
+
downloadWebp: function () {
|
|
826
|
+
if (!this.Data.webp) {
|
|
1023
827
|
NotificationManager.Push({
|
|
1024
|
-
html: 'No
|
|
828
|
+
html: 'No WebP available to download',
|
|
1025
829
|
status: 'warning',
|
|
1026
830
|
});
|
|
1027
831
|
return;
|
|
@@ -1029,31 +833,46 @@ const ObjectLayerEngineViewer = {
|
|
|
1029
833
|
|
|
1030
834
|
const { objectLayer, currentDirection, currentMode } = this.Data;
|
|
1031
835
|
const numericCode = this.getDirectionCode(currentDirection, currentMode);
|
|
1032
|
-
const filename = `${objectLayer.data.item.id}_${currentDirection}_${currentMode}_${numericCode}.
|
|
836
|
+
const filename = `${objectLayer.data.item.id}_${currentDirection}_${currentMode}_${numericCode}.webp`;
|
|
1033
837
|
|
|
1034
|
-
|
|
838
|
+
// Create a temporary anchor element to trigger download
|
|
1035
839
|
const a = document.createElement('a');
|
|
1036
|
-
a.href =
|
|
840
|
+
a.href = this.Data.webp;
|
|
1037
841
|
a.download = filename;
|
|
1038
842
|
document.body.appendChild(a);
|
|
1039
843
|
a.click();
|
|
1040
844
|
document.body.removeChild(a);
|
|
1041
|
-
URL.revokeObjectURL(url);
|
|
1042
845
|
|
|
1043
846
|
NotificationManager.Push({
|
|
1044
|
-
html: `
|
|
847
|
+
html: `WebP downloaded: ${filename}`,
|
|
1045
848
|
status: 'success',
|
|
1046
849
|
});
|
|
1047
850
|
},
|
|
1048
851
|
|
|
1049
|
-
|
|
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 }) {
|
|
1050
869
|
const queryParams = new URLSearchParams(window.location.search);
|
|
1051
870
|
const cid = queryParams.get('cid');
|
|
1052
871
|
|
|
1053
872
|
if (cid) {
|
|
1054
|
-
await this.loadObjectLayer(cid);
|
|
873
|
+
await this.loadObjectLayer(cid, Elements);
|
|
1055
874
|
} else {
|
|
1056
|
-
this.renderEmpty();
|
|
875
|
+
this.renderEmpty({ Elements });
|
|
1057
876
|
}
|
|
1058
877
|
},
|
|
1059
878
|
};
|