cyberia 3.0.1 → 3.0.2
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/.github/workflows/engine-cyberia.cd.yml +1 -0
- package/CHANGELOG.md +56 -1
- package/CLI-HELP.md +2 -4
- package/README.md +139 -0
- package/bin/build.js +5 -0
- package/bin/cyberia.js +385 -71
- package/bin/deploy.js +18 -26
- package/bin/file.js +3 -0
- package/bin/index.js +385 -71
- package/conf.js +32 -3
- package/deployment.yaml +2 -2
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/manifests/ipfs/configmap.yaml +7 -0
- package/package.json +8 -8
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +2 -0
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +7 -0
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +93 -2
- package/src/api/file/file.controller.js +3 -13
- package/src/api/file/file.ref.json +0 -21
- package/src/api/ipfs/ipfs.controller.js +104 -0
- package/src/api/ipfs/ipfs.model.js +71 -0
- package/src/api/ipfs/ipfs.router.js +31 -0
- package/src/api/ipfs/ipfs.service.js +193 -0
- package/src/api/object-layer/README.md +139 -0
- package/src/api/object-layer/object-layer.controller.js +3 -0
- package/src/api/object-layer/object-layer.model.js +15 -1
- package/src/api/object-layer/object-layer.router.js +6 -10
- package/src/api/object-layer/object-layer.service.js +311 -182
- package/src/cli/cluster.js +30 -38
- package/src/cli/index.js +0 -1
- package/src/cli/run.js +14 -0
- package/src/client/components/core/LoadingAnimation.js +2 -3
- package/src/client/components/core/Modal.js +1 -1
- package/src/client/components/cyberia/ObjectLayerEngineModal.js +4 -5
- package/src/client/components/cyberia/ObjectLayerEngineViewer.js +280 -29
- package/src/client/services/ipfs/ipfs.service.js +144 -0
- package/src/client/services/object-layer/object-layer.management.js +161 -8
- package/src/index.js +1 -1
- package/src/runtime/express/Express.js +1 -1
- package/src/server/auth.js +18 -18
- package/src/server/ipfs-client.js +433 -0
- package/src/server/object-layer.js +649 -18
- package/src/server/semantic-layer-generator.js +1083 -0
- package/src/server/shape-generator.js +952 -0
- package/test/shape-generator.test.js +457 -0
- package/bin/ssl.js +0 -63
|
@@ -10,15 +10,16 @@ import {
|
|
|
10
10
|
import { ObjectLayerService } from '../../services/object-layer/object-layer.service.js';
|
|
11
11
|
import { AtlasSpriteSheetService } from '../../services/atlas-sprite-sheet/atlas-sprite-sheet.service.js';
|
|
12
12
|
import { NotificationManager } from '../core/NotificationManager.js';
|
|
13
|
-
import { htmls, s } from '../core/VanillaJs.js';
|
|
13
|
+
import { append, htmls, s } from '../core/VanillaJs.js';
|
|
14
14
|
|
|
15
|
-
import { darkTheme, ThemeEvents } from '../core/Css.js';
|
|
15
|
+
import { darkTheme, ThemeEvents, subThemeManager, lightenHex, darkenHex } from '../core/Css.js';
|
|
16
16
|
import { ObjectLayerManagement } from '../../services/object-layer/object-layer.management.js';
|
|
17
17
|
import { ObjectLayerEngineModal } from './ObjectLayerEngineModal.js';
|
|
18
18
|
import { Modal } from '../core/Modal.js';
|
|
19
19
|
import { DefaultManagement } from '../../services/default/default.management.js';
|
|
20
20
|
import { AgGrid } from '../core/AgGrid.js';
|
|
21
21
|
import { EventsUI } from '../core/EventsUI.js';
|
|
22
|
+
import { createJSONEditor } from 'vanilla-jsoneditor';
|
|
22
23
|
|
|
23
24
|
const logger = loggerFactory(import.meta);
|
|
24
25
|
|
|
@@ -30,10 +31,11 @@ const ObjectLayerEngineViewer = {
|
|
|
30
31
|
currentMode: 'idle',
|
|
31
32
|
webp: null,
|
|
32
33
|
isGenerating: false,
|
|
33
|
-
|
|
34
|
+
currentObjectId: undefined, // Track current loaded object layer id to prevent unnecessary reloads
|
|
34
35
|
atlasSpriteSheet: null,
|
|
35
36
|
isGeneratingAtlas: false,
|
|
36
37
|
webpMetadata: null,
|
|
38
|
+
metadataJsonEditor: null,
|
|
37
39
|
},
|
|
38
40
|
|
|
39
41
|
// Map user-friendly direction/mode to numeric direction codes
|
|
@@ -55,8 +57,8 @@ const ObjectLayerEngineViewer = {
|
|
|
55
57
|
Render: async function ({ Elements }) {
|
|
56
58
|
const id = 'object-layer-engine-viewer';
|
|
57
59
|
|
|
58
|
-
// Reset
|
|
59
|
-
this.Data.
|
|
60
|
+
// Reset currentObjectId when modal is rendered to ensure Reload triggers properly
|
|
61
|
+
this.Data.currentObjectId = undefined;
|
|
60
62
|
|
|
61
63
|
Modal.Data[`modal-${id}`].onReloadModalListener[id] = async () => {
|
|
62
64
|
ObjectLayerEngineViewer.Reload({ Elements });
|
|
@@ -66,15 +68,15 @@ const ObjectLayerEngineViewer = {
|
|
|
66
68
|
listenQueryParamsChange({
|
|
67
69
|
id: `${id}-query-listener`,
|
|
68
70
|
event: async (queryParams) => {
|
|
69
|
-
const
|
|
71
|
+
const objectId = queryParams.id || null;
|
|
70
72
|
|
|
71
73
|
if (!s(`.modal-${id}`) || !s(`#${id}`)) {
|
|
72
74
|
logger.warn('ObjectLayerEngineViewer DOM not ready for query param change');
|
|
73
75
|
return;
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
// Only reload if
|
|
77
|
-
if (
|
|
78
|
+
// Only reload if object id actually changed (normalize undefined to null for comparison)
|
|
79
|
+
if (objectId !== this.Data.currentObjectId) {
|
|
78
80
|
await this.Reload({ Elements });
|
|
79
81
|
}
|
|
80
82
|
},
|
|
@@ -101,8 +103,8 @@ const ObjectLayerEngineViewer = {
|
|
|
101
103
|
return;
|
|
102
104
|
}
|
|
103
105
|
|
|
104
|
-
// Clear current
|
|
105
|
-
this.Data.
|
|
106
|
+
// Clear current object id when rendering empty state
|
|
107
|
+
this.Data.currentObjectId = null;
|
|
106
108
|
|
|
107
109
|
// Check if the management table grid already exists AND its DOM is still present
|
|
108
110
|
// If it does, don't re-render (just let DefaultManagement's RouterEvents handle URL changes)
|
|
@@ -569,6 +571,50 @@ const ObjectLayerEngineViewer = {
|
|
|
569
571
|
color: ${darkTheme ? '#666' : '#999'};
|
|
570
572
|
padding: 20px;
|
|
571
573
|
}
|
|
574
|
+
.ipfs-cid-label {
|
|
575
|
+
font-size: 14px;
|
|
576
|
+
color: ${darkTheme ? '#b0b8c8' : '#555'};
|
|
577
|
+
word-break: break-all;
|
|
578
|
+
padding: 10px 12px;
|
|
579
|
+
border: 1px solid
|
|
580
|
+
${(() => {
|
|
581
|
+
const tc = darkTheme ? subThemeManager.darkColor : subThemeManager.lightColor;
|
|
582
|
+
return tc ? (darkTheme ? darkenHex(tc, 0.7) : lightenHex(tc, 0.7)) : darkTheme ? '#3a3f4b' : '#d0d5dd';
|
|
583
|
+
})()};
|
|
584
|
+
border-radius: 6px;
|
|
585
|
+
background: ${(() => {
|
|
586
|
+
const tc = darkTheme ? subThemeManager.darkColor : subThemeManager.lightColor;
|
|
587
|
+
return tc ? (darkTheme ? darkenHex(tc, 0.85) : lightenHex(tc, 0.85)) : darkTheme ? '#1a1f2e' : '#f4f6f9';
|
|
588
|
+
})()};
|
|
589
|
+
margin-top: 8px;
|
|
590
|
+
display: flex;
|
|
591
|
+
align-items: baseline;
|
|
592
|
+
gap: 6px;
|
|
593
|
+
line-height: 1.5;
|
|
594
|
+
}
|
|
595
|
+
.ipfs-cid-label i {
|
|
596
|
+
color: ${(() => {
|
|
597
|
+
const tc = darkTheme ? subThemeManager.darkColor : subThemeManager.lightColor;
|
|
598
|
+
return tc ? (darkTheme ? lightenHex(tc, 0.5) : darkenHex(tc, 0.3)) : darkTheme ? '#4a9eff' : '#2196F3';
|
|
599
|
+
})()};
|
|
600
|
+
font-size: 14px;
|
|
601
|
+
flex-shrink: 0;
|
|
602
|
+
}
|
|
603
|
+
.ipfs-cid-label strong {
|
|
604
|
+
color: ${darkTheme ? '#cdd4e0' : '#333'};
|
|
605
|
+
white-space: nowrap;
|
|
606
|
+
font-size: 14px;
|
|
607
|
+
}
|
|
608
|
+
.ipfs-cid-label .ipfs-cid-value {
|
|
609
|
+
user-select: all;
|
|
610
|
+
cursor: text;
|
|
611
|
+
color: ${(() => {
|
|
612
|
+
const tc = darkTheme ? subThemeManager.darkColor : subThemeManager.lightColor;
|
|
613
|
+
return tc ? (darkTheme ? lightenHex(tc, 0.6) : darkenHex(tc, 0.3)) : darkTheme ? '#8ecfff' : '#1565c0';
|
|
614
|
+
})()};
|
|
615
|
+
font-family: monospace;
|
|
616
|
+
font-size: 13px;
|
|
617
|
+
}
|
|
572
618
|
|
|
573
619
|
@media (max-width: 850px) {
|
|
574
620
|
.object-layer-viewer-container {
|
|
@@ -619,6 +665,38 @@ const ObjectLayerEngineViewer = {
|
|
|
619
665
|
<span style="font-weight: 600;">${itemActivable ? 'Yes' : 'No'}</span>
|
|
620
666
|
</div>
|
|
621
667
|
</div>
|
|
668
|
+
${objectLayer.cid
|
|
669
|
+
? html`<div class="ipfs-cid-label">
|
|
670
|
+
<i class="fa-solid fa-cube"></i>
|
|
671
|
+
<strong>IPFS CID:</strong>
|
|
672
|
+
<span class="ipfs-cid-value">${objectLayer.cid}</span>
|
|
673
|
+
</div>`
|
|
674
|
+
: ''}
|
|
675
|
+
${objectLayer.data.atlasSpriteSheetCid
|
|
676
|
+
? html`<div class="ipfs-cid-label">
|
|
677
|
+
<i class="fa-solid fa-image"></i>
|
|
678
|
+
<strong>Atlas IPFS CID:</strong>
|
|
679
|
+
<span class="ipfs-cid-value">${objectLayer.data.atlasSpriteSheetCid}</span>
|
|
680
|
+
</div>`
|
|
681
|
+
: ''}
|
|
682
|
+
${objectLayer.sha256
|
|
683
|
+
? html`<div class="ipfs-cid-label">
|
|
684
|
+
<i class="fa-solid fa-fingerprint"></i>
|
|
685
|
+
<strong>SHA-256:</strong>
|
|
686
|
+
<span class="ipfs-cid-value">${objectLayer.sha256}</span>
|
|
687
|
+
</div>`
|
|
688
|
+
: ''}
|
|
689
|
+
</div>
|
|
690
|
+
|
|
691
|
+
<!-- Metadata JSON Section -->
|
|
692
|
+
<div class="control-group" style="margin-bottom: 20px;">
|
|
693
|
+
<h4><i class="fa-solid fa-code"></i> Metadata JSON</h4>
|
|
694
|
+
<div
|
|
695
|
+
id="metadata-json-editor-container"
|
|
696
|
+
style="height: 400px; border-radius: 6px; overflow: hidden; border: 1px solid ${darkTheme
|
|
697
|
+
? '#444'
|
|
698
|
+
: '#ddd'};"
|
|
699
|
+
></div>
|
|
622
700
|
</div>
|
|
623
701
|
|
|
624
702
|
<!-- Stats Data Section -->
|
|
@@ -777,6 +855,16 @@ const ObjectLayerEngineViewer = {
|
|
|
777
855
|
<p style="padding: 2px"><strong class="item-data-key-label">ID:</strong></p>
|
|
778
856
|
<p style="padding: 2px" font-size: 12px;">${this.Data.atlasSpriteSheet._id}</p>
|
|
779
857
|
</div>
|
|
858
|
+
${
|
|
859
|
+
this.Data.atlasSpriteSheet.cid
|
|
860
|
+
? html`<div style="grid-column: 1 / -1;">
|
|
861
|
+
<p style="padding: 2px"><strong class="item-data-key-label">IPFS CID:</strong></p>
|
|
862
|
+
<p class="ipfs-cid-value" style="padding: 2px;">
|
|
863
|
+
${this.Data.atlasSpriteSheet.cid}
|
|
864
|
+
</p>
|
|
865
|
+
</div>`
|
|
866
|
+
: ''
|
|
867
|
+
}
|
|
780
868
|
<div>
|
|
781
869
|
<p style="padding: 2px"><strong class="item-data-key-label">Dimensions:</strong></p>
|
|
782
870
|
<p style="padding: 2px">
|
|
@@ -837,6 +925,10 @@ const ObjectLayerEngineViewer = {
|
|
|
837
925
|
<i class="fa-solid fa-edit"></i>
|
|
838
926
|
<span>Edit</span>
|
|
839
927
|
</button>
|
|
928
|
+
<button class="default-viewer-btn" id="delete-object-layer-btn" style="background: #dc3545;">
|
|
929
|
+
<i class="fa-solid fa-trash"></i>
|
|
930
|
+
<span>Delete</span>
|
|
931
|
+
</button>
|
|
840
932
|
</div>
|
|
841
933
|
`}
|
|
842
934
|
</div>
|
|
@@ -850,6 +942,9 @@ const ObjectLayerEngineViewer = {
|
|
|
850
942
|
if (this.Data.webp) {
|
|
851
943
|
this.displayWebp();
|
|
852
944
|
}
|
|
945
|
+
|
|
946
|
+
// Initialize metadata JSON editor
|
|
947
|
+
this.initMetadataJsonEditor();
|
|
853
948
|
},
|
|
854
949
|
|
|
855
950
|
displayWebp: async function () {
|
|
@@ -894,6 +989,128 @@ const ObjectLayerEngineViewer = {
|
|
|
894
989
|
}
|
|
895
990
|
},
|
|
896
991
|
|
|
992
|
+
initMetadataJsonEditor: async function () {
|
|
993
|
+
const container = s('#metadata-json-editor-container');
|
|
994
|
+
if (!container) return;
|
|
995
|
+
|
|
996
|
+
// Ensure vanilla-jsoneditor dark theme CSS is loaded
|
|
997
|
+
if (!s('.jse-dark-theme-link')) {
|
|
998
|
+
append(
|
|
999
|
+
'head',
|
|
1000
|
+
html`<link
|
|
1001
|
+
class="jse-dark-theme-link"
|
|
1002
|
+
rel="stylesheet"
|
|
1003
|
+
type="text/css"
|
|
1004
|
+
href="${getProxyPath()}styles/vanilla-jsoneditor/jse-theme-dark.css"
|
|
1005
|
+
/>`,
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// Destroy previous instance if any
|
|
1010
|
+
if (this.Data.metadataJsonEditor) {
|
|
1011
|
+
this.Data.metadataJsonEditor.destroy();
|
|
1012
|
+
this.Data.metadataJsonEditor = null;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
const objectLayerId = this.Data.objectLayer?._id;
|
|
1016
|
+
if (!objectLayerId) return;
|
|
1017
|
+
|
|
1018
|
+
try {
|
|
1019
|
+
const response = await ObjectLayerService.getMetadata({ id: objectLayerId });
|
|
1020
|
+
const metadataContent = response.status === 'success' && response.data ? response.data : response;
|
|
1021
|
+
|
|
1022
|
+
this.Data.metadataJsonEditor = createJSONEditor({
|
|
1023
|
+
target: container,
|
|
1024
|
+
props: {
|
|
1025
|
+
content: { json: metadataContent },
|
|
1026
|
+
readOnly: true,
|
|
1027
|
+
mainMenuBar: true,
|
|
1028
|
+
navigationBar: true,
|
|
1029
|
+
statusBar: true,
|
|
1030
|
+
mode: 'tree',
|
|
1031
|
+
},
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
// Apply dark theme class based on current theme
|
|
1035
|
+
this._applyJsonEditorTheme();
|
|
1036
|
+
|
|
1037
|
+
// Register theme event to toggle dark/light on the JSON editor
|
|
1038
|
+
ThemeEvents['metadata-json-editor-theme'] = () => {
|
|
1039
|
+
this._applyJsonEditorTheme();
|
|
1040
|
+
};
|
|
1041
|
+
} catch (err) {
|
|
1042
|
+
logger.warn('Failed to initialize metadata JSON editor:', err);
|
|
1043
|
+
container.innerHTML = html`<div style="padding: 20px; color: #999; text-align: center;">
|
|
1044
|
+
Failed to load metadata JSON
|
|
1045
|
+
</div>`;
|
|
1046
|
+
}
|
|
1047
|
+
},
|
|
1048
|
+
|
|
1049
|
+
_applyJsonEditorTheme: function () {
|
|
1050
|
+
const container = s('#metadata-json-editor-container');
|
|
1051
|
+
if (!container) return;
|
|
1052
|
+
if (darkTheme) {
|
|
1053
|
+
container.classList.add('jse-theme-dark');
|
|
1054
|
+
} else {
|
|
1055
|
+
container.classList.remove('jse-theme-dark');
|
|
1056
|
+
}
|
|
1057
|
+
},
|
|
1058
|
+
|
|
1059
|
+
deleteObjectLayer: async function ({ Elements } = {}) {
|
|
1060
|
+
const objectLayerId = this.Data.objectLayer?._id;
|
|
1061
|
+
if (!objectLayerId) return;
|
|
1062
|
+
|
|
1063
|
+
const itemId = this.Data.objectLayer?.data?.item?.id || objectLayerId;
|
|
1064
|
+
|
|
1065
|
+
const confirmResult = await Modal.RenderConfirm({
|
|
1066
|
+
id: 'delete-object-layer-confirm',
|
|
1067
|
+
html: async () => html`
|
|
1068
|
+
<div class="in section-mp" style="text-align: center">
|
|
1069
|
+
<p>Are you sure you want to permanently delete object layer <strong>"${itemId}"</strong>?</p>
|
|
1070
|
+
<p style="color: #dc3545; font-size: 13px; margin-top: 8px;">
|
|
1071
|
+
This will remove all associated data including render frames, atlas sprite sheet, IPFS pins, and static
|
|
1072
|
+
asset files.
|
|
1073
|
+
</p>
|
|
1074
|
+
</div>
|
|
1075
|
+
`,
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
if (confirmResult.status !== 'confirm') return;
|
|
1079
|
+
|
|
1080
|
+
try {
|
|
1081
|
+
const result = await ObjectLayerService.delete({ id: objectLayerId });
|
|
1082
|
+
if (result.status === 'success') {
|
|
1083
|
+
NotificationManager.Push({
|
|
1084
|
+
html: `Object layer "${itemId}" deleted successfully`,
|
|
1085
|
+
status: 'success',
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
// Clean up JSON editor and its theme event
|
|
1089
|
+
if (this.Data.metadataJsonEditor) {
|
|
1090
|
+
this.Data.metadataJsonEditor.destroy();
|
|
1091
|
+
this.Data.metadataJsonEditor = null;
|
|
1092
|
+
}
|
|
1093
|
+
delete ThemeEvents['metadata-json-editor-theme'];
|
|
1094
|
+
|
|
1095
|
+
// Navigate back to list
|
|
1096
|
+
this.Data.currentObjectId = undefined;
|
|
1097
|
+
this.Data.objectLayer = null;
|
|
1098
|
+
this.Data.webp = null;
|
|
1099
|
+
this.Data.webpMetadata = null;
|
|
1100
|
+
this.Data.atlasSpriteSheet = null;
|
|
1101
|
+
setQueryParams({ id: null }, { replace: false });
|
|
1102
|
+
} else {
|
|
1103
|
+
throw new Error(result.message || 'Failed to delete object layer');
|
|
1104
|
+
}
|
|
1105
|
+
} catch (error) {
|
|
1106
|
+
logger.error('Error deleting object layer:', error);
|
|
1107
|
+
NotificationManager.Push({
|
|
1108
|
+
html: `Failed to delete object layer: ${error.message}`,
|
|
1109
|
+
status: 'error',
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
},
|
|
1113
|
+
|
|
897
1114
|
attachEventListeners: function ({ Elements }) {
|
|
898
1115
|
// Direction buttons
|
|
899
1116
|
const directionButtons = document.querySelectorAll('[data-direction]');
|
|
@@ -904,7 +1121,7 @@ const ObjectLayerEngineViewer = {
|
|
|
904
1121
|
if (direction !== this.Data.currentDirection) {
|
|
905
1122
|
this.Data.currentDirection = direction;
|
|
906
1123
|
await this.renderViewer({ Elements });
|
|
907
|
-
|
|
1124
|
+
// attachEventListeners is already called inside renderViewer
|
|
908
1125
|
await this.generateWebp();
|
|
909
1126
|
}
|
|
910
1127
|
});
|
|
@@ -919,7 +1136,7 @@ const ObjectLayerEngineViewer = {
|
|
|
919
1136
|
if (mode !== this.Data.currentMode) {
|
|
920
1137
|
this.Data.currentMode = mode;
|
|
921
1138
|
await this.renderViewer({ Elements });
|
|
922
|
-
|
|
1139
|
+
// attachEventListeners is already called inside renderViewer
|
|
923
1140
|
await this.generateWebp();
|
|
924
1141
|
}
|
|
925
1142
|
});
|
|
@@ -936,10 +1153,26 @@ const ObjectLayerEngineViewer = {
|
|
|
936
1153
|
// Return to list button
|
|
937
1154
|
const listBtn = s('#return-to-list-btn');
|
|
938
1155
|
if (listBtn) {
|
|
939
|
-
listBtn.addEventListener('click', () => {
|
|
940
|
-
// Clear
|
|
941
|
-
|
|
942
|
-
|
|
1156
|
+
listBtn.addEventListener('click', async () => {
|
|
1157
|
+
// Clear object data and reset state
|
|
1158
|
+
this.Data.webp = null;
|
|
1159
|
+
this.Data.webpMetadata = null;
|
|
1160
|
+
this.Data.objectLayer = null;
|
|
1161
|
+
this.Data.frameCounts = null;
|
|
1162
|
+
|
|
1163
|
+
// Set currentObjectId to null BEFORE setQueryParams so the
|
|
1164
|
+
// listenQueryParamsChange listener sees the id already matches
|
|
1165
|
+
// and skips calling Reload (avoids double-render race condition)
|
|
1166
|
+
this.Data.currentObjectId = null;
|
|
1167
|
+
|
|
1168
|
+
// Update the URL to remove the id parameter
|
|
1169
|
+
setQueryParams({ id: null }, { replace: false });
|
|
1170
|
+
|
|
1171
|
+
// Directly render the list view instead of relying on the
|
|
1172
|
+
// listener → Reload → renderEmpty chain which can silently
|
|
1173
|
+
// fail when the URL was already clean or currentObjectId
|
|
1174
|
+
// was already null
|
|
1175
|
+
await this.renderEmpty({ Elements });
|
|
943
1176
|
});
|
|
944
1177
|
}
|
|
945
1178
|
|
|
@@ -951,6 +1184,14 @@ const ObjectLayerEngineViewer = {
|
|
|
951
1184
|
});
|
|
952
1185
|
}
|
|
953
1186
|
|
|
1187
|
+
// Delete button
|
|
1188
|
+
const deleteBtn = s('#delete-object-layer-btn');
|
|
1189
|
+
if (deleteBtn) {
|
|
1190
|
+
deleteBtn.addEventListener('click', async () => {
|
|
1191
|
+
await this.deleteObjectLayer({ Elements });
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
|
|
954
1195
|
// Atlas buttons
|
|
955
1196
|
if (s('#generate-atlas-btn')) {
|
|
956
1197
|
EventsUI.onClick('#generate-atlas-btn', async () => {
|
|
@@ -1020,7 +1261,10 @@ const ObjectLayerEngineViewer = {
|
|
|
1020
1261
|
html: 'Atlas sprite sheet generated successfully',
|
|
1021
1262
|
status: 'success',
|
|
1022
1263
|
});
|
|
1264
|
+
// Reset generating flag before reload so renderViewer shows updated content
|
|
1265
|
+
this.Data.isGeneratingAtlas = false;
|
|
1023
1266
|
await this.Reload({ Elements, force: true, skipWebp: true });
|
|
1267
|
+
return;
|
|
1024
1268
|
} else {
|
|
1025
1269
|
throw new Error(message || 'Failed to generate atlas');
|
|
1026
1270
|
}
|
|
@@ -1031,8 +1275,10 @@ const ObjectLayerEngineViewer = {
|
|
|
1031
1275
|
status: 'error',
|
|
1032
1276
|
});
|
|
1033
1277
|
} finally {
|
|
1034
|
-
this.Data.isGeneratingAtlas
|
|
1035
|
-
|
|
1278
|
+
if (this.Data.isGeneratingAtlas) {
|
|
1279
|
+
this.Data.isGeneratingAtlas = false;
|
|
1280
|
+
await this.renderViewer({ Elements });
|
|
1281
|
+
}
|
|
1036
1282
|
}
|
|
1037
1283
|
},
|
|
1038
1284
|
|
|
@@ -1062,7 +1308,10 @@ const ObjectLayerEngineViewer = {
|
|
|
1062
1308
|
html: 'Atlas sprite sheet removed successfully',
|
|
1063
1309
|
status: 'success',
|
|
1064
1310
|
});
|
|
1311
|
+
// Reset generating flag before reload so renderViewer shows updated content
|
|
1312
|
+
this.Data.isGeneratingAtlas = false;
|
|
1065
1313
|
await this.Reload({ Elements, force: true, skipWebp: true });
|
|
1314
|
+
return;
|
|
1066
1315
|
} else {
|
|
1067
1316
|
throw new Error(message || 'Failed to remove atlas');
|
|
1068
1317
|
}
|
|
@@ -1073,8 +1322,10 @@ const ObjectLayerEngineViewer = {
|
|
|
1073
1322
|
status: 'error',
|
|
1074
1323
|
});
|
|
1075
1324
|
} finally {
|
|
1076
|
-
this.Data.isGeneratingAtlas
|
|
1077
|
-
|
|
1325
|
+
if (this.Data.isGeneratingAtlas) {
|
|
1326
|
+
this.Data.isGeneratingAtlas = false;
|
|
1327
|
+
await this.renderViewer({ Elements });
|
|
1328
|
+
}
|
|
1078
1329
|
}
|
|
1079
1330
|
},
|
|
1080
1331
|
|
|
@@ -1209,7 +1460,7 @@ const ObjectLayerEngineViewer = {
|
|
|
1209
1460
|
// Navigate to editor route first
|
|
1210
1461
|
setPath(`${getProxyPath()}object-layer-engine`);
|
|
1211
1462
|
// Then add query param without replacing history
|
|
1212
|
-
setQueryParams({
|
|
1463
|
+
setQueryParams({ id: objectLayer._id }, { replace: true });
|
|
1213
1464
|
|
|
1214
1465
|
if (s(`.modal-object-layer-engine`)) {
|
|
1215
1466
|
ObjectLayerEngineModal.Reload();
|
|
@@ -1221,22 +1472,22 @@ const ObjectLayerEngineViewer = {
|
|
|
1221
1472
|
Reload: async function (options = {}) {
|
|
1222
1473
|
const { Elements, force = false, skipWebp = false } = options;
|
|
1223
1474
|
const queryParams = getQueryParams();
|
|
1224
|
-
const
|
|
1475
|
+
const objectId = queryParams.id || null;
|
|
1225
1476
|
|
|
1226
|
-
// Only reload if
|
|
1227
|
-
if (
|
|
1228
|
-
if (
|
|
1477
|
+
// Only reload if object id actually changed (same logic as listener) or forced
|
|
1478
|
+
if (objectId !== this.Data.currentObjectId || force) {
|
|
1479
|
+
if (objectId !== this.Data.currentObjectId && !skipWebp) {
|
|
1229
1480
|
this.Data.webp = null;
|
|
1230
1481
|
this.Data.webpMetadata = null;
|
|
1231
1482
|
}
|
|
1232
|
-
this.Data.
|
|
1483
|
+
this.Data.currentObjectId = objectId;
|
|
1233
1484
|
|
|
1234
|
-
if (
|
|
1235
|
-
await this.loadObjectLayer(
|
|
1485
|
+
if (objectId) {
|
|
1486
|
+
await this.loadObjectLayer(objectId, Elements, { skipWebp });
|
|
1236
1487
|
} else {
|
|
1237
1488
|
await this.renderEmpty({ Elements });
|
|
1238
1489
|
}
|
|
1239
|
-
} else if (!
|
|
1490
|
+
} else if (!objectId && (this.Data.currentObjectId === null || force)) {
|
|
1240
1491
|
// Special case: if we're already in empty state but DOM might have been reset
|
|
1241
1492
|
// (e.g., modal reopened), force render the table if DOM is missing
|
|
1242
1493
|
const id = 'object-layer-engine-viewer';
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Auth } from '../../components/core/Auth.js';
|
|
2
|
+
import { loggerFactory } from '../../components/core/Logger.js';
|
|
3
|
+
import { getApiBaseUrl, headersFactory, payloadFactory, buildQueryUrl } from '../core/core.service.js';
|
|
4
|
+
|
|
5
|
+
const logger = loggerFactory(import.meta);
|
|
6
|
+
|
|
7
|
+
logger.info('Load service');
|
|
8
|
+
|
|
9
|
+
const endpoint = 'ipfs';
|
|
10
|
+
|
|
11
|
+
const IpfsService = {
|
|
12
|
+
post: (options = { id: '', body: {} }) =>
|
|
13
|
+
new Promise((resolve, reject) =>
|
|
14
|
+
fetch(getApiBaseUrl({ id: options.id, endpoint }), {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: headersFactory(),
|
|
17
|
+
credentials: 'include',
|
|
18
|
+
body: payloadFactory(options.body),
|
|
19
|
+
})
|
|
20
|
+
.then(async (res) => {
|
|
21
|
+
return await res.json();
|
|
22
|
+
})
|
|
23
|
+
.then((res) => {
|
|
24
|
+
logger.info(res);
|
|
25
|
+
return resolve(res);
|
|
26
|
+
})
|
|
27
|
+
.catch((error) => {
|
|
28
|
+
logger.error(error);
|
|
29
|
+
return reject(error);
|
|
30
|
+
}),
|
|
31
|
+
),
|
|
32
|
+
put: (options = { id: '', body: {} }) =>
|
|
33
|
+
new Promise((resolve, reject) =>
|
|
34
|
+
fetch(getApiBaseUrl({ id: options.id, endpoint }), {
|
|
35
|
+
method: 'PUT',
|
|
36
|
+
headers: headersFactory(),
|
|
37
|
+
credentials: 'include',
|
|
38
|
+
body: payloadFactory(options.body),
|
|
39
|
+
})
|
|
40
|
+
.then(async (res) => {
|
|
41
|
+
return await res.json();
|
|
42
|
+
})
|
|
43
|
+
.then((res) => {
|
|
44
|
+
logger.info(res);
|
|
45
|
+
return resolve(res);
|
|
46
|
+
})
|
|
47
|
+
.catch((error) => {
|
|
48
|
+
logger.error(error);
|
|
49
|
+
return reject(error);
|
|
50
|
+
}),
|
|
51
|
+
),
|
|
52
|
+
get: (options = {}) => {
|
|
53
|
+
const { id, page, limit, filterModel, sortModel, sort, asc, order } = options;
|
|
54
|
+
const url = buildQueryUrl(getApiBaseUrl({ id, endpoint }), {
|
|
55
|
+
page,
|
|
56
|
+
limit,
|
|
57
|
+
filterModel,
|
|
58
|
+
sortModel,
|
|
59
|
+
sort,
|
|
60
|
+
asc,
|
|
61
|
+
order,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return new Promise((resolve, reject) =>
|
|
65
|
+
fetch(url.toString(), {
|
|
66
|
+
method: 'GET',
|
|
67
|
+
headers: headersFactory(),
|
|
68
|
+
credentials: 'include',
|
|
69
|
+
})
|
|
70
|
+
.then(async (res) => {
|
|
71
|
+
return await res.json();
|
|
72
|
+
})
|
|
73
|
+
.then((res) => {
|
|
74
|
+
logger.info(res);
|
|
75
|
+
return resolve(res);
|
|
76
|
+
})
|
|
77
|
+
.catch((error) => {
|
|
78
|
+
logger.error(error);
|
|
79
|
+
return reject(error);
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
},
|
|
83
|
+
delete: (options = { id: '', body: {} }) =>
|
|
84
|
+
new Promise((resolve, reject) =>
|
|
85
|
+
fetch(getApiBaseUrl({ id: options.id, endpoint }), {
|
|
86
|
+
method: 'DELETE',
|
|
87
|
+
headers: headersFactory(),
|
|
88
|
+
credentials: 'include',
|
|
89
|
+
body: payloadFactory(options.body),
|
|
90
|
+
})
|
|
91
|
+
.then(async (res) => {
|
|
92
|
+
return await res.json();
|
|
93
|
+
})
|
|
94
|
+
.then((res) => {
|
|
95
|
+
logger.info(res);
|
|
96
|
+
return resolve(res);
|
|
97
|
+
})
|
|
98
|
+
.catch((error) => {
|
|
99
|
+
logger.error(error);
|
|
100
|
+
return reject(error);
|
|
101
|
+
}),
|
|
102
|
+
),
|
|
103
|
+
pin: (options = { body: {} }) =>
|
|
104
|
+
new Promise((resolve, reject) =>
|
|
105
|
+
fetch(`${getApiBaseUrl({ endpoint })}/pin`, {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers: headersFactory(),
|
|
108
|
+
credentials: 'include',
|
|
109
|
+
body: payloadFactory(options.body),
|
|
110
|
+
})
|
|
111
|
+
.then(async (res) => {
|
|
112
|
+
return await res.json();
|
|
113
|
+
})
|
|
114
|
+
.then((res) => {
|
|
115
|
+
logger.info(res);
|
|
116
|
+
return resolve(res);
|
|
117
|
+
})
|
|
118
|
+
.catch((error) => {
|
|
119
|
+
logger.error(error);
|
|
120
|
+
return reject(error);
|
|
121
|
+
}),
|
|
122
|
+
),
|
|
123
|
+
unpin: (options = { cid: '' }) =>
|
|
124
|
+
new Promise((resolve, reject) =>
|
|
125
|
+
fetch(`${getApiBaseUrl({ endpoint })}/pin/${options.cid}`, {
|
|
126
|
+
method: 'DELETE',
|
|
127
|
+
headers: headersFactory(),
|
|
128
|
+
credentials: 'include',
|
|
129
|
+
})
|
|
130
|
+
.then(async (res) => {
|
|
131
|
+
return await res.json();
|
|
132
|
+
})
|
|
133
|
+
.then((res) => {
|
|
134
|
+
logger.info(res);
|
|
135
|
+
return resolve(res);
|
|
136
|
+
})
|
|
137
|
+
.catch((error) => {
|
|
138
|
+
logger.error(error);
|
|
139
|
+
return reject(error);
|
|
140
|
+
}),
|
|
141
|
+
),
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export { IpfsService };
|