@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.
- package/.vscode/settings.json +7 -8
- package/README.md +2 -2
- package/bin/build.js +21 -5
- package/bin/deploy.js +10 -0
- package/bin/file.js +2 -1
- package/bin/util.js +0 -17
- package/cli.md +2 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +2 -4
- package/scripts/rocky-setup.sh +12 -39
- package/src/api/document/document.service.js +1 -1
- package/src/cli/cluster.js +5 -9
- package/src/cli/repository.js +9 -9
- package/src/cli/run.js +108 -106
- package/src/client/components/core/Content.js +54 -4
- package/src/client/components/core/FullScreen.js +202 -9
- package/src/client/components/core/Panel.js +91 -22
- package/src/client/components/core/PanelForm.js +5 -2
- package/src/client/components/core/Translate.js +8 -0
- package/src/client/components/core/VanillaJs.js +80 -29
- package/src/client/services/default/default.management.js +324 -136
- package/src/index.js +58 -20
- package/src/client/components/core/ObjectLayerEngine.js +0 -1520
- package/src/client/components/core/ObjectLayerEngineModal.js +0 -1245
- package/src/client/components/core/ObjectLayerEngineViewer.js +0 -880
- package/src/server/object-layer.js +0 -335
|
@@ -1,1245 +0,0 @@
|
|
|
1
|
-
import { CoreService } from '../../services/core/core.service.js';
|
|
2
|
-
import { BtnIcon } from './BtnIcon.js';
|
|
3
|
-
import { borderChar, dynamicCol } from './Css.js';
|
|
4
|
-
import { DropDown } from './DropDown.js';
|
|
5
|
-
import { EventsUI } from './EventsUI.js';
|
|
6
|
-
import { Translate } from './Translate.js';
|
|
7
|
-
import { s, append, hexToRgbA } from './VanillaJs.js';
|
|
8
|
-
import { getProxyPath, getQueryParams, setPath, setQueryParams, RouterEvents } from './Router.js';
|
|
9
|
-
import { s4 } from './CommonJs.js';
|
|
10
|
-
import { Input } from './Input.js';
|
|
11
|
-
import { ToggleSwitch } from './ToggleSwitch.js';
|
|
12
|
-
import { ObjectLayerService } from '../../services/object-layer/object-layer.service.js';
|
|
13
|
-
import { NotificationManager } from './NotificationManager.js';
|
|
14
|
-
import { AgGrid } from './AgGrid.js';
|
|
15
|
-
import { Modal } from './Modal.js';
|
|
16
|
-
import { loggerFactory } from './Logger.js';
|
|
17
|
-
import { LoadingAnimation } from './LoadingAnimation.js';
|
|
18
|
-
import { DefaultManagement } from '../../services/default/default.management.js';
|
|
19
|
-
|
|
20
|
-
const logger = loggerFactory(import.meta, { trace: true });
|
|
21
|
-
|
|
22
|
-
const ObjectLayerEngineModal = {
|
|
23
|
-
templates: [
|
|
24
|
-
{
|
|
25
|
-
label: 'empty',
|
|
26
|
-
id: 'empty',
|
|
27
|
-
data: [],
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
statDescriptions: {
|
|
31
|
-
effect: {
|
|
32
|
-
title: 'Effect',
|
|
33
|
-
icon: 'fa-solid fa-burst',
|
|
34
|
-
description: 'Amount of life removed when an entity collides or deals an impact.',
|
|
35
|
-
detail: 'Measured in life points.',
|
|
36
|
-
},
|
|
37
|
-
resistance: {
|
|
38
|
-
title: 'Resistance',
|
|
39
|
-
icon: 'fa-solid fa-shield',
|
|
40
|
-
description: "Adds to the owner's maximum life (survivability cap).",
|
|
41
|
-
detail:
|
|
42
|
-
"This value is summed with the entity's base max life. It also increases the amount of life restored when a regeneration event occurs (adds directly to current life).",
|
|
43
|
-
},
|
|
44
|
-
agility: {
|
|
45
|
-
title: 'Agility',
|
|
46
|
-
icon: 'fa-solid fa-person-running',
|
|
47
|
-
description: 'Increases the movement speed of entities.',
|
|
48
|
-
detail: 'Higher values result in faster movement.',
|
|
49
|
-
},
|
|
50
|
-
range: {
|
|
51
|
-
title: 'Range',
|
|
52
|
-
icon: 'fa-solid fa-bullseye',
|
|
53
|
-
description: 'Increases the lifetime of a cast/summoned entity.',
|
|
54
|
-
detail: 'Measured in milliseconds.',
|
|
55
|
-
},
|
|
56
|
-
intelligence: {
|
|
57
|
-
title: 'Intelligence',
|
|
58
|
-
icon: 'fa-solid fa-brain',
|
|
59
|
-
description: 'Probability-based stat that increases the chance to spawn/trigger a summoned entity.',
|
|
60
|
-
detail: 'Higher values increase summoning success rate.',
|
|
61
|
-
},
|
|
62
|
-
utility: {
|
|
63
|
-
title: 'Utility',
|
|
64
|
-
icon: 'fa-solid fa-wrench',
|
|
65
|
-
description: 'Reduces the cooldown time between actions, allowing for more frequent actions.',
|
|
66
|
-
detail: 'It also increases the chance to trigger life-regeneration events.',
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
|
|
70
|
-
RenderTemplate: (colorTemplate) => {
|
|
71
|
-
const ole = s('object-layer-engine');
|
|
72
|
-
if (!ole) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (colorTemplate.length === 0) {
|
|
77
|
-
ole.clear();
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const matrix = colorTemplate.map((row) => row.map((hex) => [...hexToRgbA(hex), 255]));
|
|
82
|
-
ole.loadMatrix(matrix);
|
|
83
|
-
},
|
|
84
|
-
ObjectLayerData: {},
|
|
85
|
-
clearData: function () {
|
|
86
|
-
// Clear all cached object layer data to prevent contamination between sessions
|
|
87
|
-
this.ObjectLayerData = {};
|
|
88
|
-
|
|
89
|
-
// Clear the canvas if it exists
|
|
90
|
-
const ole = s('object-layer-engine');
|
|
91
|
-
if (ole && typeof ole.clear === 'function') {
|
|
92
|
-
ole.clear();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Clear all frame previews from DOM for all direction codes
|
|
96
|
-
const directionCodes = ['08', '18', '02', '12', '04', '14', '06', '16'];
|
|
97
|
-
for (const directionCode of directionCodes) {
|
|
98
|
-
const framesContainer = s(`.frames-${directionCode}`);
|
|
99
|
-
if (framesContainer) {
|
|
100
|
-
framesContainer.innerHTML = '';
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Clear form inputs with correct IDs
|
|
105
|
-
const itemIdInput = s('#ol-input-item-id');
|
|
106
|
-
if (itemIdInput) itemIdInput.value = '';
|
|
107
|
-
|
|
108
|
-
const itemDescInput = s('#ol-input-item-description');
|
|
109
|
-
if (itemDescInput) itemDescInput.value = '';
|
|
110
|
-
|
|
111
|
-
const frameDurationInput = s('#ol-input-render-frame-duration');
|
|
112
|
-
if (frameDurationInput) frameDurationInput.value = '100';
|
|
113
|
-
|
|
114
|
-
// Reset toggle switches with correct IDs
|
|
115
|
-
const activableCheckbox = s('#ol-toggle-item-activable');
|
|
116
|
-
if (activableCheckbox) activableCheckbox.checked = false;
|
|
117
|
-
|
|
118
|
-
const statelessCheckbox = s('#ol-toggle-render-is-stateless');
|
|
119
|
-
if (statelessCheckbox) statelessCheckbox.checked = false;
|
|
120
|
-
|
|
121
|
-
// Clear stat inputs with correct IDs
|
|
122
|
-
const statTypes = Object.keys(ObjectLayerEngineModal.statDescriptions);
|
|
123
|
-
for (const stat of statTypes) {
|
|
124
|
-
const statInput = s(`#ol-input-item-stats-${stat}`);
|
|
125
|
-
if (statInput) statInput.value = '0';
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
loadFromDatabase: async (objectLayerId) => {
|
|
129
|
-
try {
|
|
130
|
-
// Load metadata first (lightweight)
|
|
131
|
-
const { status: metaStatus, data: metadata } = await ObjectLayerService.getMetadata({ id: objectLayerId });
|
|
132
|
-
|
|
133
|
-
if (metaStatus !== 'success' || !metadata) {
|
|
134
|
-
NotificationManager.Push({
|
|
135
|
-
html: `Failed to load object layer metadata`,
|
|
136
|
-
status: 'error',
|
|
137
|
-
});
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Load render data separately (heavy)
|
|
142
|
-
const { status: renderStatus, data: renderData } = await ObjectLayerService.getRender({ id: objectLayerId });
|
|
143
|
-
|
|
144
|
-
if (renderStatus !== 'success' || !renderData) {
|
|
145
|
-
NotificationManager.Push({
|
|
146
|
-
html: `Failed to load object layer render data`,
|
|
147
|
-
status: 'error',
|
|
148
|
-
});
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return { metadata, renderData };
|
|
153
|
-
} catch (error) {
|
|
154
|
-
console.error('Error loading object layer from database:', error);
|
|
155
|
-
NotificationManager.Push({
|
|
156
|
-
html: `Error loading object layer: ${error.message}`,
|
|
157
|
-
status: 'error',
|
|
158
|
-
});
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
Render: async (options = { idModal: '', Elements: {} }) => {
|
|
163
|
-
// Clear all cached data at the start of each render to prevent contamination
|
|
164
|
-
ObjectLayerEngineModal.clearData();
|
|
165
|
-
|
|
166
|
-
const { Elements } = options;
|
|
167
|
-
await import(`${getProxyPath()}components/core/ObjectLayerEngine.js`);
|
|
168
|
-
// await import(`${getProxyPath()}components/core/WebComponent.js`);
|
|
169
|
-
const directionCodes = ['08', '18', '02', '12', '04', '14', '06', '16'];
|
|
170
|
-
const itemTypes = ['skin', 'weapon', 'armor', 'artifact', 'floor'];
|
|
171
|
-
const statTypes = ['effect', 'resistance', 'agility', 'range', 'intelligence', 'utility'];
|
|
172
|
-
let selectItemType = itemTypes[0];
|
|
173
|
-
let itemActivable = false;
|
|
174
|
-
let renderIsStateless = false;
|
|
175
|
-
let renderFrameDuration = 100;
|
|
176
|
-
|
|
177
|
-
// Check if we have a 'cid' query parameter to load existing object layer
|
|
178
|
-
const queryParams = getQueryParams();
|
|
179
|
-
let loadedData = null;
|
|
180
|
-
let existingObjectLayerId = null; // Track the _id for updates
|
|
181
|
-
let originalDirectionCodes = []; // Track original direction codes for update mode
|
|
182
|
-
|
|
183
|
-
// Track frame editing state
|
|
184
|
-
let editingFrameId = null;
|
|
185
|
-
let editingDirectionCode = null;
|
|
186
|
-
|
|
187
|
-
// Helper function to update UI when entering edit mode
|
|
188
|
-
const enterEditMode = (frameId, directionCode) => {
|
|
189
|
-
editingFrameId = frameId;
|
|
190
|
-
editingDirectionCode = directionCode;
|
|
191
|
-
|
|
192
|
-
// Hide/disable all "Add Frame" buttons except the one for the editing direction
|
|
193
|
-
directionCodes.forEach((code) => {
|
|
194
|
-
const addButton = s(`.direction-code-bar-frames-btn-${code}`);
|
|
195
|
-
const addButtonParent = addButton?.parentElement;
|
|
196
|
-
if (addButton && addButtonParent) {
|
|
197
|
-
if (code === directionCode) {
|
|
198
|
-
// Keep the button for the editing direction visible and highlighted with animation
|
|
199
|
-
addButton.style.opacity = '1';
|
|
200
|
-
addButton.style.pointerEvents = 'auto';
|
|
201
|
-
addButton.style.border = '2px solid #4CAF50';
|
|
202
|
-
addButton.style.boxShadow = '0 0 15px rgba(76, 175, 80, 0.6)';
|
|
203
|
-
addButtonParent.classList.add('edit-mode-active');
|
|
204
|
-
} else {
|
|
205
|
-
// Hide/disable buttons for other directions
|
|
206
|
-
addButton.style.opacity = '0.3';
|
|
207
|
-
addButton.style.pointerEvents = 'none';
|
|
208
|
-
addButton.style.filter = 'grayscale(100%)';
|
|
209
|
-
addButton.style.cursor = 'not-allowed';
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// Change the edit button to close button for the frame being edited
|
|
215
|
-
const editBtn = s(`.direction-code-bar-edit-btn-${frameId}`);
|
|
216
|
-
if (editBtn) {
|
|
217
|
-
editBtn.innerHTML = '<i class="fa-solid fa-times"></i>';
|
|
218
|
-
editBtn.classList.add('edit-mode-active');
|
|
219
|
-
editBtn.title = 'Cancel editing (Esc)';
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Highlight the frame being edited
|
|
223
|
-
document.querySelectorAll('.direction-code-bar-frames-img').forEach((img) => {
|
|
224
|
-
img.style.border = '';
|
|
225
|
-
img.style.boxShadow = '';
|
|
226
|
-
});
|
|
227
|
-
const frameImg = s(`.direction-code-bar-frames-img-${frameId}`);
|
|
228
|
-
if (frameImg) {
|
|
229
|
-
frameImg.style.border = '3px solid #4CAF50';
|
|
230
|
-
frameImg.style.boxShadow = '0 0 10px rgba(76, 175, 80, 0.5)';
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Show notification with better instructions
|
|
234
|
-
NotificationManager.Push({
|
|
235
|
-
html: `<strong>Edit Mode Active</strong><br/>Click the glowing <i class="fa-solid fa-edit"></i> button for direction <strong>${directionCode}</strong> to save, or click <i class="fa-solid fa-times"></i> to cancel.`,
|
|
236
|
-
status: 'info',
|
|
237
|
-
});
|
|
238
|
-
s(`.direction-code-bar-frames-btn-icon-add-${directionCode}`).classList.add('hide');
|
|
239
|
-
s(`.direction-code-bar-frames-btn-icon-edit-${directionCode}`).classList.remove('hide');
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
// Helper function to exit edit mode and restore UI
|
|
243
|
-
const exitEditMode = () => {
|
|
244
|
-
const previousEditingFrameId = editingFrameId;
|
|
245
|
-
|
|
246
|
-
s(`.direction-code-bar-frames-btn-icon-add-${editingDirectionCode}`).classList.remove('hide');
|
|
247
|
-
s(`.direction-code-bar-frames-btn-icon-edit-${editingDirectionCode}`).classList.add('hide');
|
|
248
|
-
|
|
249
|
-
editingFrameId = null;
|
|
250
|
-
editingDirectionCode = null;
|
|
251
|
-
|
|
252
|
-
// Restore all "Add Frame" buttons
|
|
253
|
-
directionCodes.forEach((code) => {
|
|
254
|
-
const addButton = s(`.direction-code-bar-frames-btn-${code}`);
|
|
255
|
-
const addButtonParent = addButton?.parentElement;
|
|
256
|
-
if (addButton) {
|
|
257
|
-
addButton.style.opacity = '1';
|
|
258
|
-
addButton.style.pointerEvents = 'auto';
|
|
259
|
-
addButton.style.border = '';
|
|
260
|
-
addButton.style.filter = 'none';
|
|
261
|
-
addButton.style.cursor = 'pointer';
|
|
262
|
-
addButton.style.boxShadow = '';
|
|
263
|
-
if (addButtonParent) {
|
|
264
|
-
addButtonParent.classList.remove('edit-mode-active');
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
// Restore edit button icon if it exists
|
|
270
|
-
if (previousEditingFrameId) {
|
|
271
|
-
const editBtn = s(`.direction-code-bar-edit-btn-${previousEditingFrameId}`);
|
|
272
|
-
if (editBtn) {
|
|
273
|
-
editBtn.innerHTML = '<i class="fa-solid fa-edit"></i>';
|
|
274
|
-
editBtn.classList.remove('edit-mode-active');
|
|
275
|
-
editBtn.title = 'Edit frame';
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Remove all frame borders and shadows
|
|
280
|
-
document.querySelectorAll('.direction-code-bar-frames-img').forEach((img) => {
|
|
281
|
-
img.style.border = '';
|
|
282
|
-
img.style.boxShadow = '';
|
|
283
|
-
});
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
if (queryParams.cid) {
|
|
287
|
-
existingObjectLayerId = queryParams.cid;
|
|
288
|
-
loadedData = await ObjectLayerEngineModal.loadFromDatabase(queryParams.cid);
|
|
289
|
-
|
|
290
|
-
if (loadedData) {
|
|
291
|
-
const { metadata, renderData } = loadedData;
|
|
292
|
-
|
|
293
|
-
// Set form values from metadata
|
|
294
|
-
if (metadata.data) {
|
|
295
|
-
if (metadata.data.item) {
|
|
296
|
-
selectItemType = metadata.data.item.type || itemTypes[0];
|
|
297
|
-
itemActivable = metadata.data.item.activable || false;
|
|
298
|
-
|
|
299
|
-
// Add loaded item type to itemTypes array if it doesn't exist
|
|
300
|
-
if (selectItemType && !itemTypes.includes(selectItemType)) {
|
|
301
|
-
itemTypes.push(selectItemType);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
if (metadata.data.render) {
|
|
305
|
-
renderIsStateless = metadata.data.render.is_stateless || false;
|
|
306
|
-
renderFrameDuration = metadata.data.render.frame_duration || 100;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
for (const url of [
|
|
313
|
-
`${getProxyPath()}assets/templates/item-skin-08.json`,
|
|
314
|
-
`${getProxyPath()}assets/templates/item-skin-06.json`,
|
|
315
|
-
]) {
|
|
316
|
-
const id = url.split('/').pop().replace('.json', '');
|
|
317
|
-
ObjectLayerEngineModal.templates.push({
|
|
318
|
-
label: id,
|
|
319
|
-
id,
|
|
320
|
-
data: JSON.parse(await CoreService.getRaw({ url })).color,
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const cells = 26;
|
|
325
|
-
const pixelSize = parseInt(320 / cells);
|
|
326
|
-
const idSectionA = 'template-section-a';
|
|
327
|
-
const idSectionB = 'template-section-b';
|
|
328
|
-
|
|
329
|
-
let directionsCodeBarRender = '';
|
|
330
|
-
|
|
331
|
-
// Helper function to add a frame to the direction bar
|
|
332
|
-
const addFrameToBar = async (directionCode, id, image, json) => {
|
|
333
|
-
// Capture directionCode in a local variable to ensure proper closure
|
|
334
|
-
const capturedDirectionCode = directionCode;
|
|
335
|
-
|
|
336
|
-
append(
|
|
337
|
-
`.frames-${capturedDirectionCode}`,
|
|
338
|
-
html`
|
|
339
|
-
<div class="in fll ${id}">
|
|
340
|
-
<img
|
|
341
|
-
class="in fll direction-code-bar-frames-img direction-code-bar-frames-img-${id}"
|
|
342
|
-
src="${URL.createObjectURL(image)}"
|
|
343
|
-
data-direction-code="${capturedDirectionCode}"
|
|
344
|
-
/>
|
|
345
|
-
${await BtnIcon.Render({
|
|
346
|
-
label: html`<i class="fa-solid fa-edit"></i>`,
|
|
347
|
-
class: `abs direction-code-bar-edit-btn direction-code-bar-edit-btn-${id}`,
|
|
348
|
-
})}
|
|
349
|
-
${await BtnIcon.Render({
|
|
350
|
-
label: html`<i class="fa-solid fa-trash"></i>`,
|
|
351
|
-
class: `abs direction-code-bar-trash-btn direction-code-bar-trash-btn-${id}`,
|
|
352
|
-
})}
|
|
353
|
-
</div>
|
|
354
|
-
`,
|
|
355
|
-
);
|
|
356
|
-
|
|
357
|
-
EventsUI.onClick(`.direction-code-bar-frames-img-${id}`, async (e) => {
|
|
358
|
-
// Get direction code from data attribute to ensure we're using the correct one
|
|
359
|
-
const clickedDirectionCode = e.target.getAttribute('data-direction-code') || capturedDirectionCode;
|
|
360
|
-
console.log(`Clicked frame ${id} from direction code: ${clickedDirectionCode}`);
|
|
361
|
-
const frameData = ObjectLayerEngineModal.ObjectLayerData[clickedDirectionCode]?.find(
|
|
362
|
-
(frame) => frame.id === id,
|
|
363
|
-
);
|
|
364
|
-
if (frameData && frameData.json) {
|
|
365
|
-
// console.log(`Loading frame data for direction code ${clickedDirectionCode}:`, frameData.json);
|
|
366
|
-
s('object-layer-engine').importMatrixJSON(frameData.json);
|
|
367
|
-
} else {
|
|
368
|
-
console.error(`Frame data not found for id ${id} in direction code ${clickedDirectionCode}`);
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
EventsUI.onClick(`.direction-code-bar-trash-btn-${id}`, async () => {
|
|
373
|
-
s(`.${id}`).remove();
|
|
374
|
-
ObjectLayerEngineModal.ObjectLayerData[capturedDirectionCode] = ObjectLayerEngineModal.ObjectLayerData[
|
|
375
|
-
capturedDirectionCode
|
|
376
|
-
].filter((frame) => frame.id !== id);
|
|
377
|
-
|
|
378
|
-
// Clear edit mode if deleting the frame being edited
|
|
379
|
-
if (editingFrameId === id && editingDirectionCode === capturedDirectionCode) {
|
|
380
|
-
exitEditMode();
|
|
381
|
-
}
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
EventsUI.onClick(`.direction-code-bar-edit-btn-${id}`, async () => {
|
|
385
|
-
// Check if this is the frame being edited (close button behavior)
|
|
386
|
-
if (editingFrameId === id && editingDirectionCode === capturedDirectionCode) {
|
|
387
|
-
console.log(`Canceling edit mode for frame ${id}`);
|
|
388
|
-
exitEditMode();
|
|
389
|
-
// Clear the editor
|
|
390
|
-
s('object-layer-engine').clear();
|
|
391
|
-
NotificationManager.Push({
|
|
392
|
-
html: `<i class="fa-solid fa-times-circle"></i> Edit canceled`,
|
|
393
|
-
status: 'info',
|
|
394
|
-
});
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
s(`.direction-code-bar-frames-btn-icon-add-${capturedDirectionCode}`).classList.add('hide');
|
|
399
|
-
s(`.direction-code-bar-frames-btn-icon-edit-${capturedDirectionCode}`).classList.remove('hide');
|
|
400
|
-
|
|
401
|
-
console.log(`Edit button clicked for frame ${id} in direction code ${capturedDirectionCode}`);
|
|
402
|
-
|
|
403
|
-
// If another frame is being edited, exit that edit mode first
|
|
404
|
-
if (editingFrameId && editingFrameId !== id) {
|
|
405
|
-
exitEditMode();
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Find the frame data
|
|
409
|
-
const frameData = ObjectLayerEngineModal.ObjectLayerData[capturedDirectionCode]?.find(
|
|
410
|
-
(frame) => frame.id === id,
|
|
411
|
-
);
|
|
412
|
-
|
|
413
|
-
if (frameData && frameData.json) {
|
|
414
|
-
// Load the frame into the editor
|
|
415
|
-
s('object-layer-engine').importMatrixJSON(frameData.json);
|
|
416
|
-
|
|
417
|
-
// Enter edit mode with UI updates
|
|
418
|
-
enterEditMode(id, capturedDirectionCode);
|
|
419
|
-
|
|
420
|
-
console.log(`Entering edit mode for frame ${id} in direction ${capturedDirectionCode}`);
|
|
421
|
-
} else {
|
|
422
|
-
console.error(`Frame data not found for id ${id} in direction code ${capturedDirectionCode}`);
|
|
423
|
-
NotificationManager.Push({
|
|
424
|
-
html: `<i class="fa-solid fa-exclamation-triangle"></i> Error: Frame data not found`,
|
|
425
|
-
status: 'error',
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
});
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
// Helper function to show loading animation
|
|
432
|
-
const showFrameLoading = () => {
|
|
433
|
-
if (!s(`.frame-editor-container`) || s(`.frame-editor-container`).classList.contains('hide')) return;
|
|
434
|
-
LoadingAnimation.spinner.play(`.frame-editor-container-loading`, 'dual-ring-mini', {
|
|
435
|
-
prepend: html`<span class="inl loading-text">Loading </span><br /><br /> ` + '<div style="color: gray;">',
|
|
436
|
-
append: '</div>',
|
|
437
|
-
});
|
|
438
|
-
s(`.frame-editor-container`).classList.add('hide');
|
|
439
|
-
s(`.frame-editor-container-loading`).classList.remove('hide');
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
// Helper function to hide loading animation
|
|
443
|
-
const hideFrameLoading = () => {
|
|
444
|
-
if (!s(`.frame-editor-container-loading`) || s(`.frame-editor-container-loading`).classList.contains('hide'))
|
|
445
|
-
return;
|
|
446
|
-
LoadingAnimation.spinner.stop(`.frame-editor-container-loading`);
|
|
447
|
-
s(`.frame-editor-container-loading`).classList.add('hide');
|
|
448
|
-
s(`.frame-editor-container`).classList.remove('hide');
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
// Helper function to process and add frame from PNG URL using ObjectLayerPngLoader
|
|
452
|
-
const processAndAddFrameFromPngUrl = async (directionCode, pngUrl) => {
|
|
453
|
-
// Wait for components to be available with retry logic
|
|
454
|
-
let ole = s('object-layer-engine');
|
|
455
|
-
let loader = s('object-layer-png-loader');
|
|
456
|
-
|
|
457
|
-
if (!ole || !loader) {
|
|
458
|
-
console.error('object-layer-engine or object-layer-png-loader component not found after retries');
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
try {
|
|
463
|
-
// Load PNG using the loader component - it will automatically load into the editor
|
|
464
|
-
await loader.loadPngUrl(pngUrl);
|
|
465
|
-
|
|
466
|
-
// Export as blob and JSON from component after loading
|
|
467
|
-
const image = await ole.toBlob();
|
|
468
|
-
const json = ole.exportMatrixJSON();
|
|
469
|
-
const id = `frame-loaded-${s4()}-${s4()}`;
|
|
470
|
-
|
|
471
|
-
// Add to ObjectLayerData
|
|
472
|
-
if (!ObjectLayerEngineModal.ObjectLayerData[directionCode]) {
|
|
473
|
-
ObjectLayerEngineModal.ObjectLayerData[directionCode] = [];
|
|
474
|
-
}
|
|
475
|
-
ObjectLayerEngineModal.ObjectLayerData[directionCode].push({ id, image, json });
|
|
476
|
-
console.log(
|
|
477
|
-
`Stored frame ${id} in direction code ${directionCode}. Total frames:`,
|
|
478
|
-
ObjectLayerEngineModal.ObjectLayerData[directionCode].length,
|
|
479
|
-
);
|
|
480
|
-
|
|
481
|
-
// Add to UI
|
|
482
|
-
await addFrameToBar(directionCode, id, image, json);
|
|
483
|
-
} catch (error) {
|
|
484
|
-
console.error('Error loading frame from PNG URL:', error);
|
|
485
|
-
}
|
|
486
|
-
};
|
|
487
|
-
|
|
488
|
-
for (const directionCode of directionCodes) {
|
|
489
|
-
directionsCodeBarRender += html`
|
|
490
|
-
<div class="in section-mp-border">
|
|
491
|
-
<div class="fl">
|
|
492
|
-
<div class="in fll">
|
|
493
|
-
<div class="in direction-code-bar-frames-title">${directionCode}</div>
|
|
494
|
-
<div class="in direction-code-bar-frames-btn">
|
|
495
|
-
${await BtnIcon.Render({
|
|
496
|
-
label: html`
|
|
497
|
-
<i class="fa-solid fa-plus direction-code-bar-frames-btn-icon-add-${directionCode}"></i>
|
|
498
|
-
<i class="fa-solid fa-edit direction-code-bar-frames-btn-icon-edit-${directionCode} hide"></i>
|
|
499
|
-
`,
|
|
500
|
-
class: `direction-code-bar-frames-btn-add direction-code-bar-frames-btn-${directionCode}`,
|
|
501
|
-
})}
|
|
502
|
-
</div>
|
|
503
|
-
</div>
|
|
504
|
-
<div class="frames-${directionCode}"></div>
|
|
505
|
-
</div>
|
|
506
|
-
</div>
|
|
507
|
-
`;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
let statsInputsRender = '';
|
|
511
|
-
for (const statType of statTypes) {
|
|
512
|
-
const statInfo = ObjectLayerEngineModal.statDescriptions[statType];
|
|
513
|
-
const statValue = loadedData?.metadata?.data?.stats?.[statType] || 0;
|
|
514
|
-
statsInputsRender += html`
|
|
515
|
-
<div class="inl" style="margin-bottom: 10px; position: relative;">
|
|
516
|
-
${await Input.Render({
|
|
517
|
-
id: `ol-input-item-stats-${statType}`,
|
|
518
|
-
label: html`<div
|
|
519
|
-
title="${statInfo.description} ${statInfo.detail}"
|
|
520
|
-
class="inl stat-label-container stat-info-icon"
|
|
521
|
-
style="width: 120px; font-size: 16px; overflow: visible; position: relative;"
|
|
522
|
-
>
|
|
523
|
-
<i class="${statInfo.icon}" style="margin-right: 5px;"></i> ${statInfo.title}
|
|
524
|
-
</div>`,
|
|
525
|
-
containerClass: 'inl',
|
|
526
|
-
type: 'number',
|
|
527
|
-
min: 0,
|
|
528
|
-
max: 10,
|
|
529
|
-
placeholder: true,
|
|
530
|
-
value: statValue,
|
|
531
|
-
})}
|
|
532
|
-
<div class="in stat-description">
|
|
533
|
-
${statInfo.description}<br />
|
|
534
|
-
<span style="color: #888; font-style: italic;">${statInfo.detail}</span>
|
|
535
|
-
</div>
|
|
536
|
-
</div>
|
|
537
|
-
`;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
setTimeout(async () => {
|
|
541
|
-
const loadFrames = async () => {
|
|
542
|
-
showFrameLoading();
|
|
543
|
-
|
|
544
|
-
// Clear all frames and data at the start to prevent duplication from multiple calls
|
|
545
|
-
// This must happen BEFORE any async operations to avoid race conditions
|
|
546
|
-
for (const directionCode of directionCodes) {
|
|
547
|
-
// Clear DOM frames for this direction code
|
|
548
|
-
const framesContainer = s(`.frames-${directionCode}`);
|
|
549
|
-
if (framesContainer) {
|
|
550
|
-
framesContainer.innerHTML = '';
|
|
551
|
-
}
|
|
552
|
-
// Clear data for this direction code
|
|
553
|
-
ObjectLayerEngineModal.ObjectLayerData[directionCode] = [];
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
for (const directionCode of directionCodes) {
|
|
557
|
-
// Use IIFE to properly capture directionCode and handle async operations
|
|
558
|
-
await (async (currentDirectionCode) => {
|
|
559
|
-
// Register frame add button handler after DOM is ready
|
|
560
|
-
// Wait longer to ensure all direction bars are rendered
|
|
561
|
-
|
|
562
|
-
if (loadedData && loadedData.metadata && loadedData.metadata.data && currentDirectionCode) {
|
|
563
|
-
// Show loading animation only once on first direction that has frames
|
|
564
|
-
|
|
565
|
-
const { type, id } = loadedData.metadata.data.item;
|
|
566
|
-
const directions = ObjectLayerEngineModal.getDirectionsFromDirectionCode(currentDirectionCode);
|
|
567
|
-
|
|
568
|
-
console.log(`Loading frames for direction code: ${currentDirectionCode}, directions:`, directions);
|
|
569
|
-
|
|
570
|
-
// Check if frames exist for any direction mapped to this direction code
|
|
571
|
-
const { frames } = loadedData.renderData.data.render;
|
|
572
|
-
for (const direction of directions) {
|
|
573
|
-
if (frames[direction] && frames[direction].length > 0) {
|
|
574
|
-
// Track this direction code as having original data
|
|
575
|
-
if (!originalDirectionCodes.includes(currentDirectionCode)) {
|
|
576
|
-
originalDirectionCodes.push(currentDirectionCode);
|
|
577
|
-
}
|
|
578
|
-
// Load frames from static PNG URLs sequentially to avoid race conditions
|
|
579
|
-
const frameCount = frames[direction].length;
|
|
580
|
-
console.log(`Found ${frameCount} frames for direction: ${direction} (code: ${currentDirectionCode})`);
|
|
581
|
-
for (let frameIndex = 0; frameIndex < frameCount; frameIndex++) {
|
|
582
|
-
const pngUrl = `${getProxyPath()}assets/${type}/${id}/${currentDirectionCode}/${frameIndex}.png`;
|
|
583
|
-
console.log(
|
|
584
|
-
`Loading frame ${frameIndex} for direction code ${currentDirectionCode} from: ${pngUrl}`,
|
|
585
|
-
);
|
|
586
|
-
await processAndAddFrameFromPngUrl(currentDirectionCode, pngUrl);
|
|
587
|
-
}
|
|
588
|
-
console.log(`Completed loading ${frameCount} frames for direction code: ${currentDirectionCode}`);
|
|
589
|
-
// Once we found frames for this direction code, we can break to avoid duplicates
|
|
590
|
-
break;
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
const buttonSelector = `.direction-code-bar-frames-btn-${currentDirectionCode}`;
|
|
596
|
-
console.log(`Registering click handler for: ${buttonSelector}`);
|
|
597
|
-
|
|
598
|
-
EventsUI.onClick(buttonSelector, async () => {
|
|
599
|
-
console.log(`Add frame button clicked for direction: ${currentDirectionCode}`);
|
|
600
|
-
const ole = s('object-layer-engine');
|
|
601
|
-
if (!ole) {
|
|
602
|
-
console.error('object-layer-engine not found');
|
|
603
|
-
return;
|
|
604
|
-
}
|
|
605
|
-
const image = await ole.toBlob();
|
|
606
|
-
const json = ole.exportMatrixJSON();
|
|
607
|
-
|
|
608
|
-
// Check if we're in edit mode
|
|
609
|
-
if (editingFrameId && editingDirectionCode) {
|
|
610
|
-
// Ensure we're clicking the add button for the same direction being edited
|
|
611
|
-
if (currentDirectionCode !== editingDirectionCode) {
|
|
612
|
-
NotificationManager.Push({
|
|
613
|
-
html: `<i class="fa-solid fa-exclamation-circle"></i> Please click the glowing <i class="fa-solid fa-edit"></i> button for direction <strong>${editingDirectionCode}</strong> to save changes, or click <i class="fa-solid fa-times"></i> to cancel.`,
|
|
614
|
-
status: 'warning',
|
|
615
|
-
});
|
|
616
|
-
return; // Don't add a new frame
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// UPDATE existing frame
|
|
620
|
-
console.log(`Updating frame ${editingFrameId} in direction ${editingDirectionCode}`);
|
|
621
|
-
|
|
622
|
-
// Find the frame in the data array
|
|
623
|
-
const frameArray = ObjectLayerEngineModal.ObjectLayerData[editingDirectionCode];
|
|
624
|
-
const frameIndex = frameArray?.findIndex((frame) => frame.id === editingFrameId);
|
|
625
|
-
|
|
626
|
-
if (frameIndex !== undefined && frameIndex >= 0) {
|
|
627
|
-
// Update the frame data while preserving the ID and index
|
|
628
|
-
frameArray[frameIndex] = {
|
|
629
|
-
id: editingFrameId,
|
|
630
|
-
image,
|
|
631
|
-
json,
|
|
632
|
-
};
|
|
633
|
-
|
|
634
|
-
// Update the visual representation
|
|
635
|
-
const imgElement = s(`.direction-code-bar-frames-img-${editingFrameId}`);
|
|
636
|
-
if (imgElement) {
|
|
637
|
-
imgElement.src = URL.createObjectURL(image);
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
console.log(`Frame ${editingFrameId} updated successfully at index ${frameIndex}`);
|
|
641
|
-
NotificationManager.Push({
|
|
642
|
-
html: `<i class="fa-solid fa-check-circle"></i> Frame updated successfully at position ${frameIndex + 1}!`,
|
|
643
|
-
status: 'success',
|
|
644
|
-
});
|
|
645
|
-
} else {
|
|
646
|
-
console.error(`Could not find frame ${editingFrameId} in direction ${editingDirectionCode}`);
|
|
647
|
-
NotificationManager.Push({
|
|
648
|
-
html: `<i class="fa-solid fa-exclamation-triangle"></i> Error: Could not find frame to update`,
|
|
649
|
-
status: 'error',
|
|
650
|
-
});
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
// Exit edit mode and restore UI
|
|
654
|
-
exitEditMode();
|
|
655
|
-
} else {
|
|
656
|
-
// ADD new frame (existing behavior)
|
|
657
|
-
const id = `frame-capture-${s4()}-${s4()}`;
|
|
658
|
-
console.log(`Creating new frame ${id} for direction ${currentDirectionCode}`);
|
|
659
|
-
|
|
660
|
-
if (!ObjectLayerEngineModal.ObjectLayerData[currentDirectionCode])
|
|
661
|
-
ObjectLayerEngineModal.ObjectLayerData[currentDirectionCode] = [];
|
|
662
|
-
ObjectLayerEngineModal.ObjectLayerData[currentDirectionCode].push({ id, image, json });
|
|
663
|
-
console.log(
|
|
664
|
-
`Stored frame ${id} in direction code ${currentDirectionCode}. Total frames:`,
|
|
665
|
-
ObjectLayerEngineModal.ObjectLayerData[currentDirectionCode].length,
|
|
666
|
-
);
|
|
667
|
-
|
|
668
|
-
await addFrameToBar(currentDirectionCode, id, image, json);
|
|
669
|
-
}
|
|
670
|
-
});
|
|
671
|
-
})(directionCode);
|
|
672
|
-
}
|
|
673
|
-
hideFrameLoading();
|
|
674
|
-
};
|
|
675
|
-
RouterEvents[`router-${options.idModal}`] = loadFrames;
|
|
676
|
-
|
|
677
|
-
await loadFrames();
|
|
678
|
-
s('object-layer-engine').clear();
|
|
679
|
-
|
|
680
|
-
EventsUI.onClick(`.ol-btn-save`, async () => {
|
|
681
|
-
// Validate minimum frame_duration 100ms
|
|
682
|
-
const frameDuration = parseInt(s(`.ol-input-render-frame-duration`).value);
|
|
683
|
-
if (!frameDuration || frameDuration < 100) {
|
|
684
|
-
NotificationManager.Push({
|
|
685
|
-
html: 'Frame duration must be at least 100ms',
|
|
686
|
-
status: 'error',
|
|
687
|
-
});
|
|
688
|
-
return;
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// Validate that item.id is not empty
|
|
692
|
-
const itemId = s(`.ol-input-item-id`).value;
|
|
693
|
-
if (!itemId || itemId.trim() === '') {
|
|
694
|
-
NotificationManager.Push({
|
|
695
|
-
html: 'Item ID is required',
|
|
696
|
-
status: 'error',
|
|
697
|
-
});
|
|
698
|
-
return;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
const objectLayer = {
|
|
702
|
-
data: {
|
|
703
|
-
render: {
|
|
704
|
-
frames: {},
|
|
705
|
-
color: [],
|
|
706
|
-
frame_duration: 0,
|
|
707
|
-
is_stateless: false,
|
|
708
|
-
},
|
|
709
|
-
stats: {},
|
|
710
|
-
item: {},
|
|
711
|
-
},
|
|
712
|
-
};
|
|
713
|
-
for (const directionCode of directionCodes) {
|
|
714
|
-
const directions = ObjectLayerEngineModal.getDirectionsFromDirectionCode(directionCode);
|
|
715
|
-
for (const direction of directions) {
|
|
716
|
-
if (!objectLayer.data.render.frames[direction]) objectLayer.data.render.frames[direction] = [];
|
|
717
|
-
|
|
718
|
-
if (!(directionCode in ObjectLayerEngineModal.ObjectLayerData)) {
|
|
719
|
-
console.warn('No set directionCodeBarFrameData for directionCode', directionCode);
|
|
720
|
-
continue;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
for (const frameData of ObjectLayerEngineModal.ObjectLayerData[directionCode]) {
|
|
724
|
-
const { matrix } = JSON.parse(frameData.json);
|
|
725
|
-
const frameIndexColorMatrix = [];
|
|
726
|
-
let indexRow = -1;
|
|
727
|
-
for (const row of matrix) {
|
|
728
|
-
indexRow++;
|
|
729
|
-
frameIndexColorMatrix[indexRow] = [];
|
|
730
|
-
let indexCol = -1;
|
|
731
|
-
for (const value of row) {
|
|
732
|
-
indexCol++;
|
|
733
|
-
let colorIndex = objectLayer.data.render.color.findIndex(
|
|
734
|
-
(color) =>
|
|
735
|
-
color[0] === value[0] && color[1] === value[1] && color[2] === value[2] && color[3] === value[3],
|
|
736
|
-
);
|
|
737
|
-
if (colorIndex === -1) {
|
|
738
|
-
objectLayer.data.render.color.push(value);
|
|
739
|
-
colorIndex = objectLayer.data.render.color.length - 1;
|
|
740
|
-
}
|
|
741
|
-
frameIndexColorMatrix[indexRow][indexCol] = colorIndex;
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
objectLayer.data.render.frames[direction].push(frameIndexColorMatrix);
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
objectLayer.data.render.frame_duration = parseInt(s(`.ol-input-render-frame-duration`).value);
|
|
749
|
-
objectLayer.data.render.is_stateless = renderIsStateless;
|
|
750
|
-
objectLayer.data.stats = {
|
|
751
|
-
effect: parseInt(s(`.ol-input-item-stats-effect`).value),
|
|
752
|
-
resistance: parseInt(s(`.ol-input-item-stats-resistance`).value),
|
|
753
|
-
agility: parseInt(s(`.ol-input-item-stats-agility`).value),
|
|
754
|
-
range: parseInt(s(`.ol-input-item-stats-range`).value),
|
|
755
|
-
intelligence: parseInt(s(`.ol-input-item-stats-intelligence`).value),
|
|
756
|
-
utility: parseInt(s(`.ol-input-item-stats-utility`).value),
|
|
757
|
-
};
|
|
758
|
-
objectLayer.data.item = {
|
|
759
|
-
type: selectItemType,
|
|
760
|
-
activable: itemActivable,
|
|
761
|
-
id: s(`.ol-input-item-id`).value,
|
|
762
|
-
description: s(`.ol-input-item-description`).value,
|
|
763
|
-
};
|
|
764
|
-
|
|
765
|
-
// Add _id if we're updating an existing object layer
|
|
766
|
-
if (existingObjectLayerId) {
|
|
767
|
-
objectLayer._id = existingObjectLayerId;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
console.warn('objectLayer', objectLayer, existingObjectLayerId ? '(UPDATE MODE)' : '(CREATE MODE)');
|
|
771
|
-
|
|
772
|
-
if (Elements.Data.user.main.model.user.role === 'guest') {
|
|
773
|
-
NotificationManager.Push({
|
|
774
|
-
html: 'Guests cannot save object layers. Please log in.',
|
|
775
|
-
status: 'warning',
|
|
776
|
-
});
|
|
777
|
-
return;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
// Upload images
|
|
781
|
-
{
|
|
782
|
-
// Get all direction codes that currently have frames
|
|
783
|
-
const directionCodesToUpload = Object.keys(ObjectLayerEngineModal.ObjectLayerData);
|
|
784
|
-
|
|
785
|
-
// In UPDATE mode, also include original direction codes that may have been cleared
|
|
786
|
-
const allDirectionCodes = existingObjectLayerId
|
|
787
|
-
? [...new Set([...directionCodesToUpload, ...originalDirectionCodes])]
|
|
788
|
-
: directionCodesToUpload;
|
|
789
|
-
|
|
790
|
-
console.warn(
|
|
791
|
-
`Uploading frames for ${allDirectionCodes.length} directions:`,
|
|
792
|
-
allDirectionCodes,
|
|
793
|
-
existingObjectLayerId ? '(UPDATE MODE)' : '(CREATE MODE)',
|
|
794
|
-
);
|
|
795
|
-
|
|
796
|
-
for (const directionCode of allDirectionCodes) {
|
|
797
|
-
const frames = ObjectLayerEngineModal.ObjectLayerData[directionCode] || [];
|
|
798
|
-
console.warn(`Direction ${directionCode}: ${frames.length} frames`);
|
|
799
|
-
|
|
800
|
-
// Create FormData with ALL frames for this direction
|
|
801
|
-
const form = new FormData();
|
|
802
|
-
let frameIndex = -1;
|
|
803
|
-
for (const frame of frames) {
|
|
804
|
-
frameIndex++;
|
|
805
|
-
const pngBlob = frame.image;
|
|
806
|
-
|
|
807
|
-
if (!pngBlob) {
|
|
808
|
-
console.error(`Frame ${frameIndex} in direction ${directionCode} has no image blob!`);
|
|
809
|
-
continue;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
// Append all frames to the same FormData
|
|
813
|
-
form.append(directionCode, pngBlob, `${frameIndex}.png`);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
// Send all frames for this direction in one request (even if empty, to remove frames)
|
|
817
|
-
try {
|
|
818
|
-
if (existingObjectLayerId) {
|
|
819
|
-
// UPDATE: use PUT endpoint with object layer ID
|
|
820
|
-
const { status, data } = await ObjectLayerService.put({
|
|
821
|
-
id: `${existingObjectLayerId}/frame-image/${objectLayer.data.item.type}/${objectLayer.data.item.id}/${directionCode}`,
|
|
822
|
-
body: form,
|
|
823
|
-
headerId: 'file',
|
|
824
|
-
});
|
|
825
|
-
console.warn(`Updated ${frames.length} frames for direction ${directionCode}`);
|
|
826
|
-
} else {
|
|
827
|
-
// CREATE: use POST endpoint (only if frames exist)
|
|
828
|
-
if (frames.length > 0) {
|
|
829
|
-
const { status, data } = await ObjectLayerService.post({
|
|
830
|
-
id: `frame-image/${objectLayer.data.item.type}/${objectLayer.data.item.id}/${directionCode}`,
|
|
831
|
-
body: form,
|
|
832
|
-
headerId: 'file',
|
|
833
|
-
});
|
|
834
|
-
console.warn(`Created ${frames.length} frames for direction ${directionCode}`);
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
} catch (error) {
|
|
838
|
-
console.error(`Error uploading frames for direction ${directionCode}:`, error);
|
|
839
|
-
NotificationManager.Push({
|
|
840
|
-
html: `Error uploading frames for direction ${directionCode}: ${error.message}`,
|
|
841
|
-
status: 'error',
|
|
842
|
-
});
|
|
843
|
-
return;
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
console.warn('All frames uploaded successfully');
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
// Upload metadata
|
|
851
|
-
{
|
|
852
|
-
delete objectLayer.data.render.frames;
|
|
853
|
-
delete objectLayer.data.render.color;
|
|
854
|
-
|
|
855
|
-
let response;
|
|
856
|
-
if (existingObjectLayerId) {
|
|
857
|
-
// UPDATE existing object layer
|
|
858
|
-
console.warn(
|
|
859
|
-
'PUT path:',
|
|
860
|
-
`${existingObjectLayerId}/metadata/${objectLayer.data.item.type}/${objectLayer.data.item.id}`,
|
|
861
|
-
);
|
|
862
|
-
response = await ObjectLayerService.put({
|
|
863
|
-
id: `${existingObjectLayerId}/metadata/${objectLayer.data.item.type}/${objectLayer.data.item.id}`,
|
|
864
|
-
body: objectLayer,
|
|
865
|
-
});
|
|
866
|
-
} else {
|
|
867
|
-
// CREATE new object layer
|
|
868
|
-
response = await ObjectLayerService.post({
|
|
869
|
-
id: `metadata/${objectLayer.data.item.type}/${objectLayer.data.item.id}`,
|
|
870
|
-
body: objectLayer,
|
|
871
|
-
});
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
const { status, data, message } = response;
|
|
875
|
-
|
|
876
|
-
if (status === 'success') {
|
|
877
|
-
NotificationManager.Push({
|
|
878
|
-
html: `Object layer "${objectLayer.data.item.id}" ${existingObjectLayerId ? 'updated' : 'created'} successfully!`,
|
|
879
|
-
status: 'success',
|
|
880
|
-
});
|
|
881
|
-
ObjectLayerEngineModal.toManagement();
|
|
882
|
-
} else {
|
|
883
|
-
NotificationManager.Push({
|
|
884
|
-
html: `Error ${existingObjectLayerId ? 'updating' : 'creating'} object layer: ${message}`,
|
|
885
|
-
status: 'error',
|
|
886
|
-
});
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
});
|
|
890
|
-
|
|
891
|
-
// Add reset button event listener
|
|
892
|
-
EventsUI.onClick(`.ol-btn-reset`, async () => {
|
|
893
|
-
const confirmResult = await Modal.RenderConfirm({
|
|
894
|
-
html: async () => {
|
|
895
|
-
return html`
|
|
896
|
-
<div class="in section-mp" style="text-align: center">
|
|
897
|
-
Are you sure you want to reset the form? All unsaved data will be lost.
|
|
898
|
-
</div>
|
|
899
|
-
`;
|
|
900
|
-
},
|
|
901
|
-
id: `reset-ol-modal-confirm`,
|
|
902
|
-
});
|
|
903
|
-
|
|
904
|
-
if (confirmResult.status === 'confirm') {
|
|
905
|
-
NotificationManager.Push({
|
|
906
|
-
html: 'Resetting form to create new object layer...',
|
|
907
|
-
status: 'info',
|
|
908
|
-
});
|
|
909
|
-
|
|
910
|
-
// Clear all data
|
|
911
|
-
ObjectLayerEngineModal.clearData();
|
|
912
|
-
|
|
913
|
-
setPath(`${getProxyPath()}object-layer-engine`);
|
|
914
|
-
|
|
915
|
-
// Reload the modal
|
|
916
|
-
await ObjectLayerEngineModal.Reload();
|
|
917
|
-
|
|
918
|
-
NotificationManager.Push({
|
|
919
|
-
html: 'Form reset! Ready to create new object layer.',
|
|
920
|
-
status: 'success',
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
|
-
});
|
|
924
|
-
});
|
|
925
|
-
|
|
926
|
-
return html`
|
|
927
|
-
<style>
|
|
928
|
-
.direction-code-bar-frames-title {
|
|
929
|
-
font-weight: bold;
|
|
930
|
-
font-size: 1.2rem;
|
|
931
|
-
padding: 0.5rem;
|
|
932
|
-
}
|
|
933
|
-
.direction-code-bar-frames-img {
|
|
934
|
-
width: 100px;
|
|
935
|
-
height: auto;
|
|
936
|
-
margin: 3px;
|
|
937
|
-
cursor: pointer;
|
|
938
|
-
}
|
|
939
|
-
.direction-code-bar-trash-btn {
|
|
940
|
-
top: 3px;
|
|
941
|
-
left: 30px;
|
|
942
|
-
color: white;
|
|
943
|
-
border: none !important;
|
|
944
|
-
}
|
|
945
|
-
.direction-code-bar-edit-btn {
|
|
946
|
-
top: 3px;
|
|
947
|
-
left: 3px;
|
|
948
|
-
color: white;
|
|
949
|
-
border: none !important;
|
|
950
|
-
}
|
|
951
|
-
.direction-code-bar-frames-btn-add {
|
|
952
|
-
color: white;
|
|
953
|
-
border: none !important;
|
|
954
|
-
}
|
|
955
|
-
.direction-code-bar-trash-btn:hover {
|
|
956
|
-
background: none !important;
|
|
957
|
-
color: red;
|
|
958
|
-
}
|
|
959
|
-
.direction-code-bar-edit-btn:hover {
|
|
960
|
-
background: none !important;
|
|
961
|
-
color: yellow;
|
|
962
|
-
}
|
|
963
|
-
.direction-code-bar-frames-btn-add:hover {
|
|
964
|
-
background: none !important;
|
|
965
|
-
color: #c7ff58;
|
|
966
|
-
}
|
|
967
|
-
.ol-btn-save {
|
|
968
|
-
width: 120px;
|
|
969
|
-
padding: 0.5rem;
|
|
970
|
-
font-size: 20px;
|
|
971
|
-
min-height: 50px;
|
|
972
|
-
}
|
|
973
|
-
.ol-btn-reset {
|
|
974
|
-
width: 120px;
|
|
975
|
-
padding: 0.5rem;
|
|
976
|
-
font-size: 20px;
|
|
977
|
-
min-height: 50px;
|
|
978
|
-
}
|
|
979
|
-
.ol-number-label {
|
|
980
|
-
width: 120px;
|
|
981
|
-
font-size: 16px;
|
|
982
|
-
overflow: hidden;
|
|
983
|
-
font-family: 'retro-font';
|
|
984
|
-
}
|
|
985
|
-
.sub-title-modal {
|
|
986
|
-
color: #ffcc00;
|
|
987
|
-
}
|
|
988
|
-
.stat-label-container {
|
|
989
|
-
display: flex;
|
|
990
|
-
align-items: center;
|
|
991
|
-
}
|
|
992
|
-
.stat-info-icon {
|
|
993
|
-
cursor: default;
|
|
994
|
-
}
|
|
995
|
-
.stat-description {
|
|
996
|
-
padding: 2px 5px;
|
|
997
|
-
border-left: 2px solid #444;
|
|
998
|
-
margin-bottom: 5px;
|
|
999
|
-
max-width: 200px;
|
|
1000
|
-
}
|
|
1001
|
-
.frame-editor-container-loading {
|
|
1002
|
-
width: 100%;
|
|
1003
|
-
height: 150px;
|
|
1004
|
-
color: #ffcc00;
|
|
1005
|
-
}
|
|
1006
|
-
.loading-text {
|
|
1007
|
-
font-family: 'retro-font';
|
|
1008
|
-
font-size: 26px;
|
|
1009
|
-
}
|
|
1010
|
-
</style>
|
|
1011
|
-
${borderChar(2, 'black', [
|
|
1012
|
-
'.sub-title-modal',
|
|
1013
|
-
'.frame-editor-container-loading',
|
|
1014
|
-
'.direction-code-bar-edit-btn',
|
|
1015
|
-
'.direction-code-bar-trash-btn',
|
|
1016
|
-
'.direction-code-bar-frames-btn-add',
|
|
1017
|
-
])}
|
|
1018
|
-
<div class="in frame-editor-container-loading">
|
|
1019
|
-
<div class="abs center frame-editor-container-loading-center"></div>
|
|
1020
|
-
</div>
|
|
1021
|
-
<div class="in section-mp section-mp-border frame-editor-container">
|
|
1022
|
-
<div class="in sub-title-modal"><i class="fa-solid fa-table-cells-large"></i> Frame editor</div>
|
|
1023
|
-
|
|
1024
|
-
<object-layer-engine id="ole" width="${cells}" height="${cells}" pixel-size="${pixelSize}">
|
|
1025
|
-
</object-layer-engine>
|
|
1026
|
-
<object-layer-png-loader id="loader" editor-selector="#ole"></object-layer-png-loader>
|
|
1027
|
-
</div>
|
|
1028
|
-
|
|
1029
|
-
<div class="in section-mp section-mp-border">
|
|
1030
|
-
<div class="in sub-title-modal"><i class="fa-solid fa-database"></i> Render data</div>
|
|
1031
|
-
${dynamicCol({ containerSelector: options.idModal, id: idSectionA })}
|
|
1032
|
-
|
|
1033
|
-
<div class="fl">
|
|
1034
|
-
<div class="in fll ${idSectionA}-col-a">
|
|
1035
|
-
<div class="in section-mp">
|
|
1036
|
-
${await DropDown.Render({
|
|
1037
|
-
value: ObjectLayerEngineModal.templates[0].id,
|
|
1038
|
-
label: html`${Translate.Render('select-template')}`,
|
|
1039
|
-
data: ObjectLayerEngineModal.templates.map((template) => {
|
|
1040
|
-
return {
|
|
1041
|
-
value: template.id,
|
|
1042
|
-
display: html`<i class="fa-solid fa-paint-roller"></i> ${template.label}`,
|
|
1043
|
-
onClick: async () => {
|
|
1044
|
-
ObjectLayerEngineModal.RenderTemplate(template.data);
|
|
1045
|
-
},
|
|
1046
|
-
};
|
|
1047
|
-
}),
|
|
1048
|
-
})}
|
|
1049
|
-
</div>
|
|
1050
|
-
</div>
|
|
1051
|
-
<div class="in fll ${idSectionA}-col-b">
|
|
1052
|
-
<div class="in section-mp-border" style="width: 135px;">
|
|
1053
|
-
${await Input.Render({
|
|
1054
|
-
id: `ol-input-render-frame-duration`,
|
|
1055
|
-
label: html`<div class="inl ol-number-label">
|
|
1056
|
-
<i class="fa-solid fa-chart-simple"></i> Frame duration
|
|
1057
|
-
</div>`,
|
|
1058
|
-
containerClass: 'inl',
|
|
1059
|
-
type: 'number',
|
|
1060
|
-
min: 100,
|
|
1061
|
-
max: 1000,
|
|
1062
|
-
placeholder: true,
|
|
1063
|
-
value: renderFrameDuration,
|
|
1064
|
-
})}
|
|
1065
|
-
</div>
|
|
1066
|
-
<div class="in section-mp">
|
|
1067
|
-
${await ToggleSwitch.Render({
|
|
1068
|
-
id: 'ol-toggle-render-is-stateless',
|
|
1069
|
-
wrapper: true,
|
|
1070
|
-
wrapperLabel: html`${Translate.Render('is-stateless')}`,
|
|
1071
|
-
disabledOnClick: true,
|
|
1072
|
-
checked: renderIsStateless,
|
|
1073
|
-
on: {
|
|
1074
|
-
unchecked: () => {
|
|
1075
|
-
renderIsStateless = false;
|
|
1076
|
-
console.warn('renderIsStateless', renderIsStateless);
|
|
1077
|
-
},
|
|
1078
|
-
checked: () => {
|
|
1079
|
-
renderIsStateless = true;
|
|
1080
|
-
console.warn('renderIsStateless', renderIsStateless);
|
|
1081
|
-
},
|
|
1082
|
-
},
|
|
1083
|
-
})}
|
|
1084
|
-
</div>
|
|
1085
|
-
</div>
|
|
1086
|
-
</div>
|
|
1087
|
-
${directionsCodeBarRender}
|
|
1088
|
-
</div>
|
|
1089
|
-
${dynamicCol({ containerSelector: options.idModal, id: idSectionB, type: 'a-50-b-50' })}
|
|
1090
|
-
|
|
1091
|
-
<div class="fl">
|
|
1092
|
-
<div class="in fll ${idSectionB}-col-a">
|
|
1093
|
-
<div class="in section-mp section-mp-border">
|
|
1094
|
-
<div class="in sub-title-modal"><i class="fa-solid fa-database"></i> Item data</div>
|
|
1095
|
-
${await Input.Render({
|
|
1096
|
-
id: `ol-input-item-id`,
|
|
1097
|
-
label: html`<i class="fa-solid fa-pen-to-square"></i> ${Translate.Render('item-id')}`,
|
|
1098
|
-
containerClass: '',
|
|
1099
|
-
placeholder: true,
|
|
1100
|
-
value: loadedData?.metadata?.data?.item?.id || '',
|
|
1101
|
-
})}
|
|
1102
|
-
${await Input.Render({
|
|
1103
|
-
id: `ol-input-item-description`,
|
|
1104
|
-
label: html`<i class="fa-solid fa-pen-to-square"></i> ${Translate.Render('item-description')}`,
|
|
1105
|
-
containerClass: '',
|
|
1106
|
-
placeholder: true,
|
|
1107
|
-
value: loadedData?.metadata?.data?.item?.description || '',
|
|
1108
|
-
})}
|
|
1109
|
-
<div class="in section-mp">
|
|
1110
|
-
${await DropDown.Render({
|
|
1111
|
-
value: selectItemType,
|
|
1112
|
-
label: html`${Translate.Render('select-item-type')}`,
|
|
1113
|
-
data: itemTypes.map((itemType) => {
|
|
1114
|
-
return {
|
|
1115
|
-
value: itemType,
|
|
1116
|
-
display: html`${itemType}`,
|
|
1117
|
-
onClick: async () => {
|
|
1118
|
-
console.warn('itemType click', itemType);
|
|
1119
|
-
selectItemType = itemType;
|
|
1120
|
-
},
|
|
1121
|
-
};
|
|
1122
|
-
}),
|
|
1123
|
-
})}
|
|
1124
|
-
</div>
|
|
1125
|
-
<div class="in section-mp">
|
|
1126
|
-
${await ToggleSwitch.Render({
|
|
1127
|
-
id: 'ol-toggle-item-activable',
|
|
1128
|
-
wrapper: true,
|
|
1129
|
-
wrapperLabel: html`${Translate.Render('item-activable')}`,
|
|
1130
|
-
disabledOnClick: true,
|
|
1131
|
-
checked: itemActivable,
|
|
1132
|
-
on: {
|
|
1133
|
-
unchecked: () => {
|
|
1134
|
-
itemActivable = false;
|
|
1135
|
-
console.warn('itemActivable', itemActivable);
|
|
1136
|
-
},
|
|
1137
|
-
checked: () => {
|
|
1138
|
-
itemActivable = true;
|
|
1139
|
-
console.warn('itemActivable', itemActivable);
|
|
1140
|
-
},
|
|
1141
|
-
},
|
|
1142
|
-
})}
|
|
1143
|
-
</div>
|
|
1144
|
-
</div>
|
|
1145
|
-
</div>
|
|
1146
|
-
<div class="in fll ${idSectionB}-col-b">
|
|
1147
|
-
<div class="in section-mp section-mp-border">
|
|
1148
|
-
<div class="in sub-title-modal"><i class="fa-solid fa-database"></i> Stats data</div>
|
|
1149
|
-
${statsInputsRender}
|
|
1150
|
-
</div>
|
|
1151
|
-
</div>
|
|
1152
|
-
</div>
|
|
1153
|
-
|
|
1154
|
-
<div class="fl section-mp">
|
|
1155
|
-
${await BtnIcon.Render({
|
|
1156
|
-
label: html`<i class="submit-btn-icon fa-solid fa-folder-open"></i> ${Translate.Render('save')}`,
|
|
1157
|
-
class: `in flr ol-btn-save`,
|
|
1158
|
-
})}
|
|
1159
|
-
${await BtnIcon.Render({
|
|
1160
|
-
label: html`<i class="submit-btn-icon fa-solid fa-broom"></i> ${Translate.Render('reset')}`,
|
|
1161
|
-
class: `in flr ol-btn-reset`,
|
|
1162
|
-
})}
|
|
1163
|
-
</div>
|
|
1164
|
-
<div class="in section-mp"></div>
|
|
1165
|
-
`;
|
|
1166
|
-
},
|
|
1167
|
-
getDirectionsFromDirectionCode(directionCode = '08') {
|
|
1168
|
-
let objectLayerFrameDirections = [];
|
|
1169
|
-
|
|
1170
|
-
switch (directionCode) {
|
|
1171
|
-
case '08':
|
|
1172
|
-
objectLayerFrameDirections = ['down_idle', 'none_idle', 'default_idle'];
|
|
1173
|
-
break;
|
|
1174
|
-
case '18':
|
|
1175
|
-
objectLayerFrameDirections = ['down_walking'];
|
|
1176
|
-
break;
|
|
1177
|
-
case '02':
|
|
1178
|
-
objectLayerFrameDirections = ['up_idle'];
|
|
1179
|
-
break;
|
|
1180
|
-
case '12':
|
|
1181
|
-
objectLayerFrameDirections = ['up_walking'];
|
|
1182
|
-
break;
|
|
1183
|
-
case '04':
|
|
1184
|
-
objectLayerFrameDirections = ['left_idle', 'up_left_idle', 'down_left_idle'];
|
|
1185
|
-
break;
|
|
1186
|
-
case '14':
|
|
1187
|
-
objectLayerFrameDirections = ['left_walking', 'up_left_walking', 'down_left_walking'];
|
|
1188
|
-
break;
|
|
1189
|
-
case '06':
|
|
1190
|
-
objectLayerFrameDirections = ['right_idle', 'up_right_idle', 'down_right_idle'];
|
|
1191
|
-
break;
|
|
1192
|
-
case '16':
|
|
1193
|
-
objectLayerFrameDirections = ['right_walking', 'up_right_walking', 'down_right_walking'];
|
|
1194
|
-
break;
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
return objectLayerFrameDirections;
|
|
1198
|
-
},
|
|
1199
|
-
toManagement: async () => {
|
|
1200
|
-
await ObjectLayerEngineModal.clearData();
|
|
1201
|
-
const subModalId = 'viewer' || 'management';
|
|
1202
|
-
const modalId = `modal-object-layer-engine-${subModalId}`;
|
|
1203
|
-
const queryParams = getQueryParams();
|
|
1204
|
-
queryParams.cid = '';
|
|
1205
|
-
queryParams.page = 1;
|
|
1206
|
-
setQueryParams(queryParams);
|
|
1207
|
-
const managerComponent = DefaultManagement.Tokens[modalId];
|
|
1208
|
-
if (managerComponent) {
|
|
1209
|
-
managerComponent.page = 1;
|
|
1210
|
-
if (!managerComponent.readyRowDataEvent) managerComponent.readyRowDataEvent = {};
|
|
1211
|
-
let readyLoad = false;
|
|
1212
|
-
const gridId = `object-layer-engine-management-grid-${modalId}`;
|
|
1213
|
-
managerComponent.readyRowDataEvent[`object-layer-engine-${subModalId}`] = async () => {
|
|
1214
|
-
if (readyLoad) {
|
|
1215
|
-
AgGrid.grids[gridId].setGridOption('getRowClass', null);
|
|
1216
|
-
return delete managerComponent.readyRowDataEvent[`object-layer-engine-${subModalId}`];
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
AgGrid.grids[gridId].setGridOption('getRowClass', (params) => {
|
|
1220
|
-
if (params.node.rowIndex === 0) {
|
|
1221
|
-
return 'row-new-highlight';
|
|
1222
|
-
}
|
|
1223
|
-
});
|
|
1224
|
-
readyLoad = true;
|
|
1225
|
-
};
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
const _s = s(` .management-table-btn-reload-${modalId}`);
|
|
1229
|
-
if (_s) _s.click();
|
|
1230
|
-
|
|
1231
|
-
s(`.main-btn-object-layer-engine-${subModalId}`).click();
|
|
1232
|
-
},
|
|
1233
|
-
Reload: async function () {
|
|
1234
|
-
// Clear data before reload to prevent contamination
|
|
1235
|
-
ObjectLayerEngineModal.clearData();
|
|
1236
|
-
const idModal = 'modal-object-layer-engine';
|
|
1237
|
-
if (s(`.modal-object-layer-engine`))
|
|
1238
|
-
Modal.writeHTML({
|
|
1239
|
-
idModal,
|
|
1240
|
-
html: await Modal.Data[idModal].options.html(),
|
|
1241
|
-
});
|
|
1242
|
-
},
|
|
1243
|
-
};
|
|
1244
|
-
|
|
1245
|
-
export { ObjectLayerEngineModal };
|