cyberia 3.1.3 → 3.2.5

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.
Files changed (208) hide show
  1. package/.env.example +0 -2
  2. package/.github/workflows/engine-cyberia.cd.yml +10 -8
  3. package/.github/workflows/engine-cyberia.ci.yml +12 -29
  4. package/.github/workflows/ghpkg.ci.yml +4 -4
  5. package/.github/workflows/npmpkg.ci.yml +28 -11
  6. package/.github/workflows/publish.ci.yml +21 -2
  7. package/.github/workflows/pwa-microservices-template-page.cd.yml +4 -5
  8. package/.github/workflows/pwa-microservices-template-test.ci.yml +3 -3
  9. package/.github/workflows/release.cd.yml +13 -8
  10. package/CHANGELOG.md +433 -1
  11. package/CLI-HELP.md +57 -7
  12. package/Dockerfile +4 -2
  13. package/README.md +347 -22
  14. package/bin/build.js +5 -2
  15. package/bin/cyberia.js +1789 -112
  16. package/bin/deploy.js +177 -124
  17. package/bin/file.js +3 -0
  18. package/bin/index.js +1789 -112
  19. package/conf.js +64 -8
  20. package/deployment.yaml +92 -20
  21. package/hardhat/hardhat.config.js +13 -13
  22. package/hardhat/ignition/modules/ObjectLayerToken.js +1 -1
  23. package/hardhat/package-lock.json +2554 -5859
  24. package/hardhat/package.json +13 -22
  25. package/hardhat/scripts/deployObjectLayerToken.js +1 -1
  26. package/hardhat/test/ObjectLayerToken.js +4 -2
  27. package/hardhat/types/ethers-contracts/ObjectLayerToken.ts +690 -0
  28. package/hardhat/types/ethers-contracts/common.ts +92 -0
  29. package/hardhat/types/ethers-contracts/factories/ObjectLayerToken__factory.ts +1055 -0
  30. package/hardhat/types/ethers-contracts/factories/index.ts +4 -0
  31. package/hardhat/types/ethers-contracts/hardhat.d.ts +47 -0
  32. package/hardhat/types/ethers-contracts/index.ts +6 -0
  33. package/jsdoc.dd-cyberia.json +64 -55
  34. package/jsdoc.json +64 -55
  35. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +5 -4
  36. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +5 -4
  37. package/manifests/deployment/dd-cyberia-development/deployment.yaml +92 -20
  38. package/manifests/deployment/dd-cyberia-development/proxy.yaml +54 -18
  39. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  40. package/manifests/deployment/dd-test-development/deployment.yaml +88 -74
  41. package/manifests/deployment/dd-test-development/proxy.yaml +13 -4
  42. package/manifests/deployment/playwright/deployment.yaml +1 -1
  43. package/nodemon.json +1 -1
  44. package/package.json +22 -16
  45. package/proxy.yaml +54 -18
  46. package/scripts/rhel-grpc-setup.sh +56 -0
  47. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +44 -0
  48. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +16 -0
  49. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +5 -0
  50. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +80 -7
  51. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +93 -0
  52. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +36 -0
  53. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +29 -0
  54. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +51 -0
  55. package/src/api/cyberia-entity/cyberia-entity.controller.js +74 -0
  56. package/src/api/cyberia-entity/cyberia-entity.model.js +24 -0
  57. package/src/api/cyberia-entity/cyberia-entity.router.js +27 -0
  58. package/src/api/cyberia-entity/cyberia-entity.service.js +42 -0
  59. package/src/api/cyberia-instance/cyberia-fallback-world.js +368 -0
  60. package/src/api/cyberia-instance/cyberia-instance.controller.js +92 -0
  61. package/src/api/cyberia-instance/cyberia-instance.model.js +84 -0
  62. package/src/api/cyberia-instance/cyberia-instance.router.js +63 -0
  63. package/src/api/cyberia-instance/cyberia-instance.service.js +191 -0
  64. package/src/api/cyberia-instance/cyberia-portal-connector.js +486 -0
  65. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +74 -0
  66. package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +413 -0
  67. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +228 -0
  68. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +27 -0
  69. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +42 -0
  70. package/src/api/cyberia-map/cyberia-map.controller.js +79 -0
  71. package/src/api/cyberia-map/cyberia-map.model.js +30 -0
  72. package/src/api/cyberia-map/cyberia-map.router.js +40 -0
  73. package/src/api/cyberia-map/cyberia-map.service.js +74 -0
  74. package/src/api/file/file.ref.json +18 -0
  75. package/src/api/ipfs/ipfs.controller.js +4 -25
  76. package/src/api/ipfs/ipfs.model.js +43 -34
  77. package/src/api/ipfs/ipfs.router.js +8 -13
  78. package/src/api/ipfs/ipfs.service.js +54 -102
  79. package/src/api/object-layer/README.md +347 -22
  80. package/src/api/object-layer/object-layer.router.js +30 -0
  81. package/src/api/object-layer/object-layer.service.js +114 -31
  82. package/src/api/user/user.service.js +8 -7
  83. package/src/cli/cluster.js +7 -7
  84. package/src/cli/db.js +710 -827
  85. package/src/cli/deploy.js +151 -93
  86. package/src/cli/env.js +29 -0
  87. package/src/cli/fs.js +5 -2
  88. package/src/cli/index.js +48 -2
  89. package/src/cli/kubectl.js +211 -0
  90. package/src/cli/release.js +284 -0
  91. package/src/cli/repository.js +438 -75
  92. package/src/cli/run.js +195 -35
  93. package/src/cli/secrets.js +73 -0
  94. package/src/cli/test.js +3 -3
  95. package/src/client/Cryptokoyn.index.js +3 -4
  96. package/src/client/CyberiaPortal.index.js +3 -4
  97. package/src/client/Default.index.js +3 -4
  98. package/src/client/Itemledger.index.js +3 -4
  99. package/src/client/Underpost.index.js +3 -4
  100. package/src/client/components/core/AppStore.js +69 -0
  101. package/src/client/components/core/CalendarCore.js +2 -2
  102. package/src/client/components/core/DropDown.js +137 -17
  103. package/src/client/components/core/Keyboard.js +2 -2
  104. package/src/client/components/core/LogIn.js +2 -2
  105. package/src/client/components/core/LogOut.js +2 -2
  106. package/src/client/components/core/Modal.js +0 -1
  107. package/src/client/components/core/Panel.js +0 -1
  108. package/src/client/components/core/PanelForm.js +19 -19
  109. package/src/client/components/core/SocketIo.js +82 -29
  110. package/src/client/components/core/SocketIoHandler.js +75 -0
  111. package/src/client/components/core/Stream.js +143 -95
  112. package/src/client/components/core/Webhook.js +40 -7
  113. package/src/client/components/cryptokoyn/AppStoreCryptokoyn.js +5 -0
  114. package/src/client/components/cryptokoyn/LogInCryptokoyn.js +3 -3
  115. package/src/client/components/cryptokoyn/LogOutCryptokoyn.js +2 -2
  116. package/src/client/components/cryptokoyn/MenuCryptokoyn.js +3 -3
  117. package/src/client/components/cryptokoyn/SocketIoCryptokoyn.js +3 -51
  118. package/src/client/components/cyberia/InstanceEngineCyberia.js +700 -0
  119. package/src/client/components/cyberia/MapEngineCyberia.js +1359 -2
  120. package/src/client/components/cyberia/ObjectLayerEngineModal.js +17 -6
  121. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +92 -54
  122. package/src/client/components/cyberia-portal/AppStoreCyberiaPortal.js +5 -0
  123. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +216 -30
  124. package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +3 -3
  125. package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +2 -2
  126. package/src/client/components/cyberia-portal/MenuCyberiaPortal.js +40 -7
  127. package/src/client/components/cyberia-portal/RoutesCyberiaPortal.js +4 -0
  128. package/src/client/components/cyberia-portal/SocketIoCyberiaPortal.js +3 -49
  129. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
  130. package/src/client/components/default/AppStoreDefault.js +5 -0
  131. package/src/client/components/default/LogInDefault.js +3 -3
  132. package/src/client/components/default/LogOutDefault.js +2 -2
  133. package/src/client/components/default/MenuDefault.js +5 -5
  134. package/src/client/components/default/SocketIoDefault.js +3 -51
  135. package/src/client/components/itemledger/AppStoreItemledger.js +5 -0
  136. package/src/client/components/itemledger/LogInItemledger.js +3 -3
  137. package/src/client/components/itemledger/LogOutItemledger.js +2 -2
  138. package/src/client/components/itemledger/MenuItemledger.js +3 -3
  139. package/src/client/components/itemledger/SocketIoItemledger.js +3 -51
  140. package/src/client/components/underpost/AppStoreUnderpost.js +5 -0
  141. package/src/client/components/underpost/LogInUnderpost.js +3 -3
  142. package/src/client/components/underpost/LogOutUnderpost.js +2 -2
  143. package/src/client/components/underpost/MenuUnderpost.js +5 -5
  144. package/src/client/components/underpost/SocketIoUnderpost.js +3 -51
  145. package/src/client/services/core/core.service.js +20 -8
  146. package/src/client/services/cyberia-dialogue/cyberia-dialogue.service.js +105 -0
  147. package/src/client/services/cyberia-entity/cyberia-entity.management.js +57 -0
  148. package/src/client/services/cyberia-entity/cyberia-entity.service.js +105 -0
  149. package/src/client/services/cyberia-instance/cyberia-instance.management.js +194 -0
  150. package/src/client/services/cyberia-instance/cyberia-instance.service.js +122 -0
  151. package/src/client/services/cyberia-instance-conf/cyberia-instance-conf.service.js +105 -0
  152. package/src/client/services/cyberia-map/cyberia-map.management.js +193 -0
  153. package/src/client/services/cyberia-map/cyberia-map.service.js +126 -0
  154. package/src/client/services/instance/instance.management.js +2 -2
  155. package/src/client/services/ipfs/ipfs.service.js +3 -23
  156. package/src/client/services/object-layer/object-layer.management.js +3 -3
  157. package/src/client/services/object-layer/object-layer.service.js +21 -0
  158. package/src/client/services/user/user.management.js +2 -2
  159. package/src/client/ssr/pages/CyberiaServerMetrics.js +1 -1
  160. package/src/grpc/cyberia/OFF_CHAIN_ECONOMY.md +305 -0
  161. package/src/grpc/cyberia/README.md +326 -0
  162. package/src/grpc/cyberia/grpc-server.js +530 -0
  163. package/src/index.js +24 -1
  164. package/src/runtime/express/Dockerfile +4 -0
  165. package/src/runtime/express/Express.js +18 -1
  166. package/src/runtime/lampp/Dockerfile +13 -2
  167. package/src/runtime/lampp/Lampp.js +27 -4
  168. package/src/runtime/wp/Dockerfile +68 -0
  169. package/src/runtime/wp/Wp.js +639 -0
  170. package/src/server/auth.js +24 -1
  171. package/src/server/backup.js +37 -9
  172. package/src/server/client-build-docs.js +9 -2
  173. package/src/server/client-build.js +31 -31
  174. package/src/server/client-formatted.js +109 -57
  175. package/src/server/conf.js +24 -9
  176. package/src/server/cron.js +25 -23
  177. package/src/server/dns.js +2 -1
  178. package/src/server/ipfs-client.js +24 -1
  179. package/src/server/object-layer.js +149 -108
  180. package/src/server/peer.js +8 -0
  181. package/src/server/runtime.js +25 -1
  182. package/src/server/semantic-layer-generator-floor.js +359 -0
  183. package/src/server/semantic-layer-generator-skin.js +1294 -0
  184. package/src/server/semantic-layer-generator.js +116 -555
  185. package/src/server/start.js +2 -2
  186. package/src/ws/IoInterface.js +1 -10
  187. package/src/ws/IoServer.js +14 -33
  188. package/src/ws/core/channels/core.ws.chat.js +65 -20
  189. package/src/ws/core/channels/core.ws.mailer.js +113 -32
  190. package/src/ws/core/channels/core.ws.stream.js +90 -31
  191. package/src/ws/core/core.ws.connection.js +12 -33
  192. package/src/ws/core/core.ws.emit.js +10 -26
  193. package/src/ws/core/core.ws.server.js +25 -58
  194. package/src/ws/default/channels/default.ws.main.js +53 -12
  195. package/src/ws/default/default.ws.connection.js +26 -13
  196. package/src/ws/default/default.ws.server.js +30 -12
  197. package/src/client/components/cryptokoyn/CommonCryptokoyn.js +0 -29
  198. package/src/client/components/cryptokoyn/ElementsCryptokoyn.js +0 -38
  199. package/src/client/components/cyberia-portal/ElementsCyberiaPortal.js +0 -38
  200. package/src/client/components/default/ElementsDefault.js +0 -38
  201. package/src/client/components/itemledger/CommonItemledger.js +0 -29
  202. package/src/client/components/itemledger/ElementsItemledger.js +0 -38
  203. package/src/client/components/underpost/CommonUnderpost.js +0 -29
  204. package/src/client/components/underpost/ElementsUnderpost.js +0 -38
  205. package/src/ws/core/management/core.ws.chat.js +0 -8
  206. package/src/ws/core/management/core.ws.mailer.js +0 -16
  207. package/src/ws/core/management/core.ws.stream.js +0 -8
  208. package/src/ws/default/management/default.ws.main.js +0 -8
@@ -1,6 +1,1363 @@
1
+ import { BtnIcon } from '../core/BtnIcon.js';
2
+ import { Input, InputFile, getFileFromBlobEndpoint } from '../core/Input.js';
3
+ import { htmls, s } from '../core/VanillaJs.js';
4
+ import { NotificationManager } from '../core/NotificationManager.js';
5
+ import { Translate } from '../core/Translate.js';
6
+ import { darkTheme, dynamicCol, ThemeEvents } from '../core/Css.js';
7
+ import { DropDown } from '../core/DropDown.js';
8
+ import { ToggleSwitch } from '../core/ToggleSwitch.js';
9
+ import { CyberiaMapManagement } from '../../services/cyberia-map/cyberia-map.management.js';
10
+ import { CyberiaMapService } from '../../services/cyberia-map/cyberia-map.service.js';
11
+ import { FileService } from '../../services/file/file.service.js';
12
+ import { DefaultManagement } from '../../services/default/default.management.js';
13
+ import { getApiBaseUrl } from '../../services/core/core.service.js';
14
+ import { ObjectLayerService } from '../../services/object-layer/object-layer.service.js';
15
+ import { getProxyPath } from '../core/Router.js';
16
+
1
17
  class MapEngineCyberia {
2
- static async render() {
3
- return html`<div>Map Engine Cyberia Component</div>`;
18
+ static entities = [];
19
+ static currentMapId = null;
20
+ static currentThumbnailId = null;
21
+ static thumbnailDirty = false;
22
+ static loadMap = null;
23
+ static showGridBorders = true;
24
+ static addOnClick = true;
25
+ static showObjectLayers = false;
26
+ static randomDim = false;
27
+ static captureObjLayerThumbnail = true;
28
+ static imageCache = {};
29
+
30
+ static loadObjectLayerImage(itemId, onLoad) {
31
+ if (MapEngineCyberia.imageCache[itemId]) return;
32
+ MapEngineCyberia.imageCache[itemId] = { img: null, loaded: false, error: false };
33
+ ObjectLayerService.get({
34
+ limit: 1,
35
+ filterModel: { 'data.item.id': { filterType: 'text', type: 'equals', filter: itemId } },
36
+ })
37
+ .then((res) => {
38
+ const doc = res?.data?.data?.[0];
39
+ if (!doc || !doc.data?.item?.type || !doc.data?.item?.id) {
40
+ MapEngineCyberia.imageCache[itemId].error = true;
41
+ return;
42
+ }
43
+ const { type, id } = doc.data.item;
44
+ const img = new Image();
45
+ img.onload = () => {
46
+ MapEngineCyberia.imageCache[itemId].img = img;
47
+ MapEngineCyberia.imageCache[itemId].loaded = true;
48
+ if (onLoad) onLoad();
49
+ };
50
+ img.onerror = () => {
51
+ MapEngineCyberia.imageCache[itemId].error = true;
52
+ };
53
+ img.src = `${getProxyPath()}assets/${type}/${id}/08/0.png`;
54
+ })
55
+ .catch(() => {
56
+ MapEngineCyberia.imageCache[itemId].error = true;
57
+ });
58
+ }
59
+
60
+ static renderGrid(canvas, cols, rows, cellW, cellH, showGrid = true) {
61
+ canvas.width = cols * cellW;
62
+ canvas.height = rows * cellH;
63
+ const ctx = canvas.getContext('2d');
64
+
65
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
66
+
67
+ // Draw entities
68
+ for (const entity of MapEngineCyberia.entities) {
69
+ const x = entity.initCellX * cellW;
70
+ const y = entity.initCellY * cellH;
71
+ const w = entity.dimX * cellW;
72
+ const h = entity.dimY * cellH;
73
+
74
+ if (MapEngineCyberia.showObjectLayers && entity.objectLayerItemIds?.length) {
75
+ for (const itemId of entity.objectLayerItemIds) {
76
+ const cached = MapEngineCyberia.imageCache[itemId];
77
+ if (cached?.loaded && cached.img) {
78
+ ctx.drawImage(cached.img, x, y, w, h);
79
+ }
80
+ }
81
+ } else {
82
+ ctx.fillStyle = entity.color;
83
+ ctx.fillRect(x, y, w, h);
84
+ }
85
+ }
86
+
87
+ // Draw grid lines on top
88
+ if (showGrid) {
89
+ ctx.strokeStyle = '#aaa';
90
+ ctx.lineWidth = 1;
91
+
92
+ for (let r = 0; r < rows; r++) {
93
+ for (let c = 0; c < cols; c++) {
94
+ ctx.strokeRect(c * cellW, r * cellH, cellW, cellH);
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ static renderToOffscreenCanvas(cols, rows, cellW, cellH, { forceObjectLayers = false } = {}) {
101
+ const offscreen = document.createElement('canvas');
102
+ offscreen.width = cols * cellW;
103
+ offscreen.height = rows * cellH;
104
+ const ctx = offscreen.getContext('2d');
105
+ ctx.clearRect(0, 0, offscreen.width, offscreen.height);
106
+ const useObjectLayers = forceObjectLayers || MapEngineCyberia.showObjectLayers;
107
+ for (const entity of MapEngineCyberia.entities) {
108
+ const x = entity.initCellX * cellW;
109
+ const y = entity.initCellY * cellH;
110
+ const w = entity.dimX * cellW;
111
+ const h = entity.dimY * cellH;
112
+
113
+ if (useObjectLayers && entity.objectLayerItemIds?.length) {
114
+ for (const itemId of entity.objectLayerItemIds) {
115
+ const cached = MapEngineCyberia.imageCache[itemId];
116
+ if (cached?.loaded && cached.img) {
117
+ ctx.drawImage(cached.img, x, y, w, h);
118
+ }
119
+ }
120
+ } else {
121
+ ctx.fillStyle = entity.color;
122
+ ctx.fillRect(x, y, w, h);
123
+ }
124
+ }
125
+ return offscreen;
126
+ }
127
+
128
+ static renderEntityList(containerId) {
129
+ const container = s(`.${containerId}`);
130
+ if (!container) return;
131
+
132
+ const filterType = s('.map-engine-filter-entity-type')?.value?.trim().toLowerCase() || '';
133
+ const filterX = s('.map-engine-filter-init-x')?.value?.trim() || '';
134
+ const filterY = s('.map-engine-filter-init-y')?.value?.trim() || '';
135
+
136
+ const filtered = [];
137
+ MapEngineCyberia.entities.forEach((entity, i) => {
138
+ if (filterType && !(entity.entityType || '').toLowerCase().includes(filterType)) return;
139
+ if (filterX !== '' && !String(entity.initCellX).includes(filterX)) return;
140
+ if (filterY !== '' && !String(entity.initCellY).includes(filterY)) return;
141
+ filtered.push({ entity, i });
142
+ });
143
+
144
+ let html = '';
145
+ filtered.forEach(({ entity, i }) => {
146
+ const layerTags = (entity.objectLayerItemIds || [])
147
+ .map(
148
+ (id) =>
149
+ html`<div
150
+ class="badge inl"
151
+ style="background:${darkTheme ? '#335' : '#cde'};color:${darkTheme
152
+ ? '#adf'
153
+ : '#246'};border-radius:4px;font-size:11px;height:auto;min-width:auto;margin:1px 2px;"
154
+ >
155
+ <div class="badge-text"><i class="fa-solid fa-tag" style="margin-right:3px;font-size:9px;"></i>${id}</div>
156
+ </div>`,
157
+ )
158
+ .join('');
159
+ html += html`<div class="fl" style="border-bottom:1px solid #444; padding:4px 0; align-items:center;">
160
+ <div
161
+ class="in fll"
162
+ style="width:20px;height:20px;background:${entity.color};border:1px solid #888;margin-right:6px;"
163
+ ></div>
164
+ <div class="in fll" style="flex:1;font-size:12px;font-family:monospace;">
165
+ ${entity.entityType} (${entity.initCellX},${entity.initCellY}) ${entity.dimX}x${entity.dimY}
166
+ ${layerTags ? html`<div style="margin-top:2px;">${layerTags}</div>` : ''}
167
+ </div>
168
+ <div class="in fll" style="display:flex;gap:3px;">
169
+ <button
170
+ class="btn-map-engine-load-entity-values"
171
+ data-index="${i}"
172
+ style="cursor:pointer;background:#36a;color:#fff;border:none;padding:2px 8px;font-size:12px;"
173
+ >
174
+ <i class="fa-solid fa-clone"></i>
175
+ </button>
176
+ <button
177
+ class="btn-map-engine-remove-entity"
178
+ data-index="${i}"
179
+ style="cursor:pointer;background:#a00;color:#fff;border:none;padding:2px 8px;font-size:12px;"
180
+ >
181
+ <i class="fa-solid fa-trash"></i>
182
+ </button>
183
+ </div>
184
+ </div>`;
185
+ });
186
+ if (!html)
187
+ html = `<div style="color:#888;font-size:13px;">${MapEngineCyberia.entities.length > 0 ? 'No matching entities.' : 'No entities added yet.'}</div>`;
188
+ htmls(`.${containerId}`, html);
189
+
190
+ container.querySelectorAll('.btn-map-engine-remove-entity').forEach((btn) => {
191
+ btn.onclick = () => {
192
+ const idx = parseInt(btn.dataset.index);
193
+ MapEngineCyberia.entities.splice(idx, 1);
194
+ MapEngineCyberia.renderEntityList(containerId);
195
+ const canvasEl = s('.map-engine-canvas');
196
+ if (canvasEl) {
197
+ const cols = parseInt(s('.map-engine-input-x')?.value) || 16;
198
+ const rows = parseInt(s('.map-engine-input-y')?.value) || 16;
199
+ const cellW = parseInt(s('.map-engine-input-cell-w')?.value) || 32;
200
+ const cellH = parseInt(s('.map-engine-input-cell-h')?.value) || 32;
201
+ MapEngineCyberia.renderGrid(canvasEl, cols, rows, cellW, cellH, MapEngineCyberia.showGridBorders);
202
+ }
203
+ };
204
+ });
205
+
206
+ container.querySelectorAll('.btn-map-engine-load-entity-values').forEach((btn) => {
207
+ btn.onclick = () => {
208
+ const idx = parseInt(btn.dataset.index);
209
+ const entity = MapEngineCyberia.entities[idx];
210
+ if (!entity) return;
211
+
212
+ if (s('.map-engine-entity-type')) s('.map-engine-entity-type').value = entity.entityType || 'floor';
213
+ if (s('.map-engine-init-cell-x')) s('.map-engine-init-cell-x').value = entity.initCellX || 0;
214
+ if (s('.map-engine-init-cell-y')) s('.map-engine-init-cell-y').value = entity.initCellY || 0;
215
+ if (s('.map-engine-dim-x')) s('.map-engine-dim-x').value = entity.dimX || 1;
216
+ if (s('.map-engine-dim-y')) s('.map-engine-dim-y').value = entity.dimY || 1;
217
+
218
+ // Parse rgba color back to hex + alpha
219
+ const rgbaMatch = (entity.color || '').match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\)/);
220
+ if (rgbaMatch) {
221
+ const r = parseInt(rgbaMatch[1]).toString(16).padStart(2, '0');
222
+ const g = parseInt(rgbaMatch[2]).toString(16).padStart(2, '0');
223
+ const b = parseInt(rgbaMatch[3]).toString(16).padStart(2, '0');
224
+ const alpha = rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) : 1;
225
+ if (s('.map-engine-color')) s('.map-engine-color').value = `#${r}${g}${b}`;
226
+ if (s('.map-engine-alpha')) {
227
+ s('.map-engine-alpha').value = alpha;
228
+ s('.map-engine-alpha').dispatchEvent(new Event('input'));
229
+ }
230
+ if (s('.map-engine-color')) s('.map-engine-color').dispatchEvent(new Event('input'));
231
+ }
232
+
233
+ // Load object layer item IDs into the dropdown
234
+ const ddId = 'map-engine-obj-layer-dropdown';
235
+ if (DropDown.Tokens[ddId]) {
236
+ DropDown.Tokens[ddId].oncheckvalues = {};
237
+ const itemIds = entity.objectLayerItemIds || [];
238
+ for (const itemId of itemIds) {
239
+ const key = itemId.trim().replaceAll(' ', '-');
240
+ DropDown.Tokens[ddId].oncheckvalues[key] = { data: itemId, display: itemId, value: itemId };
241
+ }
242
+ DropDown.Tokens[ddId].value = itemIds;
243
+ if (s(`.${ddId}`)) s(`.${ddId}`).value = itemIds;
244
+ // Trigger badge re-render
245
+ if (s(`.dropdown-current-${ddId}`)) {
246
+ DropDown.Tokens[ddId]._renderSelectedBadges?.();
247
+ }
248
+ }
249
+ };
250
+ });
251
+ }
252
+
253
+ static async render(options = {}) {
254
+ const { appStore } = options;
255
+ const idCode = 'map-engine-input-code';
256
+ const idName = 'map-engine-input-name';
257
+ const idDescription = 'map-engine-input-description';
258
+ const idTags = 'map-engine-input-tags';
259
+ const idStatus = 'map-engine-input-status';
260
+ const idThumbnail = 'map-engine-input-thumbnail';
261
+ const idCreator = 'map-engine-input-creator';
262
+
263
+ const idX = 'map-engine-input-x';
264
+ const idY = 'map-engine-input-y';
265
+ const idCellW = 'map-engine-input-cell-w';
266
+ const idCellH = 'map-engine-input-cell-h';
267
+ const canvasId = 'map-engine-canvas';
268
+
269
+ const idEntityType = 'map-engine-entity-type';
270
+ const idInitCellX = 'map-engine-init-cell-x';
271
+ const idInitCellY = 'map-engine-init-cell-y';
272
+ const idDimX = 'map-engine-dim-x';
273
+ const idDimY = 'map-engine-dim-y';
274
+ const idColor = 'map-engine-color';
275
+ const idAlpha = 'map-engine-alpha';
276
+ const idFactorA = 'map-engine-factor-a';
277
+ const idFactorB = 'map-engine-factor-b';
278
+ const idVariationPreserve = 'map-engine-variation-preserve';
279
+ const rgbaDisplayId = 'map-engine-rgba-display';
280
+ const entityListId = 'map-engine-entity-list';
281
+ const idObjLayerDropdown = 'map-engine-obj-layer-dropdown';
282
+ const managementId = 'modal-cyberia-map-engine';
283
+
284
+ MapEngineCyberia.entities = [];
285
+ MapEngineCyberia.currentMapId = null;
286
+ MapEngineCyberia.currentThumbnailId = null;
287
+
288
+ const hexToRgba = (hex, alpha) => {
289
+ const r = parseInt(hex.slice(1, 3), 16);
290
+ const g = parseInt(hex.slice(3, 5), 16);
291
+ const b = parseInt(hex.slice(5, 7), 16);
292
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
293
+ };
294
+
295
+ const getCanvasParams = () => ({
296
+ cols: parseInt(s(`.${idX}`)?.value) || 16,
297
+ rows: parseInt(s(`.${idY}`)?.value) || 16,
298
+ cellW: parseInt(s(`.${idCellW}`)?.value) || 32,
299
+ cellH: parseInt(s(`.${idCellH}`)?.value) || 32,
300
+ });
301
+
302
+ const rerenderCanvas = () => {
303
+ const canvas = s(`.${canvasId}`);
304
+ if (!canvas) return;
305
+ const { cols, rows, cellW, cellH } = getCanvasParams();
306
+ MapEngineCyberia.renderGrid(canvas, cols, rows, cellW, cellH, MapEngineCyberia.showGridBorders);
307
+ };
308
+
309
+ const getEntityParams = () => {
310
+ const hex = s(`.${idColor}`)?.value || '#ff0000';
311
+ const alpha = parseFloat(s(`.${idAlpha}`)?.value);
312
+ return {
313
+ entityType: s(`.${idEntityType}`)?.value || 'floor',
314
+ initCellX: parseInt(s(`.${idInitCellX}`)?.value) || 0,
315
+ initCellY: parseInt(s(`.${idInitCellY}`)?.value) || 0,
316
+ dimX: parseInt(s(`.${idDimX}`)?.value) || 1,
317
+ dimY: parseInt(s(`.${idDimY}`)?.value) || 1,
318
+ color: hexToRgba(hex, alpha),
319
+ };
320
+ };
321
+
322
+ const applyRandomDim = (ep) => {
323
+ if (!MapEngineCyberia.randomDim) return;
324
+ const a = parseFloat(s(`.${idFactorA}`)?.value) || 0.5;
325
+ const b = parseFloat(s(`.${idFactorB}`)?.value) || 1.5;
326
+ const min = Math.min(a, b);
327
+ const max = Math.max(a, b);
328
+ const factor = min + Math.random() * (max - min);
329
+ ep.dimX = Math.max(1, Math.round(ep.dimX * factor));
330
+ ep.dimY = Math.max(1, Math.round(ep.dimY * factor));
331
+ };
332
+
333
+ const addEntityLocally = () => {
334
+ const ep = getEntityParams();
335
+ ep.objectLayerItemIds = DropDown.Tokens[idObjLayerDropdown]?.value
336
+ ? [...DropDown.Tokens[idObjLayerDropdown].value]
337
+ : [];
338
+ applyRandomDim(ep);
339
+ MapEngineCyberia.entities.push(ep);
340
+ for (const itemId of ep.objectLayerItemIds) {
341
+ MapEngineCyberia.loadObjectLayerImage(itemId, rerenderCanvas);
342
+ }
343
+ MapEngineCyberia.renderEntityList(entityListId);
344
+ rerenderCanvas();
345
+ };
346
+
347
+ const fillMapWithEntity = () => {
348
+ const ep = getEntityParams();
349
+ ep.objectLayerItemIds = DropDown.Tokens[idObjLayerDropdown]?.value
350
+ ? [...DropDown.Tokens[idObjLayerDropdown].value]
351
+ : [];
352
+ const { cols, rows } = getCanvasParams();
353
+ const dimX = ep.dimX || 1;
354
+ const dimY = ep.dimY || 1;
355
+ for (let r = 0; r < rows; r += dimY) {
356
+ for (let c = 0; c < cols; c += dimX) {
357
+ const tile = {
358
+ ...ep,
359
+ initCellX: c,
360
+ initCellY: r,
361
+ objectLayerItemIds: [...ep.objectLayerItemIds],
362
+ };
363
+ applyRandomDim(tile);
364
+ MapEngineCyberia.entities.push(tile);
365
+ }
366
+ }
367
+ for (const itemId of ep.objectLayerItemIds) {
368
+ MapEngineCyberia.loadObjectLayerImage(itemId, rerenderCanvas);
369
+ }
370
+ MapEngineCyberia.renderEntityList(entityListId);
371
+ rerenderCanvas();
372
+ };
373
+
374
+ const generateVariation = () => {
375
+ const a = parseFloat(s(`.${idFactorA}`)?.value) || 0.5;
376
+ const b = parseFloat(s(`.${idFactorB}`)?.value) || 1.5;
377
+ const min = Math.min(a, b);
378
+ const max = Math.max(a, b);
379
+ const preserveRaw = s(`.${idVariationPreserve}`)?.value || '';
380
+ const preserveSet = new Set(
381
+ preserveRaw
382
+ .split(',')
383
+ .map((t) => t.trim().toLowerCase())
384
+ .filter((t) => t),
385
+ );
386
+ const { cols, rows } = getCanvasParams();
387
+ for (const entity of MapEngineCyberia.entities) {
388
+ if (preserveSet.has((entity.entityType || '').toLowerCase())) continue;
389
+ const dimFactor = min + Math.random() * (max - min);
390
+ entity.dimX = Math.max(1, Math.round(entity.dimX * dimFactor));
391
+ entity.dimY = Math.max(1, Math.round(entity.dimY * dimFactor));
392
+ const posFactor = min + Math.random() * (max - min);
393
+ entity.initCellX = Math.max(0, Math.min(cols - 1, Math.round(entity.initCellX * posFactor)));
394
+ entity.initCellY = Math.max(0, Math.min(rows - 1, Math.round(entity.initCellY * posFactor)));
395
+ }
396
+ MapEngineCyberia.renderEntityList(entityListId);
397
+ rerenderCanvas();
398
+ };
399
+
400
+ const flipHorizontal = () => {
401
+ const { cols } = getCanvasParams();
402
+ for (const entity of MapEngineCyberia.entities) {
403
+ entity.initCellX = cols - entity.initCellX - entity.dimX;
404
+ }
405
+ MapEngineCyberia.renderEntityList(entityListId);
406
+ rerenderCanvas();
407
+ };
408
+
409
+ const flipVertical = () => {
410
+ const { rows } = getCanvasParams();
411
+ for (const entity of MapEngineCyberia.entities) {
412
+ entity.initCellY = rows - entity.initCellY - entity.dimY;
413
+ }
414
+ MapEngineCyberia.renderEntityList(entityListId);
415
+ rerenderCanvas();
416
+ };
417
+
418
+ const getMapPayload = () => {
419
+ const tagsRaw = s(`.${idTags}`)?.value || '';
420
+ const tags = tagsRaw
421
+ .split(',')
422
+ .map((t) => t.trim())
423
+ .filter((t) => t);
424
+ const { cols, rows, cellW, cellH } = getCanvasParams();
425
+ const payload = {
426
+ code: s(`.${idCode}`)?.value || '',
427
+ name: s(`.${idName}`)?.value || '',
428
+ description: s(`.${idDescription}`)?.value || '',
429
+ tags,
430
+ status: DropDown.Tokens[idStatus]?.value || 'unlisted',
431
+ entities: MapEngineCyberia.entities,
432
+ gridX: cols,
433
+ gridY: rows,
434
+ cellWidth: cellW,
435
+ cellHeight: cellH,
436
+ };
437
+ if (MapEngineCyberia.currentThumbnailId) payload.thumbnail = MapEngineCyberia.currentThumbnailId;
438
+ return payload;
439
+ };
440
+
441
+ const saveMap = async () => {
442
+ // Upload thumbnail file only if user selected a new one
443
+ const thumbnailInput = s(`.${idThumbnail}`);
444
+ if (
445
+ MapEngineCyberia.thumbnailDirty &&
446
+ thumbnailInput &&
447
+ thumbnailInput.files &&
448
+ thumbnailInput.files.length > 0
449
+ ) {
450
+ const formData = new FormData();
451
+ formData.append('file', thumbnailInput.files[0]);
452
+ const uploadResult = await FileService.post({ body: formData });
453
+ if (uploadResult.status === 'success' && uploadResult.data && uploadResult.data.length > 0) {
454
+ MapEngineCyberia.currentThumbnailId = uploadResult.data[0]._id;
455
+ } else {
456
+ NotificationManager.Push({
457
+ html: uploadResult.message || 'Failed to upload thumbnail',
458
+ status: 'error',
459
+ });
460
+ return;
461
+ }
462
+ }
463
+
464
+ // Capture object layer thumbnail on save/update if checkbox is checked
465
+ if (MapEngineCyberia.captureObjLayerThumbnail) {
466
+ const { cols, rows, cellW, cellH } = getCanvasParams();
467
+ const offscreen = MapEngineCyberia.renderToOffscreenCanvas(cols, rows, cellW, cellH, {
468
+ forceObjectLayers: true,
469
+ });
470
+ const blob = await new Promise((resolve) => offscreen.toBlob(resolve, 'image/png'));
471
+ if (blob) {
472
+ const file = new File([blob], 'map-thumbnail.png', { type: 'image/png' });
473
+ const formData = new FormData();
474
+ formData.append('file', file);
475
+ const uploadResult = await FileService.post({ body: formData });
476
+ if (uploadResult.status === 'success' && uploadResult.data?.length > 0) {
477
+ MapEngineCyberia.currentThumbnailId = uploadResult.data[0]._id;
478
+ }
479
+ }
480
+ }
481
+
482
+ const body = getMapPayload();
483
+ let result;
484
+ if (MapEngineCyberia.currentMapId) {
485
+ result = await CyberiaMapService.put({ id: MapEngineCyberia.currentMapId, body });
486
+ } else {
487
+ result = await CyberiaMapService.post({ body });
488
+ }
489
+ NotificationManager.Push({
490
+ html:
491
+ result.status === 'error'
492
+ ? result.message
493
+ : MapEngineCyberia.currentMapId
494
+ ? Translate.Render('success-update-item')
495
+ : Translate.Render('success-create-item'),
496
+ status: result.status,
497
+ });
498
+ if (result.status === 'success') {
499
+ if (result.data?._id) MapEngineCyberia.currentMapId = result.data._id;
500
+ await DefaultManagement.loadTable(managementId, { force: true, reload: true });
501
+ }
502
+ };
503
+
504
+ const cloneMap = async () => {
505
+ if (!MapEngineCyberia.currentMapId) return;
506
+
507
+ // Always upload a new thumbnail file for the clone so it doesn't share the original's file
508
+ const thumbnailInput = s(`.${idThumbnail}`);
509
+ let cloneThumbnailId = null;
510
+ if (thumbnailInput && thumbnailInput.files && thumbnailInput.files.length > 0) {
511
+ const formData = new FormData();
512
+ formData.append('file', thumbnailInput.files[0]);
513
+ const uploadResult = await FileService.post({ body: formData });
514
+ if (uploadResult.status === 'success' && uploadResult.data && uploadResult.data.length > 0) {
515
+ cloneThumbnailId = uploadResult.data[0]._id;
516
+ } else {
517
+ NotificationManager.Push({
518
+ html: uploadResult.message || 'Failed to upload thumbnail',
519
+ status: 'error',
520
+ });
521
+ return;
522
+ }
523
+ }
524
+
525
+ // Capture object layer thumbnail for clone if checkbox is checked
526
+ if (!cloneThumbnailId && MapEngineCyberia.captureObjLayerThumbnail) {
527
+ const { cols, rows, cellW, cellH } = getCanvasParams();
528
+ const offscreen = MapEngineCyberia.renderToOffscreenCanvas(cols, rows, cellW, cellH, {
529
+ forceObjectLayers: true,
530
+ });
531
+ const blob = await new Promise((resolve) => offscreen.toBlob(resolve, 'image/png'));
532
+ if (blob) {
533
+ const file = new File([blob], 'map-thumbnail.png', { type: 'image/png' });
534
+ const formData = new FormData();
535
+ formData.append('file', file);
536
+ const uploadResult = await FileService.post({ body: formData });
537
+ if (uploadResult.status === 'success' && uploadResult.data?.length > 0) {
538
+ cloneThumbnailId = uploadResult.data[0]._id;
539
+ }
540
+ }
541
+ }
542
+
543
+ const body = getMapPayload();
544
+ if (cloneThumbnailId) body.thumbnail = cloneThumbnailId;
545
+ const result = await CyberiaMapService.post({ body });
546
+ NotificationManager.Push({
547
+ html: result.status === 'error' ? result.message : Translate.Render('success-create-item'),
548
+ status: result.status,
549
+ });
550
+ if (result.status === 'success') {
551
+ if (result.data?._id) MapEngineCyberia.currentMapId = result.data._id;
552
+ if (cloneThumbnailId) MapEngineCyberia.currentThumbnailId = cloneThumbnailId;
553
+ await DefaultManagement.loadTable(managementId, { force: true, reload: true });
554
+ }
555
+ };
556
+
557
+ const loadMap = async (mapData) => {
558
+ MapEngineCyberia.currentMapId = mapData._id || null;
559
+ if (s(`.${idCode}`)) s(`.${idCode}`).value = mapData.code || '';
560
+ if (s(`.${idName}`)) s(`.${idName}`).value = mapData.name || '';
561
+ if (s(`.${idDescription}`)) s(`.${idDescription}`).value = mapData.description || '';
562
+ if (s(`.${idTags}`)) s(`.${idTags}`).value = (mapData.tags || []).join(', ');
563
+
564
+ // Restore grid dimensions
565
+ if (s(`.${idX}`)) s(`.${idX}`).value = mapData.gridX || 16;
566
+ if (s(`.${idY}`)) s(`.${idY}`).value = mapData.gridY || 16;
567
+ if (s(`.${idCellW}`)) s(`.${idCellW}`).value = mapData.cellWidth || 32;
568
+ if (s(`.${idCellH}`)) s(`.${idCellH}`).value = mapData.cellHeight || 32;
569
+ const statusValue = mapData.status || 'unlisted';
570
+ if (DropDown.Tokens[idStatus]) {
571
+ const statusIndex = statusOptions.findIndex((opt) => opt.value === statusValue);
572
+ if (statusIndex > -1) s(`.dropdown-option-${idStatus}-${statusIndex}`).click();
573
+ }
574
+
575
+ // Thumbnail
576
+ MapEngineCyberia.currentThumbnailId = mapData.thumbnail || null;
577
+ const thumbnailPreview = s(`.map-engine-thumbnail-preview`);
578
+ if (MapEngineCyberia.currentThumbnailId) {
579
+ const thumbId =
580
+ typeof MapEngineCyberia.currentThumbnailId === 'object'
581
+ ? MapEngineCyberia.currentThumbnailId._id
582
+ : MapEngineCyberia.currentThumbnailId;
583
+
584
+ // Set preview image
585
+ if (thumbnailPreview) {
586
+ thumbnailPreview.innerHTML = html`<img
587
+ src="${getApiBaseUrl({ id: thumbId, endpoint: 'file/blob' })}"
588
+ style="max-width:120px;max-height:120px;border:1px solid #555;"
589
+ onerror="this.style.display='none';"
590
+ />`;
591
+ }
592
+
593
+ // Populate InputFile with the actual file from server
594
+ if (s(`.${idThumbnail}`)) {
595
+ const fileData = await getFileFromBlobEndpoint({ _id: thumbId, mimetype: 'image/png' });
596
+ if (fileData) {
597
+ const dataTransfer = new DataTransfer();
598
+ dataTransfer.items.add(fileData);
599
+ s(`.${idThumbnail}`).files = dataTransfer.files;
600
+ s(`.${idThumbnail}`).onchange({ target: s(`.${idThumbnail}`) });
601
+ }
602
+ }
603
+ MapEngineCyberia.thumbnailDirty = false;
604
+ } else {
605
+ if (thumbnailPreview) thumbnailPreview.innerHTML = '';
606
+ // Clear InputFile
607
+ if (s(`.${idThumbnail}`)) {
608
+ s(`.${idThumbnail}`).value = '';
609
+ s(`.${idThumbnail}`).onchange({ target: s(`.${idThumbnail}`) });
610
+ }
611
+ }
612
+
613
+ // Creator display
614
+ const creatorDisplay = s(`.map-engine-creator-display`);
615
+ if (creatorDisplay) {
616
+ if (mapData.creator) {
617
+ const creatorUsername =
618
+ typeof mapData.creator === 'object' ? mapData.creator.username || mapData.creator._id : mapData.creator;
619
+ creatorDisplay.innerHTML = html`<span style="font-family:monospace;font-size:12px;"
620
+ >${creatorUsername}</span
621
+ >`;
622
+ } else {
623
+ creatorDisplay.innerHTML = html`<span style="color:#888;font-size:12px;">—</span>`;
624
+ }
625
+ }
626
+
627
+ MapEngineCyberia.entities = (mapData.entities || []).map((e) => ({
628
+ entityType: e.entityType,
629
+ initCellX: e.initCellX,
630
+ initCellY: e.initCellY,
631
+ dimX: e.dimX,
632
+ dimY: e.dimY,
633
+ color: e.color,
634
+ objectLayerItemIds: e.objectLayerItemIds || [],
635
+ }));
636
+ for (const entity of MapEngineCyberia.entities) {
637
+ for (const itemId of entity.objectLayerItemIds || []) {
638
+ MapEngineCyberia.loadObjectLayerImage(itemId, rerenderCanvas);
639
+ }
640
+ }
641
+ MapEngineCyberia.renderEntityList(entityListId);
642
+ rerenderCanvas();
643
+ };
644
+
645
+ MapEngineCyberia.loadMap = loadMap;
646
+
647
+ const resetForm = () => {
648
+ MapEngineCyberia.currentMapId = null;
649
+ MapEngineCyberia.currentThumbnailId = null;
650
+ MapEngineCyberia.thumbnailDirty = false;
651
+ if (s(`.${idCode}`)) s(`.${idCode}`).value = '';
652
+ if (s(`.${idName}`)) s(`.${idName}`).value = '';
653
+ if (s(`.${idDescription}`)) s(`.${idDescription}`).value = '';
654
+ if (s(`.${idTags}`)) s(`.${idTags}`).value = '';
655
+ if (DropDown.Tokens[idStatus]) {
656
+ const resetIndex = statusOptions.findIndex((opt) => opt.value === 'unlisted');
657
+ if (resetIndex > -1) s(`.dropdown-option-${idStatus}-${resetIndex}`).click();
658
+ }
659
+ const thumbnailPreview = s(`.map-engine-thumbnail-preview`);
660
+ if (thumbnailPreview) thumbnailPreview.innerHTML = '';
661
+ if (s(`.${idThumbnail}`)) {
662
+ s(`.${idThumbnail}`).value = '';
663
+ s(`.${idThumbnail}`).onchange({ target: s(`.${idThumbnail}`) });
664
+ }
665
+ const creatorDisplay = s(`.map-engine-creator-display`);
666
+ if (creatorDisplay) creatorDisplay.innerHTML = '<span style="color:#888;font-size:12px;">—</span>';
667
+ if (s(`.${idX}`)) s(`.${idX}`).value = 16;
668
+ if (s(`.${idY}`)) s(`.${idY}`).value = 16;
669
+ if (s(`.${idCellW}`)) s(`.${idCellW}`).value = 32;
670
+ if (s(`.${idCellH}`)) s(`.${idCellH}`).value = 32;
671
+ if (s(`.${idVariationPreserve}`)) s(`.${idVariationPreserve}`).value = '';
672
+ MapEngineCyberia.entities = [];
673
+ MapEngineCyberia.renderEntityList(entityListId);
674
+ rerenderCanvas();
675
+ if (DropDown.Tokens[idObjLayerDropdown]) {
676
+ DropDown.Tokens[idObjLayerDropdown].oncheckvalues = {};
677
+ DropDown.Tokens[idObjLayerDropdown].value = [];
678
+ htmls(`.dropdown-current-${idObjLayerDropdown}`, '');
679
+ htmls(`.${idObjLayerDropdown}-render-container`, '');
680
+ }
681
+ };
682
+
683
+ setTimeout(() => {
684
+ const canvas = s(`.${canvasId}`);
685
+ if (!canvas) return;
686
+
687
+ const updateRgbaDisplay = () => {
688
+ const hex = s(`.${idColor}`)?.value || '#ff0000';
689
+ const alpha = parseFloat(s(`.${idAlpha}`)?.value);
690
+ const rgba = hexToRgba(hex, alpha);
691
+ if (s(`.${rgbaDisplayId}`))
692
+ htmls(
693
+ `.${rgbaDisplayId}`,
694
+ `<span style="display:inline-block;width:16px;height:16px;background:${rgba};border:1px solid #888;vertical-align:middle;margin-right:6px;"></span>${rgba}`,
695
+ );
696
+ };
697
+
698
+ if (s(`.${idColor}`)) s(`.${idColor}`).addEventListener('input', updateRgbaDisplay);
699
+ if (s(`.${idAlpha}`)) s(`.${idAlpha}`).addEventListener('input', updateRgbaDisplay);
700
+ updateRgbaDisplay();
701
+
702
+ const params = getCanvasParams();
703
+ MapEngineCyberia.renderGrid(
704
+ canvas,
705
+ params.cols,
706
+ params.rows,
707
+ params.cellW,
708
+ params.cellH,
709
+ MapEngineCyberia.showGridBorders,
710
+ );
711
+
712
+ canvas.onclick = (e) => {
713
+ const rect = canvas.getBoundingClientRect();
714
+ const { cellW, cellH } = getCanvasParams();
715
+ const col = Math.floor(((e.clientX - rect.left) * (canvas.width / rect.width)) / cellW);
716
+ const row = Math.floor(((e.clientY - rect.top) * (canvas.height / rect.height)) / cellH);
717
+ console.log(`Cell clicked: (${col}, ${row})`);
718
+
719
+ if (s('.map-engine-cell-coords')) htmls('.map-engine-cell-coords', `Cell: (${col}, ${row})`);
720
+
721
+ if (s(`.${idInitCellX}`)) s(`.${idInitCellX}`).value = col;
722
+ if (s(`.${idInitCellY}`)) s(`.${idInitCellY}`).value = row;
723
+
724
+ if (MapEngineCyberia.addOnClick) addEntityLocally();
725
+ };
726
+
727
+ if (s(`.btn-map-engine-add-entity`)) s(`.btn-map-engine-add-entity`).onclick = () => addEntityLocally();
728
+
729
+ if (s(`.btn-map-engine-fill-map`)) s(`.btn-map-engine-fill-map`).onclick = () => fillMapWithEntity();
730
+
731
+ if (s(`.btn-map-engine-generate-variation`))
732
+ s(`.btn-map-engine-generate-variation`).onclick = () => generateVariation();
733
+
734
+ if (s(`.btn-map-engine-flip-horizontal`)) s(`.btn-map-engine-flip-horizontal`).onclick = () => flipHorizontal();
735
+
736
+ if (s(`.btn-map-engine-flip-vertical`)) s(`.btn-map-engine-flip-vertical`).onclick = () => flipVertical();
737
+
738
+ if (s(`.btn-map-engine-generate`))
739
+ s(`.btn-map-engine-generate`).onclick = () => {
740
+ rerenderCanvas();
741
+ };
742
+
743
+ if (s(`.btn-map-engine-save-map`)) s(`.btn-map-engine-save-map`).onclick = () => saveMap();
744
+
745
+ if (s(`.btn-map-engine-clone-map`))
746
+ s(`.btn-map-engine-clone-map`).onclick = () => {
747
+ if (!MapEngineCyberia.currentMapId) return;
748
+ cloneMap();
749
+ };
750
+
751
+ if (s(`.btn-map-engine-new-map`)) s(`.btn-map-engine-new-map`).onclick = () => resetForm();
752
+
753
+ ThemeEvents['map-engine-theme'] = () => {
754
+ MapEngineCyberia.renderEntityList(entityListId);
755
+ };
756
+
757
+ if (s(`.btn-map-engine-capture-thumbnail`))
758
+ s(`.btn-map-engine-capture-thumbnail`).onclick = () => {
759
+ const { cols, rows, cellW, cellH } = getCanvasParams();
760
+ const offscreen = MapEngineCyberia.renderToOffscreenCanvas(cols, rows, cellW, cellH);
761
+ offscreen.toBlob((blob) => {
762
+ if (!blob) return;
763
+ const file = new File([blob], 'map-thumbnail.png', { type: 'image/png' });
764
+ const dataTransfer = new DataTransfer();
765
+ dataTransfer.items.add(file);
766
+ const thumbnailInput = s(`.${idThumbnail}`);
767
+ if (thumbnailInput) {
768
+ thumbnailInput.files = dataTransfer.files;
769
+ thumbnailInput.onchange({ target: thumbnailInput });
770
+ }
771
+ MapEngineCyberia.thumbnailDirty = true;
772
+ }, 'image/png');
773
+ };
774
+
775
+ if (s(`.btn-map-engine-toggle-thumbnail`))
776
+ s(`.btn-map-engine-toggle-thumbnail`).onclick = () => {
777
+ const body = s(`.map-engine-thumbnail-body`);
778
+ const caret = s(`.map-engine-thumbnail-caret`);
779
+ if (body) body.classList.toggle('hide');
780
+ if (caret) {
781
+ caret.classList.toggle('fa-caret-right');
782
+ caret.classList.toggle('fa-caret-down');
783
+ }
784
+ };
785
+
786
+ if (s('.btn-map-engine-toggle-entity-filter'))
787
+ s('.btn-map-engine-toggle-entity-filter').onclick = () => {
788
+ const body = s('.map-engine-entity-filter-body');
789
+ const caret = s('.map-engine-entity-filter-caret');
790
+ if (body) body.classList.toggle('hide');
791
+ if (caret) {
792
+ caret.classList.toggle('fa-caret-right');
793
+ caret.classList.toggle('fa-caret-down');
794
+ }
795
+ };
796
+
797
+ let entityFilterTimeout = null;
798
+ const applyEntityFilter = () => {
799
+ clearTimeout(entityFilterTimeout);
800
+ entityFilterTimeout = setTimeout(() => {
801
+ MapEngineCyberia.renderEntityList(entityListId);
802
+ }, 300);
803
+ };
804
+ [idFilterEntityType, idFilterInitX, idFilterInitY].forEach((cls) => {
805
+ if (s(`.${cls}`)) s(`.${cls}`).addEventListener('input', applyEntityFilter);
806
+ });
807
+
808
+ if (s('.btn-map-engine-clear-entity-filter'))
809
+ s('.btn-map-engine-clear-entity-filter').onclick = () => {
810
+ [idFilterEntityType, idFilterInitX, idFilterInitY].forEach((cls) => {
811
+ if (s(`.${cls}`)) s(`.${cls}`).value = '';
812
+ });
813
+ MapEngineCyberia.renderEntityList(entityListId);
814
+ };
815
+ });
816
+
817
+ const statusOptions = [
818
+ { value: 'unlisted', display: 'unlisted', data: 'unlisted', onClick: () => {} },
819
+ { value: 'draft', display: 'draft', data: 'draft', onClick: () => {} },
820
+ { value: 'published', display: 'published', data: 'published', onClick: () => {} },
821
+ { value: 'archived', display: 'archived', data: 'archived', onClick: () => {} },
822
+ ];
823
+
824
+ const managementTableHtml = await CyberiaMapManagement.RenderTable({
825
+ idModal: managementId,
826
+ loadMapCallback: loadMap,
827
+ appStore,
828
+ readyRowDataEvent: {
829
+ 'map-engine-check-deleted': (rowData) => {
830
+ if (MapEngineCyberia.currentMapId) {
831
+ const stillExists = rowData.some((row) => row._id === MapEngineCyberia.currentMapId);
832
+ if (!stillExists) MapEngineCyberia.currentMapId = null;
833
+ }
834
+ },
835
+ },
836
+ });
837
+
838
+ const dcMapFields = 'map-engine-dc-fields';
839
+ const dcMetaFields = 'map-engine-dc-meta';
840
+ const dcGridSize = 'map-engine-dc-grid-size';
841
+ const dcCellSize = 'map-engine-dc-cell-size';
842
+ const dcEntityType = 'map-engine-dc-entity-type';
843
+ const dcAlpha = 'map-engine-dc-alpha';
844
+ const dcCellPos = 'map-engine-dc-cell-pos';
845
+ const dcDim = 'map-engine-dc-dim';
846
+ const dcFactors = 'map-engine-dc-factors';
847
+ const dcSaveNew = 'map-engine-dc-save-new';
848
+ const dcEntityFilter = 'map-engine-dc-entity-filter';
849
+ const dcCanvasOpts = 'map-engine-dc-canvas-opts';
850
+ const idFilterEntityType = 'map-engine-filter-entity-type';
851
+ const idFilterInitX = 'map-engine-filter-init-x';
852
+ const idFilterInitY = 'map-engine-filter-init-y';
853
+
854
+ return html`<div class="in section-mp map-engine-container">
855
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcMapFields, type: 'search-inputs' })}
856
+ <div class="fl">
857
+ <div class="in fll ${dcMapFields}-col-a">
858
+ ${await Input.Render({
859
+ id: idCode,
860
+ label: html`Code`,
861
+ containerClass: 'inl',
862
+ type: 'text',
863
+ })}
864
+ </div>
865
+ <div class="in fll ${dcMapFields}-col-b">
866
+ ${await Input.Render({
867
+ id: idName,
868
+ label: html`Name`,
869
+ containerClass: 'inl',
870
+ type: 'text',
871
+ })}
872
+ </div>
873
+ <div class="in fll ${dcMapFields}-col-c">
874
+ ${await Input.Render({
875
+ id: idDescription,
876
+ label: html`Description`,
877
+ containerClass: 'inl',
878
+ type: 'text',
879
+ })}
880
+ </div>
881
+ </div>
882
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcMetaFields, type: 'search-inputs' })}
883
+ <div class="fl">
884
+ <div class="in fll ${dcMetaFields}-col-a">
885
+ ${await Input.Render({
886
+ id: idTags,
887
+ label: html`Tags (comma separated)`,
888
+ containerClass: 'inl',
889
+ type: 'text',
890
+ })}
891
+ </div>
892
+ <div class="in fll ${dcMetaFields}-col-b">
893
+ ${await DropDown.Render({
894
+ id: idStatus,
895
+ label: html`Status`,
896
+ data: statusOptions.map((opt) => ({ ...opt })),
897
+ value: 'unlisted',
898
+ containerClass: 'inl',
899
+ })}
900
+ </div>
901
+ <div class="in fll ${dcMetaFields}-col-c">
902
+ <div class="inl">
903
+ <div class="in input-label">Creator</div>
904
+ <div class="in map-engine-creator-display">
905
+ <span style="color:#888;font-size:12px;">—</span>
906
+ </div>
907
+ </div>
908
+ </div>
909
+ </div>
910
+ <div class="in section-mp" style="margin-top: 5px;">
911
+ <div class="in map-engine-thumbnail-preview" style="margin-bottom: 5px;"></div>
912
+ ${await BtnIcon.Render({
913
+ class: 'wfa btn-map-engine-capture-thumbnail',
914
+ label: html`<i class="fa-solid fa-camera"></i> Capture Thumbnail`,
915
+ })}
916
+ <div class="fl" style="align-items: center; gap: 8px; font-size: 20px; text-align: left; margin: 5px 0;">
917
+ ${await ToggleSwitch.Render({
918
+ id: 'map-engine-capture-obj-layer-thumb',
919
+ type: 'checkbox',
920
+ displayMode: 'checkbox',
921
+ containerClass: 'in fll',
922
+ checked: true,
923
+ on: {
924
+ checked: () => {
925
+ MapEngineCyberia.captureObjLayerThumbnail = true;
926
+ },
927
+ unchecked: () => {
928
+ MapEngineCyberia.captureObjLayerThumbnail = false;
929
+ },
930
+ },
931
+ })}
932
+ <div class="section-mp">&nbsp &nbsp Capture Object Layer Map Thumbnail on Save/Update</div>
933
+ </div>
934
+ ${await BtnIcon.Render({
935
+ class: 'wfa btn-map-engine-toggle-thumbnail',
936
+ label: html`<i class="fa-solid fa-caret-right map-engine-thumbnail-caret"></i> Thumbnail`,
937
+ })}
938
+ <div class="in map-engine-thumbnail-body hide">
939
+ ${await InputFile.Render(
940
+ {
941
+ id: idThumbnail,
942
+ multiple: false,
943
+ extensionsAccept: ['image/png', 'image/jpeg'],
944
+ },
945
+ {
946
+ change: (e) => {
947
+ const file = e.target.files[0];
948
+ if (file) {
949
+ MapEngineCyberia.thumbnailDirty = true;
950
+ const url = URL.createObjectURL(file);
951
+ const preview = s('.map-engine-thumbnail-preview');
952
+ if (preview)
953
+ preview.innerHTML = html`<img
954
+ src="${url}"
955
+ class="in"
956
+ style="max-width:300px;height:auto;border:1px solid #555;margin:auto"
957
+ />`;
958
+ }
959
+ },
960
+ clear: () => {
961
+ MapEngineCyberia.thumbnailDirty = true;
962
+ MapEngineCyberia.currentThumbnailId = null;
963
+ const preview = s('.map-engine-thumbnail-preview');
964
+ if (preview) preview.innerHTML = '';
965
+ },
966
+ },
967
+ )}
968
+ </div>
969
+ </div>
970
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcGridSize, type: 'a-50-b-50' })}
971
+ <div class="fl">
972
+ <div class="in fll ${dcGridSize}-col-a">
973
+ ${await Input.Render({
974
+ id: idX,
975
+ label: html`X`,
976
+ containerClass: 'inl',
977
+ type: 'number',
978
+ min: 1,
979
+ value: 16,
980
+ })}
981
+ </div>
982
+ <div class="in fll ${dcGridSize}-col-b">
983
+ ${await Input.Render({
984
+ id: idY,
985
+ label: html`Y`,
986
+ containerClass: 'inl',
987
+ type: 'number',
988
+ min: 1,
989
+ value: 16,
990
+ })}
991
+ </div>
992
+ </div>
993
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcCellSize, type: 'a-50-b-50' })}
994
+ <div class="fl">
995
+ <div class="in fll ${dcCellSize}-col-a">
996
+ ${await Input.Render({
997
+ id: idCellW,
998
+ label: html`Cell Width (px)`,
999
+ containerClass: 'inl',
1000
+ type: 'number',
1001
+ min: 1,
1002
+ value: 32,
1003
+ })}
1004
+ </div>
1005
+ <div class="in fll ${dcCellSize}-col-b">
1006
+ ${await Input.Render({
1007
+ id: idCellH,
1008
+ label: html`Cell Height (px)`,
1009
+ containerClass: 'inl',
1010
+ type: 'number',
1011
+ min: 1,
1012
+ value: 32,
1013
+ })}
1014
+ </div>
1015
+ </div>
1016
+ <div class="fl">
1017
+ <div class="in wfa" style="padding: 10px; max-width: 200px; margin: auto;">
1018
+ ${await BtnIcon.Render({
1019
+ class: 'wfa btn-map-engine-generate',
1020
+ label: html`<i class="fa-solid fa-arrows-rotate"></i> Generate`,
1021
+ })}
1022
+ </div>
1023
+ </div>
1024
+ <div class="in" style="text-align: center; margin-top: 10px;">
1025
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcCanvasOpts, type: 'search-inputs' })}
1026
+ <div class="fl" style="margin-bottom: 5px;">
1027
+ <div class="in fll ${dcCanvasOpts}-col-a">
1028
+ <div class="fl" style="align-items: center; gap: 8px; font-size: 20px; text-align: left;">
1029
+ ${await ToggleSwitch.Render({
1030
+ id: 'map-engine-show-grid',
1031
+ type: 'checkbox',
1032
+ displayMode: 'checkbox',
1033
+ containerClass: 'in fll',
1034
+ checked: true,
1035
+ on: {
1036
+ checked: () => {
1037
+ MapEngineCyberia.showGridBorders = true;
1038
+ rerenderCanvas();
1039
+ },
1040
+ unchecked: () => {
1041
+ MapEngineCyberia.showGridBorders = false;
1042
+ rerenderCanvas();
1043
+ },
1044
+ },
1045
+ })}
1046
+ <div class="section-mp">&nbsp &nbsp Show Grid</div>
1047
+ </div>
1048
+ </div>
1049
+ <div class="in fll ${dcCanvasOpts}-col-b">
1050
+ <div class="fl" style="align-items: center; gap: 8px; font-size: 20px; text-align: left;">
1051
+ ${await ToggleSwitch.Render({
1052
+ id: 'map-engine-add-on-click',
1053
+ type: 'checkbox',
1054
+ displayMode: 'checkbox',
1055
+ containerClass: 'in fll',
1056
+ checked: true,
1057
+ on: {
1058
+ checked: () => {
1059
+ MapEngineCyberia.addOnClick = true;
1060
+ },
1061
+ unchecked: () => {
1062
+ MapEngineCyberia.addOnClick = false;
1063
+ },
1064
+ },
1065
+ })}
1066
+ <div class="section-mp">&nbsp &nbsp Add on Click</div>
1067
+ </div>
1068
+ </div>
1069
+ <div class="in fll ${dcCanvasOpts}-col-c">
1070
+ <div class="fl" style="align-items: center; gap: 8px; font-size: 20px; text-align: left;">
1071
+ ${await ToggleSwitch.Render({
1072
+ id: 'map-engine-show-object-layers',
1073
+ type: 'checkbox',
1074
+ displayMode: 'checkbox',
1075
+ containerClass: 'in fll',
1076
+ checked: false,
1077
+ on: {
1078
+ checked: () => {
1079
+ MapEngineCyberia.showObjectLayers = true;
1080
+ rerenderCanvas();
1081
+ },
1082
+ unchecked: () => {
1083
+ MapEngineCyberia.showObjectLayers = false;
1084
+ rerenderCanvas();
1085
+ },
1086
+ },
1087
+ })}
1088
+ <div class="section-mp">&nbsp &nbsp Object Layers</div>
1089
+ </div>
1090
+ </div>
1091
+ </div>
1092
+ <canvas class="${canvasId}" width="512" height="512" style="border: 1px solid #555;"></canvas>
1093
+ <div class="in map-engine-cell-coords" style="font-family:monospace;font-size:13px;color:#888;margin-top:4px;">
1094
+ Cell: (0, 0)
1095
+ </div>
1096
+ </div>
1097
+ <div class="in section-mp" style="margin-top: 10px;">
1098
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcEntityType, type: 'a-50-b-50' })}
1099
+ <div class="fl">
1100
+ <div class="in fll ${dcEntityType}-col-a">
1101
+ ${await Input.Render({
1102
+ id: idEntityType,
1103
+ label: html`Entity Type`,
1104
+ containerClass: 'inl',
1105
+ type: 'text',
1106
+ value: 'floor',
1107
+ })}
1108
+ </div>
1109
+ <div class="in fll ${dcEntityType}-col-b">
1110
+ ${await Input.Render({
1111
+ id: idColor,
1112
+ label: html`Color`,
1113
+ containerClass: 'inl',
1114
+ type: 'color',
1115
+ value: '#ff0000',
1116
+ })}
1117
+ </div>
1118
+ </div>
1119
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcAlpha, type: 'a-50-b-50' })}
1120
+ <div class="fl">
1121
+ <div class="in fll ${dcAlpha}-col-a">
1122
+ <div class="inl input-container-${idAlpha}">
1123
+ <div class="in">
1124
+ <div class="in input-label">Alpha</div>
1125
+ <label for="${idAlpha}-name">
1126
+ <span class="hide">Alpha</span>
1127
+ <input
1128
+ type="range"
1129
+ class="in wfa ${idAlpha}"
1130
+ min="0"
1131
+ max="1"
1132
+ step="0.01"
1133
+ value="1"
1134
+ name="${idAlpha}-name"
1135
+ id="${idAlpha}-name"
1136
+ />
1137
+ </label>
1138
+ </div>
1139
+ </div>
1140
+ </div>
1141
+ <div class="in fll ${dcAlpha}-col-b" style="line-height: 40px;">
1142
+ <div class="in input-label">RGBA</div>
1143
+ <div class="in ${rgbaDisplayId}" style="font-family: monospace; font-size: 13px;"></div>
1144
+ </div>
1145
+ </div>
1146
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcCellPos, type: 'a-50-b-50' })}
1147
+ <div class="fl">
1148
+ <div class="in fll ${dcCellPos}-col-a">
1149
+ ${await Input.Render({
1150
+ id: idInitCellX,
1151
+ label: html`initCellX`,
1152
+ containerClass: 'inl',
1153
+ type: 'number',
1154
+ min: 0,
1155
+ value: 0,
1156
+ })}
1157
+ </div>
1158
+ <div class="in fll ${dcCellPos}-col-b">
1159
+ ${await Input.Render({
1160
+ id: idInitCellY,
1161
+ label: html`initCellY`,
1162
+ containerClass: 'inl',
1163
+ type: 'number',
1164
+ min: 0,
1165
+ value: 0,
1166
+ })}
1167
+ </div>
1168
+ </div>
1169
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcDim, type: 'a-50-b-50' })}
1170
+ <div class="fl">
1171
+ <div class="in fll ${dcDim}-col-a">
1172
+ ${await Input.Render({
1173
+ id: idDimX,
1174
+ label: html`dimX`,
1175
+ containerClass: 'inl',
1176
+ type: 'number',
1177
+ min: 1,
1178
+ value: 1,
1179
+ })}
1180
+ </div>
1181
+ <div class="in fll ${dcDim}-col-b">
1182
+ ${await Input.Render({
1183
+ id: idDimY,
1184
+ label: html`dimY`,
1185
+ containerClass: 'inl',
1186
+ type: 'number',
1187
+ min: 1,
1188
+ value: 1,
1189
+ })}
1190
+ </div>
1191
+ </div>
1192
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcFactors, type: 'a-50-b-50' })}
1193
+ <div class="fl">
1194
+ <div class="in fll ${dcFactors}-col-a">
1195
+ ${await Input.Render({
1196
+ id: idFactorA,
1197
+ label: html`factorA`,
1198
+ containerClass: 'inl',
1199
+ type: 'number',
1200
+ step: 0.01,
1201
+ value: 0.5,
1202
+ })}
1203
+ </div>
1204
+ <div class="in fll ${dcFactors}-col-b">
1205
+ ${await Input.Render({
1206
+ id: idFactorB,
1207
+ label: html`factorB`,
1208
+ containerClass: 'inl',
1209
+ type: 'number',
1210
+ step: 0.01,
1211
+ value: 1.5,
1212
+ })}
1213
+ </div>
1214
+ </div>
1215
+ ${await Input.Render({
1216
+ id: idVariationPreserve,
1217
+ label: html`Variation Preserve List`,
1218
+ containerClass: 'inl',
1219
+ type: 'text',
1220
+ placeholder: true,
1221
+ })}
1222
+ <div class="fl" style="align-items: center; gap: 8px; font-size: 20px; text-align: left; margin: 5px 0;">
1223
+ ${await ToggleSwitch.Render({
1224
+ id: 'map-engine-random-dim',
1225
+ type: 'checkbox',
1226
+ displayMode: 'checkbox',
1227
+ containerClass: 'in fll',
1228
+ checked: false,
1229
+ on: {
1230
+ checked: () => {
1231
+ MapEngineCyberia.randomDim = true;
1232
+ },
1233
+ unchecked: () => {
1234
+ MapEngineCyberia.randomDim = false;
1235
+ },
1236
+ },
1237
+ })}
1238
+ <div class="section-mp">&nbsp &nbsp Random Dim</div>
1239
+ </div>
1240
+ <div class="in" style="margin: 10px;">
1241
+ ${await DropDown.Render({
1242
+ id: idObjLayerDropdown,
1243
+ label: html`Object Layers`,
1244
+ data: [],
1245
+ type: 'checkbox',
1246
+ containerClass: 'inl',
1247
+ excludeSelected: true,
1248
+ serviceProvider: async (q) => {
1249
+ const result = await ObjectLayerService.searchItemIds({ q });
1250
+ if (result.status === 'success' && result.data?.itemIds) {
1251
+ return result.data.itemIds.map((itemId) => ({
1252
+ value: itemId,
1253
+ display: itemId,
1254
+ data: itemId,
1255
+ onClick: () => {},
1256
+ }));
1257
+ }
1258
+ return [];
1259
+ },
1260
+ })}
1261
+ </div>
1262
+ <div class="in">
1263
+ ${await BtnIcon.Render({
1264
+ class: 'wfa btn-map-engine-add-entity',
1265
+ label: html`<i class="fa-solid fa-plus"></i> Add Entity`,
1266
+ })}
1267
+ </div>
1268
+ <div class="in" style="margin-top: 5px;">
1269
+ ${await BtnIcon.Render({
1270
+ class: 'wfa btn-map-engine-fill-map',
1271
+ label: html`<i class="fa-solid fa-fill-drip"></i> Map Fill`,
1272
+ })}
1273
+ </div>
1274
+ <div class="in" style="margin-top: 5px;">
1275
+ ${await BtnIcon.Render({
1276
+ class: 'wfa btn-map-engine-generate-variation',
1277
+ label: html`<i class="fa-solid fa-shuffle"></i> Generate Variation`,
1278
+ })}
1279
+ </div>
1280
+ <div class="in" style="margin-top: 5px;">
1281
+ ${await BtnIcon.Render({
1282
+ class: 'wfa btn-map-engine-flip-horizontal',
1283
+ label: html`<i class="fa-solid fa-arrows-left-right"></i> Flip Horizontal`,
1284
+ })}
1285
+ </div>
1286
+ <div class="in" style="margin-top: 5px;">
1287
+ ${await BtnIcon.Render({
1288
+ class: 'wfa btn-map-engine-flip-vertical',
1289
+ label: html`<i class="fa-solid fa-arrows-up-down"></i> Flip Vertical`,
1290
+ })}
1291
+ </div>
1292
+ <div class="in" style="margin-top: 10px;">
1293
+ ${await BtnIcon.Render({
1294
+ class: 'wfa btn-map-engine-toggle-entity-filter',
1295
+ label: html`<i class="fa-solid fa-caret-right map-engine-entity-filter-caret"></i> Filters`,
1296
+ })}
1297
+ <div class="in map-engine-entity-filter-body hide">
1298
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcEntityFilter, type: 'search-inputs' })}
1299
+ <div class="fl">
1300
+ <div class="in fll ${dcEntityFilter}-col-a">
1301
+ ${await Input.Render({
1302
+ id: idFilterEntityType,
1303
+ label: html`Entity Type`,
1304
+ containerClass: 'inl',
1305
+ type: 'text',
1306
+ placeholder: true,
1307
+ })}
1308
+ </div>
1309
+ <div class="in fll ${dcEntityFilter}-col-b">
1310
+ ${await Input.Render({
1311
+ id: idFilterInitX,
1312
+ label: html`initCellX`,
1313
+ containerClass: 'inl',
1314
+ type: 'text',
1315
+ placeholder: true,
1316
+ })}
1317
+ </div>
1318
+ <div class="in fll ${dcEntityFilter}-col-c">
1319
+ ${await Input.Render({
1320
+ id: idFilterInitY,
1321
+ label: html`initCellY`,
1322
+ containerClass: 'inl',
1323
+ type: 'text',
1324
+ placeholder: true,
1325
+ })}
1326
+ </div>
1327
+ </div>
1328
+ <div class="in" style="margin-top:5px;">
1329
+ ${await BtnIcon.Render({
1330
+ class: 'wfa btn-map-engine-clear-entity-filter',
1331
+ label: html`<i class="fa-solid fa-broom"></i> Clear Filters`,
1332
+ })}
1333
+ </div>
1334
+ </div>
1335
+ </div>
1336
+ <div class="in ${entityListId}" style="margin-top: 10px; max-height: 200px; overflow-y: auto;"></div>
1337
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcSaveNew, type: 'search-inputs' })}
1338
+ <div class="fl" style="margin-top: 10px;">
1339
+ <div class="in fll ${dcSaveNew}-col-a" style="padding: 5px;">
1340
+ ${await BtnIcon.Render({
1341
+ class: 'wfa btn-map-engine-save-map',
1342
+ label: html`<i class="fa-solid fa-floppy-disk"></i> Save Map`,
1343
+ })}
1344
+ </div>
1345
+ <div class="in fll ${dcSaveNew}-col-b" style="padding: 5px;">
1346
+ ${await BtnIcon.Render({
1347
+ class: 'wfa btn-map-engine-clone-map',
1348
+ label: html`<i class="fa-solid fa-clone"></i> Clone Map`,
1349
+ })}
1350
+ </div>
1351
+ <div class="in fll ${dcSaveNew}-col-c" style="padding: 5px;">
1352
+ ${await BtnIcon.Render({
1353
+ class: 'wfa btn-map-engine-new-map',
1354
+ label: html`<i class="fa-solid fa-file"></i> New Map`,
1355
+ })}
1356
+ </div>
1357
+ </div>
1358
+ <div class="in" style="margin-top: 10px;">${managementTableHtml}</div>
1359
+ </div>
1360
+ </div>`;
4
1361
  }
5
1362
  }
6
1363