cyberia 3.1.3 → 3.2.9

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 (377) 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 +14 -10
  10. package/CHANGELOG.md +783 -1
  11. package/CLI-HELP.md +95 -18
  12. package/Dockerfile +0 -2
  13. package/README.md +290 -220
  14. package/bin/build.js +24 -7
  15. package/bin/cyberia.js +2838 -252
  16. package/bin/deploy.js +747 -125
  17. package/bin/file.js +9 -0
  18. package/bin/index.js +2838 -252
  19. package/bin/vs.js +1 -1
  20. package/conf.js +99 -65
  21. package/deployment.yaml +18 -164
  22. package/hardhat/hardhat.config.js +13 -13
  23. package/hardhat/ignition/modules/ObjectLayerToken.js +1 -1
  24. package/hardhat/package-lock.json +2559 -5864
  25. package/hardhat/package.json +14 -23
  26. package/hardhat/scripts/deployObjectLayerToken.js +1 -1
  27. package/hardhat/test/ObjectLayerToken.js +4 -2
  28. package/hardhat/types/ethers-contracts/ObjectLayerToken.ts +690 -0
  29. package/hardhat/types/ethers-contracts/common.ts +92 -0
  30. package/hardhat/types/ethers-contracts/factories/ObjectLayerToken__factory.ts +1055 -0
  31. package/hardhat/types/ethers-contracts/factories/index.ts +4 -0
  32. package/hardhat/types/ethers-contracts/hardhat.d.ts +47 -0
  33. package/hardhat/types/ethers-contracts/index.ts +6 -0
  34. package/jsconfig.json +1 -1
  35. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +6 -5
  36. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +6 -5
  37. package/manifests/deployment/dd-cyberia-development/deployment.yaml +18 -164
  38. package/manifests/deployment/dd-cyberia-development/proxy.yaml +7 -79
  39. package/manifests/deployment/dd-default-development/deployment.yaml +2 -6
  40. package/manifests/deployment/dd-test-development/deployment.yaml +112 -28
  41. package/manifests/deployment/dd-test-development/proxy.yaml +46 -1
  42. package/manifests/deployment/playwright/deployment.yaml +1 -1
  43. package/nodemon.json +1 -1
  44. package/package.json +39 -24
  45. package/proxy.yaml +7 -79
  46. package/scripts/k3s-node-setup.sh +2 -2
  47. package/scripts/nat-iptables.sh +103 -18
  48. package/scripts/rhel-grpc-setup.sh +56 -0
  49. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +58 -14
  50. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +23 -14
  51. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +5 -0
  52. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +148 -20
  53. package/src/api/core/core.controller.js +10 -10
  54. package/src/api/core/core.service.js +10 -10
  55. package/src/api/crypto/crypto.controller.js +8 -8
  56. package/src/api/crypto/crypto.service.js +8 -8
  57. package/src/api/cyberia-action/cyberia-action.controller.js +74 -0
  58. package/src/api/cyberia-action/cyberia-action.model.js +87 -0
  59. package/src/api/cyberia-action/cyberia-action.router.js +27 -0
  60. package/src/api/cyberia-action/cyberia-action.service.js +42 -0
  61. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +93 -0
  62. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +36 -0
  63. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +29 -0
  64. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +51 -0
  65. package/src/api/cyberia-entity/cyberia-entity.controller.js +74 -0
  66. package/src/api/cyberia-entity/cyberia-entity.model.js +24 -0
  67. package/src/api/cyberia-entity/cyberia-entity.router.js +27 -0
  68. package/src/api/cyberia-entity/cyberia-entity.service.js +42 -0
  69. package/src/api/cyberia-instance/cyberia-fallback-world.js +178 -0
  70. package/src/api/cyberia-instance/cyberia-instance.controller.js +92 -0
  71. package/src/api/cyberia-instance/cyberia-instance.model.js +87 -0
  72. package/src/api/cyberia-instance/cyberia-instance.router.js +63 -0
  73. package/src/api/cyberia-instance/cyberia-instance.service.js +156 -0
  74. package/src/api/cyberia-instance/cyberia-portal-connector.js +260 -0
  75. package/src/api/cyberia-instance/cyberia-world-generator.js +505 -0
  76. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +74 -0
  77. package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +574 -0
  78. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +231 -0
  79. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +27 -0
  80. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +46 -0
  81. package/src/api/cyberia-map/cyberia-map.controller.js +79 -0
  82. package/src/api/cyberia-map/cyberia-map.model.js +30 -0
  83. package/src/api/cyberia-map/cyberia-map.router.js +40 -0
  84. package/src/api/cyberia-map/cyberia-map.service.js +74 -0
  85. package/src/api/cyberia-quest/cyberia-quest.controller.js +74 -0
  86. package/src/api/cyberia-quest/cyberia-quest.model.js +67 -0
  87. package/src/api/cyberia-quest/cyberia-quest.router.js +27 -0
  88. package/src/api/cyberia-quest/cyberia-quest.service.js +42 -0
  89. package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +74 -0
  90. package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +49 -0
  91. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +27 -0
  92. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +42 -0
  93. package/src/api/default/default.controller.js +10 -10
  94. package/src/api/default/default.service.js +10 -10
  95. package/src/api/document/document.controller.js +12 -12
  96. package/src/api/document/document.model.js +10 -16
  97. package/src/api/file/file.controller.js +8 -8
  98. package/src/api/file/file.model.js +10 -10
  99. package/src/api/file/file.ref.json +18 -0
  100. package/src/api/file/file.service.js +36 -36
  101. package/src/api/instance/instance.controller.js +10 -10
  102. package/src/api/instance/instance.model.js +4 -10
  103. package/src/api/instance/instance.service.js +10 -10
  104. package/src/api/ipfs/ipfs.controller.js +15 -36
  105. package/src/api/ipfs/ipfs.model.js +47 -47
  106. package/src/api/ipfs/ipfs.router.js +8 -13
  107. package/src/api/ipfs/ipfs.service.js +67 -129
  108. package/src/api/object-layer/object-layer.controller.js +12 -12
  109. package/src/api/object-layer/object-layer.model.js +4 -17
  110. package/src/api/object-layer/object-layer.router.js +30 -0
  111. package/src/api/object-layer/object-layer.service.js +126 -43
  112. package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +10 -10
  113. package/src/api/object-layer-render-frames/object-layer-render-frames.model.js +6 -16
  114. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +18 -14
  115. package/src/api/test/test.controller.js +8 -8
  116. package/src/api/test/test.service.js +8 -8
  117. package/src/api/user/guest.service.js +99 -0
  118. package/src/api/user/user.controller.js +6 -6
  119. package/src/api/user/user.model.js +8 -13
  120. package/src/api/user/user.service.js +11 -27
  121. package/src/cli/cluster.js +68 -21
  122. package/src/cli/db.js +753 -825
  123. package/src/cli/deploy.js +215 -125
  124. package/src/cli/env.js +29 -0
  125. package/src/cli/fs.js +82 -8
  126. package/src/cli/image.js +43 -1
  127. package/src/cli/index.js +74 -3
  128. package/src/cli/kubectl.js +211 -0
  129. package/src/cli/release.js +340 -0
  130. package/src/cli/repository.js +475 -74
  131. package/src/cli/run.js +582 -43
  132. package/src/cli/secrets.js +73 -0
  133. package/src/cli/ssh.js +1 -1
  134. package/src/cli/static.js +43 -115
  135. package/src/cli/test.js +3 -3
  136. package/src/client/Cryptokoyn.index.js +18 -22
  137. package/src/client/CyberiaPortal.index.js +19 -24
  138. package/src/client/Default.index.js +21 -34
  139. package/src/client/Itemledger.index.js +20 -27
  140. package/src/client/Underpost.index.js +19 -24
  141. package/src/client/components/core/404.js +4 -4
  142. package/src/client/components/core/500.js +4 -4
  143. package/src/client/components/core/Account.js +73 -60
  144. package/src/client/components/core/AgGrid.js +23 -33
  145. package/src/client/components/core/Alert.js +12 -13
  146. package/src/client/components/core/AppStore.js +69 -0
  147. package/src/client/components/core/Auth.js +35 -37
  148. package/src/client/components/core/Badge.js +7 -13
  149. package/src/client/components/core/BtnIcon.js +15 -17
  150. package/src/client/components/core/CalendarCore.js +43 -64
  151. package/src/client/components/core/Chat.js +13 -15
  152. package/src/client/components/core/ClientEvents.js +87 -0
  153. package/src/client/components/core/ColorPaletteElement.js +309 -0
  154. package/src/client/components/core/Content.js +17 -14
  155. package/src/client/components/core/Css.js +15 -71
  156. package/src/client/components/core/CssCore.js +12 -16
  157. package/src/client/components/core/D3Chart.js +4 -4
  158. package/src/client/components/core/Docs.js +64 -91
  159. package/src/client/components/core/DropDown.js +194 -96
  160. package/src/client/components/core/EventBus.js +92 -0
  161. package/src/client/components/core/EventsUI.js +14 -17
  162. package/src/client/components/core/FileExplorer.js +96 -228
  163. package/src/client/components/core/FullScreen.js +47 -75
  164. package/src/client/components/core/Input.js +24 -69
  165. package/src/client/components/core/Keyboard.js +26 -19
  166. package/src/client/components/core/KeyboardAvoidance.js +145 -0
  167. package/src/client/components/core/LoadingAnimation.js +25 -31
  168. package/src/client/components/core/LogIn.js +43 -43
  169. package/src/client/components/core/LogOut.js +25 -16
  170. package/src/client/components/core/Modal.js +462 -179
  171. package/src/client/components/core/NotificationManager.js +14 -18
  172. package/src/client/components/core/Panel.js +54 -51
  173. package/src/client/components/core/PanelForm.js +44 -144
  174. package/src/client/components/core/Polyhedron.js +110 -214
  175. package/src/client/components/core/PublicProfile.js +39 -32
  176. package/src/client/components/core/Recover.js +48 -44
  177. package/src/client/components/core/Responsive.js +88 -32
  178. package/src/client/components/core/RichText.js +9 -18
  179. package/src/client/components/core/Router.js +24 -3
  180. package/src/client/components/core/SearchBox.js +37 -37
  181. package/src/client/components/core/SignUp.js +39 -30
  182. package/src/client/components/core/SocketIo.js +112 -30
  183. package/src/client/components/core/SocketIoHandler.js +75 -0
  184. package/src/client/components/core/Stream.js +143 -95
  185. package/src/client/components/core/ToggleSwitch.js +8 -20
  186. package/src/client/components/core/ToolTip.js +5 -17
  187. package/src/client/components/core/Translate.js +56 -59
  188. package/src/client/components/core/Validator.js +26 -16
  189. package/src/client/components/core/Wallet.js +15 -26
  190. package/src/client/components/core/Webhook.js +40 -7
  191. package/src/client/components/core/Worker.js +163 -27
  192. package/src/client/components/core/windowGetDimensions.js +7 -7
  193. package/src/client/components/cryptokoyn/{MenuCryptokoyn.js → AppShellCryptokoyn.js} +59 -59
  194. package/src/client/components/cryptokoyn/AppStoreCryptokoyn.js +5 -0
  195. package/src/client/components/cryptokoyn/CssCryptokoyn.js +15 -15
  196. package/src/client/components/cryptokoyn/LogInCryptokoyn.js +9 -7
  197. package/src/client/components/cryptokoyn/LogOutCryptokoyn.js +8 -6
  198. package/src/client/components/cryptokoyn/RouterCryptokoyn.js +37 -0
  199. package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +4 -4
  200. package/src/client/components/cryptokoyn/SignUpCryptokoyn.js +6 -4
  201. package/src/client/components/cryptokoyn/SocketIoCryptokoyn.js +3 -51
  202. package/src/client/components/cyberia/InstanceEngineCyberia.js +781 -0
  203. package/src/client/components/cyberia/MapEngineCyberia.js +1836 -2
  204. package/src/client/components/cyberia/ObjectLayerEngine.js +19 -0
  205. package/src/client/components/cyberia/ObjectLayerEngineModal.js +1220 -99
  206. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +252 -316
  207. package/src/client/components/cyberia-portal/{MenuCyberiaPortal.js → AppShellCyberiaPortal.js} +136 -103
  208. package/src/client/components/cyberia-portal/AppStoreCyberiaPortal.js +5 -0
  209. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +462 -32
  210. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +15 -15
  211. package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +9 -7
  212. package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +8 -6
  213. package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +4 -4
  214. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +60 -0
  215. package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +4 -4
  216. package/src/client/components/cyberia-portal/SignUpCyberiaPortal.js +6 -4
  217. package/src/client/components/cyberia-portal/SocketIoCyberiaPortal.js +3 -49
  218. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +8 -4
  219. package/src/client/components/default/{MenuDefault.js → AppShellDefault.js} +91 -91
  220. package/src/client/components/default/AppStoreDefault.js +5 -0
  221. package/src/client/components/default/CssDefault.js +12 -12
  222. package/src/client/components/default/LogInDefault.js +9 -7
  223. package/src/client/components/default/LogOutDefault.js +8 -6
  224. package/src/client/components/default/RouterDefault.js +47 -0
  225. package/src/client/components/default/SettingsDefault.js +4 -4
  226. package/src/client/components/default/SignUpDefault.js +6 -4
  227. package/src/client/components/default/SocketIoDefault.js +3 -51
  228. package/src/client/components/default/TranslateDefault.js +3 -3
  229. package/src/client/components/itemledger/{MenuItemledger.js → AppShellItemledger.js} +59 -59
  230. package/src/client/components/itemledger/AppStoreItemledger.js +5 -0
  231. package/src/client/components/itemledger/CssItemledger.js +15 -15
  232. package/src/client/components/itemledger/LogInItemledger.js +9 -7
  233. package/src/client/components/itemledger/LogOutItemledger.js +8 -6
  234. package/src/client/components/itemledger/RouterItemledger.js +38 -0
  235. package/src/client/components/itemledger/SettingsItemledger.js +4 -4
  236. package/src/client/components/itemledger/SignUpItemledger.js +6 -4
  237. package/src/client/components/itemledger/SocketIoItemledger.js +3 -51
  238. package/src/client/components/itemledger/TranslateItemledger.js +3 -3
  239. package/src/client/components/underpost/{MenuUnderpost.js → AppShellUnderpost.js} +92 -92
  240. package/src/client/components/underpost/AppStoreUnderpost.js +5 -0
  241. package/src/client/components/underpost/CssUnderpost.js +14 -14
  242. package/src/client/components/underpost/CyberpunkBloggerUnderpost.js +4 -4
  243. package/src/client/components/underpost/DocumentSearchProvider.js +1 -1
  244. package/src/client/components/underpost/LabGalleryUnderpost.js +12 -15
  245. package/src/client/components/underpost/LogInUnderpost.js +9 -7
  246. package/src/client/components/underpost/LogOutUnderpost.js +8 -6
  247. package/src/client/components/underpost/RouterUnderpost.js +45 -0
  248. package/src/client/components/underpost/SettingsUnderpost.js +4 -4
  249. package/src/client/components/underpost/SignUpUnderpost.js +6 -4
  250. package/src/client/components/underpost/SocketIoUnderpost.js +3 -51
  251. package/src/client/components/underpost/TranslateUnderpost.js +4 -4
  252. package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +235 -0
  253. package/src/client/public/cyberia-docs/ARCHITECTURE.md +443 -0
  254. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +417 -0
  255. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +313 -0
  256. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +260 -0
  257. package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +241 -0
  258. package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +300 -0
  259. package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +279 -0
  260. package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +206 -0
  261. package/src/client/public/cyberia-docs/ROADMAP.md +240 -0
  262. package/src/client/public/cyberia-docs/WHITE-PAPER.md +732 -0
  263. package/src/client/services/atlas-sprite-sheet/atlas-sprite-sheet.service.js +14 -20
  264. package/src/client/services/core/core.service.js +35 -55
  265. package/src/client/services/crypto/crypto.service.js +8 -13
  266. package/src/client/services/cyberia-action/cyberia-action.service.js +99 -0
  267. package/src/client/services/cyberia-dialogue/cyberia-dialogue.service.js +99 -0
  268. package/src/client/services/cyberia-entity/cyberia-entity.management.js +57 -0
  269. package/src/client/services/cyberia-entity/cyberia-entity.service.js +99 -0
  270. package/src/client/services/cyberia-instance/cyberia-instance.management.js +194 -0
  271. package/src/client/services/cyberia-instance/cyberia-instance.service.js +116 -0
  272. package/src/client/services/cyberia-instance-conf/cyberia-instance-conf.service.js +99 -0
  273. package/src/client/services/cyberia-map/cyberia-map.management.js +193 -0
  274. package/src/client/services/cyberia-map/cyberia-map.service.js +120 -0
  275. package/src/client/services/cyberia-quest/cyberia-quest.service.js +99 -0
  276. package/src/client/services/cyberia-quest-progress/cyberia-quest-progress.service.js +99 -0
  277. package/src/client/services/default/default.management.js +159 -267
  278. package/src/client/services/default/default.service.js +10 -16
  279. package/src/client/services/document/document.service.js +14 -19
  280. package/src/client/services/file/file.service.js +8 -13
  281. package/src/client/services/instance/instance.management.js +6 -6
  282. package/src/client/services/instance/instance.service.js +10 -15
  283. package/src/client/services/ipfs/ipfs.service.js +14 -40
  284. package/src/client/services/object-layer/object-layer.management.js +14 -14
  285. package/src/client/services/object-layer/object-layer.service.js +39 -24
  286. package/src/client/services/object-layer-render-frames/object-layer-render-frames.service.js +10 -16
  287. package/src/client/services/test/test.service.js +8 -13
  288. package/src/client/services/user/guest.service.js +86 -0
  289. package/src/client/services/user/user.management.js +6 -6
  290. package/src/client/services/user/user.service.js +14 -20
  291. package/src/client/ssr/body/404.js +3 -3
  292. package/src/client/ssr/body/500.js +3 -3
  293. package/src/client/ssr/body/CacheControl.js +5 -2
  294. package/src/client/ssr/body/DefaultSplashScreen.js +19 -12
  295. package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +13 -6
  296. package/src/client/ssr/head/PwaItemledger.js +197 -60
  297. package/src/client/ssr/mailer/DefaultRecoverEmail.js +19 -20
  298. package/src/client/ssr/mailer/DefaultVerifyEmail.js +15 -16
  299. package/src/client/ssr/offline/Maintenance.js +12 -11
  300. package/src/client/ssr/offline/NoNetworkConnection.js +3 -3
  301. package/src/client/ssr/pages/CyberiaServerMetrics.js +1 -1
  302. package/src/client/ssr/pages/Test.js +2 -2
  303. package/src/client/sw/core.sw.js +212 -0
  304. package/src/grpc/cyberia/grpc-server.js +642 -0
  305. package/src/index.js +24 -1
  306. package/src/runtime/cyberia-client/Dockerfile +80 -0
  307. package/src/runtime/cyberia-server/Dockerfile +37 -0
  308. package/src/runtime/express/Dockerfile +5 -1
  309. package/src/runtime/express/Express.js +18 -1
  310. package/src/runtime/lampp/Dockerfile +17 -5
  311. package/src/runtime/lampp/Lampp.js +27 -4
  312. package/src/runtime/wp/Dockerfile +62 -0
  313. package/src/runtime/wp/Wp.js +639 -0
  314. package/src/server/atlas-sprite-sheet-generator.js +4 -2
  315. package/src/server/auth.js +24 -1
  316. package/src/server/backup.js +37 -9
  317. package/src/server/client-build-docs.js +52 -46
  318. package/src/server/client-build.js +356 -82
  319. package/src/server/client-formatted.js +140 -57
  320. package/src/server/conf.js +29 -13
  321. package/src/server/cron.js +25 -23
  322. package/src/server/data-query.js +32 -20
  323. package/src/server/dns.js +24 -1
  324. package/src/server/ipfs-client.js +253 -89
  325. package/src/server/object-layer.js +150 -114
  326. package/src/server/peer.js +8 -0
  327. package/src/server/process.js +13 -27
  328. package/src/server/runtime.js +25 -1
  329. package/src/server/semantic-layer-generator-floor.js +319 -0
  330. package/src/server/semantic-layer-generator-resource.js +259 -0
  331. package/src/server/semantic-layer-generator-skin.js +1164 -0
  332. package/src/server/semantic-layer-generator.js +211 -542
  333. package/src/server/shape-generator.js +108 -0
  334. package/src/server/start.js +19 -5
  335. package/src/server/valkey.js +141 -235
  336. package/src/ws/IoInterface.js +1 -10
  337. package/src/ws/IoServer.js +14 -33
  338. package/src/ws/core/channels/core.ws.chat.js +65 -20
  339. package/src/ws/core/channels/core.ws.mailer.js +113 -32
  340. package/src/ws/core/channels/core.ws.stream.js +90 -31
  341. package/src/ws/core/core.ws.connection.js +12 -33
  342. package/src/ws/core/core.ws.emit.js +10 -26
  343. package/src/ws/core/core.ws.server.js +25 -58
  344. package/src/ws/default/channels/default.ws.main.js +53 -12
  345. package/src/ws/default/default.ws.connection.js +26 -13
  346. package/src/ws/default/default.ws.server.js +30 -12
  347. package/tsconfig.docs.json +15 -0
  348. package/typedoc.dd-cyberia.json +29 -0
  349. package/typedoc.json +29 -0
  350. package/WHITE-PAPER.md +0 -1540
  351. package/hardhat/README.md +0 -531
  352. package/hardhat/WHITE-PAPER.md +0 -1540
  353. package/jsdoc.dd-cyberia.json +0 -59
  354. package/jsdoc.json +0 -59
  355. package/src/api/object-layer/README.md +0 -347
  356. package/src/client/components/core/ColorPalette.js +0 -5267
  357. package/src/client/components/core/JoyStick.js +0 -80
  358. package/src/client/components/cryptokoyn/CommonCryptokoyn.js +0 -29
  359. package/src/client/components/cryptokoyn/ElementsCryptokoyn.js +0 -38
  360. package/src/client/components/cryptokoyn/RoutesCryptokoyn.js +0 -39
  361. package/src/client/components/cyberia-portal/ElementsCyberiaPortal.js +0 -38
  362. package/src/client/components/cyberia-portal/RoutesCyberiaPortal.js +0 -58
  363. package/src/client/components/cyberia-portal/ServerCyberiaPortal.js +0 -136
  364. package/src/client/components/default/ElementsDefault.js +0 -38
  365. package/src/client/components/default/RoutesDefault.js +0 -49
  366. package/src/client/components/itemledger/CommonItemledger.js +0 -29
  367. package/src/client/components/itemledger/ElementsItemledger.js +0 -38
  368. package/src/client/components/itemledger/RoutesItemledger.js +0 -40
  369. package/src/client/components/underpost/CommonUnderpost.js +0 -29
  370. package/src/client/components/underpost/ElementsUnderpost.js +0 -38
  371. package/src/client/components/underpost/RoutesUnderpost.js +0 -47
  372. package/src/client/sw/default.sw.js +0 -127
  373. package/src/client/sw/template.sw.js +0 -84
  374. package/src/ws/core/management/core.ws.chat.js +0 -8
  375. package/src/ws/core/management/core.ws.mailer.js +0 -16
  376. package/src/ws/core/management/core.ws.stream.js +0 -8
  377. package/src/ws/default/management/default.ws.main.js +0 -8
@@ -11,7 +11,6 @@ import { ObjectLayerService } from '../../services/object-layer/object-layer.ser
11
11
  import { AtlasSpriteSheetService } from '../../services/atlas-sprite-sheet/atlas-sprite-sheet.service.js';
12
12
  import { NotificationManager } from '../core/NotificationManager.js';
13
13
  import { append, htmls, s } from '../core/VanillaJs.js';
14
-
15
14
  import { darkTheme, ThemeEvents, subThemeManager, lightenHex, darkenHex } from '../core/Css.js';
16
15
  import { ObjectLayerManagement } from '../../services/object-layer/object-layer.management.js';
17
16
  import { ObjectLayerEngineModal } from './ObjectLayerEngineModal.js';
@@ -20,11 +19,9 @@ import { DefaultManagement } from '../../services/default/default.management.js'
20
19
  import { AgGrid } from '../core/AgGrid.js';
21
20
  import { EventsUI } from '../core/EventsUI.js';
22
21
  import { createJSONEditor } from 'vanilla-jsoneditor';
23
-
24
22
  const logger = loggerFactory(import.meta);
25
-
26
- const ObjectLayerEngineViewer = {
27
- Data: {
23
+ class ObjectLayerEngineViewer {
24
+ static Data = {
28
25
  objectLayer: null,
29
26
  frameCounts: null,
30
27
  currentDirection: 'down',
@@ -36,10 +33,9 @@ const ObjectLayerEngineViewer = {
36
33
  isGeneratingAtlas: false,
37
34
  webpMetadata: null,
38
35
  metadataJsonEditor: null,
39
- },
40
-
36
+ };
41
37
  // Map user-friendly direction/mode to numeric direction codes
42
- getDirectionCode: function (direction, mode) {
38
+ static getDirectionCode(direction, mode) {
43
39
  const key = `${direction}_${mode}`;
44
40
  const directionCodeMap = {
45
41
  down_idle: '08',
@@ -52,36 +48,29 @@ const ObjectLayerEngineViewer = {
52
48
  right_walking: '16',
53
49
  };
54
50
  return directionCodeMap[key] || null;
55
- },
56
-
57
- Render: async function ({ Elements }) {
51
+ }
52
+ static async instance({ appStore }) {
58
53
  const id = 'object-layer-engine-viewer';
59
-
60
54
  // Reset currentObjectId when modal is rendered to ensure Reload triggers properly
61
- this.Data.currentObjectId = undefined;
62
-
55
+ ObjectLayerEngineViewer.Data.currentObjectId = undefined;
63
56
  Modal.Data[`modal-${id}`].onReloadModalListener[id] = async () => {
64
- ObjectLayerEngineViewer.Reload({ Elements });
57
+ ObjectLayerEngineViewer.Reload({ appStore });
65
58
  };
66
-
67
59
  // Listen for query parameter changes for smooth navigation
68
60
  listenQueryParamsChange({
69
61
  id: `${id}-query-listener`,
70
62
  event: async (queryParams) => {
71
63
  const objectId = queryParams.id || null;
72
-
73
64
  if (!s(`.modal-${id}`) || !s(`#${id}`)) {
74
65
  logger.warn('ObjectLayerEngineViewer DOM not ready for query param change');
75
66
  return;
76
67
  }
77
-
78
68
  // Only reload if object id actually changed (normalize undefined to null for comparison)
79
- if (objectId !== this.Data.currentObjectId) {
80
- await this.Reload({ Elements });
69
+ if (objectId !== ObjectLayerEngineViewer.Data.currentObjectId) {
70
+ await ObjectLayerEngineViewer.Reload({ appStore });
81
71
  }
82
72
  },
83
73
  });
84
-
85
74
  return html`
86
75
  <div class="fl">
87
76
  <div class="in ${id}" id="${id}">
@@ -91,101 +80,83 @@ const ObjectLayerEngineViewer = {
91
80
  </div>
92
81
  </div>
93
82
  `;
94
- },
95
-
96
- renderEmpty: async function ({ Elements }) {
83
+ }
84
+ static async renderEmpty({ appStore }) {
97
85
  const id = 'object-layer-engine-viewer';
98
86
  const idModal = 'modal-object-layer-engine-viewer';
99
-
100
87
  // Check if DOM element exists
101
88
  if (!s(`#${id}`)) {
102
89
  logger.warn('ObjectLayerEngineViewer DOM not ready for renderEmpty');
103
90
  return;
104
91
  }
105
-
106
92
  // Clear current object id when rendering empty state
107
- this.Data.currentObjectId = null;
108
-
93
+ ObjectLayerEngineViewer.Data.currentObjectId = null;
109
94
  // Check if the management table grid already exists AND its DOM is still present
110
95
  // If it does, don't re-render (just let DefaultManagement's RouterEvents handle URL changes)
111
96
  const gridId = `object-layer-engine-management-grid-${idModal}`;
112
97
  const gridExists = AgGrid.grids[gridId];
113
98
  const gridDomExists = s(`.${gridId}`);
114
-
115
99
  if (gridExists && gridDomExists) {
116
100
  // Grid already exists with DOM intact, no need to destroy and recreate it
117
101
  // The DefaultManagement RouterEvents listener will handle pagination/filter updates
118
102
  return;
119
103
  }
120
-
121
104
  // Grid doesn't exist or its DOM was destroyed, render/re-render it
122
105
  if (gridExists && !gridDomExists) {
123
106
  // Clean up orphaned grid reference
124
107
  AgGrid.grids[gridId].destroy();
125
108
  delete AgGrid.grids[gridId];
126
109
  }
127
-
128
110
  htmls(
129
111
  `#${id}`,
130
- await ObjectLayerManagement.RenderTable({
131
- Elements,
112
+ await ObjectLayerManagement.instance({
113
+ appStore,
132
114
  idModal,
133
115
  }),
134
116
  );
135
- },
136
-
137
- loadObjectLayer: async function (objectLayerId, Elements, options = {}) {
117
+ }
118
+ static async loadObjectLayer(objectLayerId, appStore, options = {}) {
138
119
  const { skipWebp = false } = options;
139
120
  const id = 'object-layer-engine-viewer';
140
-
141
121
  // Check if DOM element exists
142
122
  if (!s(`#${id}`)) {
143
123
  logger.warn('ObjectLayerEngineViewer DOM not ready for loadObjectLayer');
144
124
  return;
145
125
  }
146
-
147
126
  try {
148
127
  // Load metadata first
149
128
  const { status: metaStatus, data: metadata } = await ObjectLayerService.getMetadata({ id: objectLayerId });
150
-
151
129
  if (metaStatus !== 'success' || !metadata) {
152
130
  throw new Error('Failed to load object layer metadata');
153
131
  }
154
-
155
- this.Data.objectLayer = metadata;
156
-
132
+ ObjectLayerEngineViewer.Data.objectLayer = metadata;
157
133
  if (metadata.atlasSpriteSheetId) {
158
134
  const { status: atlasStatus, data: atlasData } = await AtlasSpriteSheetService.get({
159
135
  id: metadata.atlasSpriteSheetId,
160
136
  });
161
137
  if (atlasStatus === 'success') {
162
- this.Data.atlasSpriteSheet = atlasData;
138
+ ObjectLayerEngineViewer.Data.atlasSpriteSheet = atlasData;
163
139
  }
164
140
  } else {
165
- this.Data.atlasSpriteSheet = null;
141
+ ObjectLayerEngineViewer.Data.atlasSpriteSheet = null;
166
142
  }
167
-
168
143
  // Load frame counts for all directions
169
144
  const { status: frameStatus, data: frameData } = await ObjectLayerService.getFrameCounts({ id: objectLayerId });
170
-
171
145
  if (frameStatus !== 'success' || !frameData) {
172
146
  throw new Error('Failed to load frame counts');
173
147
  }
174
-
175
- this.Data.frameCounts = frameData.frameCounts;
148
+ ObjectLayerEngineViewer.Data.frameCounts = frameData.frameCounts;
176
149
  // Priority order for directions
177
150
  const directions = ['down', 'up', 'left', 'right'];
178
151
  // Priority order for modes
179
152
  const modes = ['idle', 'walking'];
180
- this.Data.currentDirection = 'down';
181
- this.Data.currentMode = 'idle';
182
-
183
- // Render the viewer UI
184
- await this.renderViewer({ Elements });
185
-
153
+ ObjectLayerEngineViewer.Data.currentDirection = 'down';
154
+ ObjectLayerEngineViewer.Data.currentMode = 'idle';
155
+ // instance the viewer UI
156
+ await ObjectLayerEngineViewer.renderViewer({ appStore });
186
157
  // Generate WebP
187
158
  if (!skipWebp) {
188
- await this.generateWebp();
159
+ await ObjectLayerEngineViewer.generateWebp();
189
160
  }
190
161
  } catch (error) {
191
162
  logger.error('Error loading object layer:', error);
@@ -193,7 +164,6 @@ const ObjectLayerEngineViewer = {
193
164
  html: `Failed to load object layer: ${error.message}`,
194
165
  status: 'error',
195
166
  });
196
-
197
167
  htmls(
198
168
  `#${id}`,
199
169
  html`
@@ -206,42 +176,34 @@ const ObjectLayerEngineViewer = {
206
176
  `,
207
177
  );
208
178
  }
209
- },
210
-
211
- renderViewer: async function ({ Elements }) {
179
+ }
180
+ static async renderViewer({ appStore }) {
212
181
  const id = 'object-layer-engine-viewer';
213
- const { objectLayer, frameCounts } = this.Data;
214
-
182
+ const { objectLayer, frameCounts } = ObjectLayerEngineViewer.Data;
215
183
  if (!objectLayer || !frameCounts) return;
216
-
217
184
  // Check if DOM element exists
218
185
  if (!s(`#${id}`)) {
219
186
  logger.warn('ObjectLayerEngineViewer DOM not ready for renderViewer');
220
187
  return;
221
188
  }
222
-
223
189
  const itemType = objectLayer.data.item.type;
224
190
  const itemId = objectLayer.data.item.id;
225
191
  const itemDescription = objectLayer.data.item.description || '';
226
192
  const itemActivable = objectLayer.data.item.activable || false;
227
-
228
193
  // Get ledger data
229
194
  const ledger = objectLayer.data.ledger || {};
230
195
  const ledgerType = ledger.type || '';
231
196
  const ledgerAddress = ledger.address || '';
232
-
233
197
  // Get stats data
234
198
  const stats = objectLayer.data.stats || {};
235
-
236
199
  // Helper function to check if direction/mode has frames
237
200
  const hasFrames = (direction, mode) => {
238
- const numericCode = this.getDirectionCode(direction, mode);
201
+ const numericCode = ObjectLayerEngineViewer.getDirectionCode(direction, mode);
239
202
  return numericCode && frameCounts[numericCode] && frameCounts[numericCode] > 0;
240
203
  };
241
-
242
204
  // Helper function to get frame count
243
205
  const getFrameCount = (direction, mode) => {
244
- const numericCode = this.getDirectionCode(direction, mode);
206
+ const numericCode = ObjectLayerEngineViewer.getDirectionCode(direction, mode);
245
207
  return numericCode ? frameCounts[numericCode] || 0 : 0;
246
208
  };
247
209
  ThemeEvents[id] = () => {
@@ -662,7 +624,7 @@ const ObjectLayerEngineViewer = {
662
624
  <div class="hide style-${id}"></div>
663
625
 
664
626
  <div class="object-layer-viewer-container">
665
- ${this.Data.isGeneratingAtlas
627
+ ${ObjectLayerEngineViewer.Data.isGeneratingAtlas
666
628
  ? html`
667
629
  <div
668
630
  style="display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 500px; gap: 20px; text-align: center;"
@@ -789,7 +751,7 @@ const ObjectLayerEngineViewer = {
789
751
  <span>WebP</span>
790
752
  </button>
791
753
  <div class="webp-canvas-container chess in" id="webp-canvas-container">
792
- ${!this.Data.webp
754
+ ${!ObjectLayerEngineViewer.Data.webp
793
755
  ? html`
794
756
  <div class="webp-placeholder">
795
757
  <i class="fa-solid fa-image"></i>
@@ -811,47 +773,55 @@ const ObjectLayerEngineViewer = {
811
773
  <h4><i class="fa-solid fa-compass"></i> Direction</h4>
812
774
  <div class="button-group">
813
775
  <button
814
- class="control-btn ${this.Data.currentDirection === 'up' ? 'active' : ''}"
776
+ class="control-btn ${ObjectLayerEngineViewer.Data.currentDirection === 'up' ? 'active' : ''}"
815
777
  data-direction="up"
816
- ${!hasFrames('up', this.Data.currentMode) ? 'disabled' : ''}
778
+ ${!hasFrames('up', ObjectLayerEngineViewer.Data.currentMode) ? 'disabled' : ''}
817
779
  >
818
780
  <i class="fa-solid fa-arrow-up"></i>
819
781
  <span>Up</span>
820
- ${hasFrames('up', this.Data.currentMode)
821
- ? html`<span class="frame-count">(${getFrameCount('up', this.Data.currentMode)})</span>`
782
+ ${hasFrames('up', ObjectLayerEngineViewer.Data.currentMode)
783
+ ? html`<span class="frame-count"
784
+ >(${getFrameCount('up', ObjectLayerEngineViewer.Data.currentMode)})</span
785
+ >`
822
786
  : ''}
823
787
  </button>
824
788
  <button
825
- class="control-btn ${this.Data.currentDirection === 'down' ? 'active' : ''}"
789
+ class="control-btn ${ObjectLayerEngineViewer.Data.currentDirection === 'down' ? 'active' : ''}"
826
790
  data-direction="down"
827
- ${!hasFrames('down', this.Data.currentMode) ? 'disabled' : ''}
791
+ ${!hasFrames('down', ObjectLayerEngineViewer.Data.currentMode) ? 'disabled' : ''}
828
792
  >
829
793
  <i class="fa-solid fa-arrow-down"></i>
830
794
  <span>Down</span>
831
- ${hasFrames('down', this.Data.currentMode)
832
- ? html`<span class="frame-count">(${getFrameCount('down', this.Data.currentMode)})</span>`
795
+ ${hasFrames('down', ObjectLayerEngineViewer.Data.currentMode)
796
+ ? html`<span class="frame-count"
797
+ >(${getFrameCount('down', ObjectLayerEngineViewer.Data.currentMode)})</span
798
+ >`
833
799
  : ''}
834
800
  </button>
835
801
  <button
836
- class="control-btn ${this.Data.currentDirection === 'left' ? 'active' : ''}"
802
+ class="control-btn ${ObjectLayerEngineViewer.Data.currentDirection === 'left' ? 'active' : ''}"
837
803
  data-direction="left"
838
- ${!hasFrames('left', this.Data.currentMode) ? 'disabled' : ''}
804
+ ${!hasFrames('left', ObjectLayerEngineViewer.Data.currentMode) ? 'disabled' : ''}
839
805
  >
840
806
  <i class="fa-solid fa-arrow-left"></i>
841
807
  <span>Left</span>
842
- ${hasFrames('left', this.Data.currentMode)
843
- ? html`<span class="frame-count">(${getFrameCount('left', this.Data.currentMode)})</span>`
808
+ ${hasFrames('left', ObjectLayerEngineViewer.Data.currentMode)
809
+ ? html`<span class="frame-count"
810
+ >(${getFrameCount('left', ObjectLayerEngineViewer.Data.currentMode)})</span
811
+ >`
844
812
  : ''}
845
813
  </button>
846
814
  <button
847
- class="control-btn ${this.Data.currentDirection === 'right' ? 'active' : ''}"
815
+ class="control-btn ${ObjectLayerEngineViewer.Data.currentDirection === 'right' ? 'active' : ''}"
848
816
  data-direction="right"
849
- ${!hasFrames('right', this.Data.currentMode) ? 'disabled' : ''}
817
+ ${!hasFrames('right', ObjectLayerEngineViewer.Data.currentMode) ? 'disabled' : ''}
850
818
  >
851
819
  <i class="fa-solid fa-arrow-right"></i>
852
820
  <span>Right</span>
853
- ${hasFrames('right', this.Data.currentMode)
854
- ? html`<span class="frame-count">(${getFrameCount('right', this.Data.currentMode)})</span>`
821
+ ${hasFrames('right', ObjectLayerEngineViewer.Data.currentMode)
822
+ ? html`<span class="frame-count"
823
+ >(${getFrameCount('right', ObjectLayerEngineViewer.Data.currentMode)})</span
824
+ >`
855
825
  : ''}
856
826
  </button>
857
827
  </div>
@@ -861,28 +831,28 @@ const ObjectLayerEngineViewer = {
861
831
  <h4><i class="fa-solid fa-person-running"></i> Mode</h4>
862
832
  <div class="button-group">
863
833
  <button
864
- class="control-btn ${this.Data.currentMode === 'idle' ? 'active' : ''}"
834
+ class="control-btn ${ObjectLayerEngineViewer.Data.currentMode === 'idle' ? 'active' : ''}"
865
835
  data-mode="idle"
866
- ${!hasFrames(this.Data.currentDirection, 'idle') ? 'disabled' : ''}
836
+ ${!hasFrames(ObjectLayerEngineViewer.Data.currentDirection, 'idle') ? 'disabled' : ''}
867
837
  >
868
838
  <i class="fa-solid fa-user"></i>
869
839
  <span>Idle</span>
870
- ${hasFrames(this.Data.currentDirection, 'idle')
840
+ ${hasFrames(ObjectLayerEngineViewer.Data.currentDirection, 'idle')
871
841
  ? html`<span class="frame-count"
872
- >(${getFrameCount(this.Data.currentDirection, 'idle')})</span
842
+ >(${getFrameCount(ObjectLayerEngineViewer.Data.currentDirection, 'idle')})</span
873
843
  >`
874
844
  : ''}
875
845
  </button>
876
846
  <button
877
- class="control-btn ${this.Data.currentMode === 'walking' ? 'active' : ''}"
847
+ class="control-btn ${ObjectLayerEngineViewer.Data.currentMode === 'walking' ? 'active' : ''}"
878
848
  data-mode="walking"
879
- ${!hasFrames(this.Data.currentDirection, 'walking') ? 'disabled' : ''}
849
+ ${!hasFrames(ObjectLayerEngineViewer.Data.currentDirection, 'walking') ? 'disabled' : ''}
880
850
  >
881
851
  <i class="fa-solid fa-person-walking"></i>
882
852
  <span>Walking</span>
883
- ${hasFrames(this.Data.currentDirection, 'walking')
853
+ ${hasFrames(ObjectLayerEngineViewer.Data.currentDirection, 'walking')
884
854
  ? html`<span class="frame-count"
885
- >(${getFrameCount(this.Data.currentDirection, 'walking')})</span
855
+ >(${getFrameCount(ObjectLayerEngineViewer.Data.currentDirection, 'walking')})</span
886
856
  >`
887
857
  : ''}
888
858
  </button>
@@ -892,16 +862,17 @@ const ObjectLayerEngineViewer = {
892
862
  <div class="control-group">
893
863
  <h4><i class="fa-solid fa-file-image"></i> Atlas Sprite Sheet</h4>
894
864
  <div class="button-group" style="flex-direction: column; align-items: flex-start;">
895
- ${this.Data.atlasSpriteSheet
865
+ ${ObjectLayerEngineViewer.Data.atlasSpriteSheet
896
866
  ? html`
897
867
  <div class="atlas-preview-container">
898
868
  ${
899
- this.Data.atlasSpriteSheet.fileId
869
+ ObjectLayerEngineViewer.Data.atlasSpriteSheet.fileId
900
870
  ? html`
901
871
  <div class="atlas-img-wrapper">
902
872
  <img
903
- src="${getProxyPath()}api/file/blob/${this.Data.atlasSpriteSheet.fileId._id ||
904
- this.Data.atlasSpriteSheet.fileId}"
873
+ src="${getProxyPath()}api/file/blob/${ObjectLayerEngineViewer.Data
874
+ .atlasSpriteSheet.fileId._id ||
875
+ ObjectLayerEngineViewer.Data.atlasSpriteSheet.fileId}"
905
876
  class="in atlas-img-preview"
906
877
  />
907
878
  </div>
@@ -915,14 +886,14 @@ const ObjectLayerEngineViewer = {
915
886
  <div class="atlas-metadata-grid">
916
887
  <div>
917
888
  <p style="padding: 2px"><strong class="item-data-key-label">ID:</strong></p>
918
- <p style="padding: 2px" font-size: 12px;">${this.Data.atlasSpriteSheet._id}</p>
889
+ <p style="padding: 2px" font-size: 12px;">${ObjectLayerEngineViewer.Data.atlasSpriteSheet._id}</p>
919
890
  </div>
920
891
  ${
921
- this.Data.atlasSpriteSheet.cid
892
+ ObjectLayerEngineViewer.Data.atlasSpriteSheet.cid
922
893
  ? html`<div style="grid-column: 1 / -1;">
923
894
  <p style="padding: 2px"><strong class="item-data-key-label">IPFS CID:</strong></p>
924
895
  <p class="ipfs-cid-value" style="padding: 2px;">
925
- ${this.Data.atlasSpriteSheet.cid}
896
+ ${ObjectLayerEngineViewer.Data.atlasSpriteSheet.cid}
926
897
  </p>
927
898
  </div>`
928
899
  : ''
@@ -930,18 +901,16 @@ const ObjectLayerEngineViewer = {
930
901
  <div>
931
902
  <p style="padding: 2px"><strong class="item-data-key-label">Dimensions:</strong></p>
932
903
  <p style="padding: 2px">
933
- ${this.Data.atlasSpriteSheet.metadata.atlasWidth}x${
934
- this.Data.atlasSpriteSheet.metadata.atlasHeight
935
- }
904
+ ${ObjectLayerEngineViewer.Data.atlasSpriteSheet.metadata.atlasWidth}x${ObjectLayerEngineViewer.Data.atlasSpriteSheet.metadata.atlasHeight}
936
905
  </p>
937
906
  </div>
938
907
  <div>
939
908
  <p style="padding: 2px"><strong class="item-data-key-label">Cell Dim:</strong></p>
940
- <p style="padding: 2px">${this.Data.atlasSpriteSheet.metadata.cellPixelDim}px</p>
909
+ <p style="padding: 2px">${ObjectLayerEngineViewer.Data.atlasSpriteSheet.metadata.cellPixelDim}px</p>
941
910
  </div>
942
911
  <div>
943
912
  <p style="padding: 2px"><strong class="item-data-key-label">Item Key:</strong></p>
944
- <p style="padding: 2px">${this.Data.atlasSpriteSheet.metadata.itemKey}</p>
913
+ <p style="padding: 2px">${ObjectLayerEngineViewer.Data.atlasSpriteSheet.metadata.itemKey}</p>
945
914
  </div>
946
915
  </div>
947
916
  </div>
@@ -994,37 +963,43 @@ const ObjectLayerEngineViewer = {
994
963
  );
995
964
  ThemeEvents[id]();
996
965
  // Attach event listeners
997
- this.attachEventListeners({ Elements });
998
-
966
+ ObjectLayerEngineViewer.attachEventListeners({ appStore });
999
967
  // If we already have a webp loaded, display it without re-generating
1000
- if (this.Data.webp) {
1001
- this.displayWebp();
968
+ if (ObjectLayerEngineViewer.Data.webp) {
969
+ ObjectLayerEngineViewer.displayWebp();
1002
970
  }
1003
-
1004
971
  // Initialize metadata JSON editor
1005
- this.initMetadataJsonEditor();
1006
- },
1007
-
1008
- displayWebp: async function () {
1009
- const { webp, webpMetadata } = this.Data;
972
+ ObjectLayerEngineViewer.initMetadataJsonEditor();
973
+ }
974
+ static async displayWebp() {
975
+ const { webp, webpMetadata } = ObjectLayerEngineViewer.Data;
1010
976
  if (!webp || !webpMetadata) return;
1011
-
1012
977
  const { frameCount, frameDuration, currentDirection, currentMode, numericCode } = webpMetadata;
1013
-
1014
978
  const container = s('#webp-canvas-container');
1015
- if (container) {
1016
- // Clear container
1017
- container.innerHTML = '';
1018
-
1019
- // Create and append image
1020
- const img = document.createElement('img');
1021
- img.src = webp;
979
+ if (!container) return;
980
+ // Remove one-time placeholder without destroying the rest of the container
981
+ // (clearing innerHTML would also destroy #webp-loading-overlay, breaking showLoading)
982
+ const placeholder = container.querySelector('.webp-placeholder');
983
+ if (placeholder) placeholder.remove();
984
+ // Reuse the existing <img> element or create one — never nuke the container
985
+ let img = container.querySelector('img');
986
+ if (!img) {
987
+ img = document.createElement('img');
1022
988
  img.alt = 'WebP Animation';
1023
- container.appendChild(img);
1024
-
1025
- // Create and append info badge
1026
- const infoBadge = document.createElement('div');
1027
- infoBadge.className = 'webp-info-badge';
989
+ // Insert before the loading overlay so the overlay stays on top
990
+ const overlay = container.querySelector('#webp-loading-overlay');
991
+ container.insertBefore(img, overlay || null);
992
+ }
993
+ img.src = webp;
994
+ // Update info badge in-place or create it once
995
+ const displayArea = s('.webp-display-area');
996
+ if (displayArea) {
997
+ let infoBadge = displayArea.querySelector('.webp-info-badge');
998
+ if (!infoBadge) {
999
+ infoBadge = document.createElement('div');
1000
+ infoBadge.className = 'webp-info-badge';
1001
+ displayArea.appendChild(infoBadge);
1002
+ }
1028
1003
  infoBadge.innerHTML = html`
1029
1004
  <span class="info-label" style="margin-left: 8px;">Frames:</span>
1030
1005
  <span>${frameCount}</span><br />
@@ -1037,20 +1012,11 @@ const ObjectLayerEngineViewer = {
1037
1012
  <span class="info-label" style="margin-left: 8px;">Code:</span>
1038
1013
  <span>${numericCode}</span>
1039
1014
  `;
1040
- const displayArea = s('.webp-display-area');
1041
- if (displayArea) {
1042
- // Remove old badge if exists
1043
- const oldBadge = s('.webp-info-badge');
1044
- if (oldBadge) oldBadge.remove();
1045
- displayArea.appendChild(infoBadge);
1046
- }
1047
1015
  }
1048
- },
1049
-
1050
- initMetadataJsonEditor: async function () {
1016
+ }
1017
+ static async initMetadataJsonEditor() {
1051
1018
  const container = s('#metadata-json-editor-container');
1052
1019
  if (!container) return;
1053
-
1054
1020
  // Ensure vanilla-jsoneditor dark theme CSS is loaded
1055
1021
  if (!s('.jse-dark-theme-link')) {
1056
1022
  append(
@@ -1063,21 +1029,17 @@ const ObjectLayerEngineViewer = {
1063
1029
  />`,
1064
1030
  );
1065
1031
  }
1066
-
1067
1032
  // Destroy previous instance if any
1068
- if (this.Data.metadataJsonEditor) {
1069
- this.Data.metadataJsonEditor.destroy();
1070
- this.Data.metadataJsonEditor = null;
1033
+ if (ObjectLayerEngineViewer.Data.metadataJsonEditor) {
1034
+ ObjectLayerEngineViewer.Data.metadataJsonEditor.destroy();
1035
+ ObjectLayerEngineViewer.Data.metadataJsonEditor = null;
1071
1036
  }
1072
-
1073
- const objectLayerId = this.Data.objectLayer?._id;
1037
+ const objectLayerId = ObjectLayerEngineViewer.Data.objectLayer?._id;
1074
1038
  if (!objectLayerId) return;
1075
-
1076
1039
  try {
1077
1040
  const response = await ObjectLayerService.getMetadata({ id: objectLayerId });
1078
1041
  const metadataContent = response.status === 'success' && response.data ? response.data : response;
1079
-
1080
- this.Data.metadataJsonEditor = createJSONEditor({
1042
+ ObjectLayerEngineViewer.Data.metadataJsonEditor = createJSONEditor({
1081
1043
  target: container,
1082
1044
  props: {
1083
1045
  content: { json: metadataContent },
@@ -1088,13 +1050,11 @@ const ObjectLayerEngineViewer = {
1088
1050
  mode: 'tree',
1089
1051
  },
1090
1052
  });
1091
-
1092
1053
  // Apply dark theme class based on current theme
1093
- this._applyJsonEditorTheme();
1094
-
1054
+ ObjectLayerEngineViewer._applyJsonEditorTheme();
1095
1055
  // Register theme event to toggle dark/light on the JSON editor
1096
1056
  ThemeEvents['metadata-json-editor-theme'] = () => {
1097
- this._applyJsonEditorTheme();
1057
+ ObjectLayerEngineViewer._applyJsonEditorTheme();
1098
1058
  };
1099
1059
  } catch (err) {
1100
1060
  logger.warn('Failed to initialize metadata JSON editor:', err);
@@ -1102,9 +1062,8 @@ const ObjectLayerEngineViewer = {
1102
1062
  Failed to load metadata JSON
1103
1063
  </div>`;
1104
1064
  }
1105
- },
1106
-
1107
- _applyJsonEditorTheme: function () {
1065
+ }
1066
+ static _applyJsonEditorTheme() {
1108
1067
  const container = s('#metadata-json-editor-container');
1109
1068
  if (!container) return;
1110
1069
  if (darkTheme) {
@@ -1112,14 +1071,11 @@ const ObjectLayerEngineViewer = {
1112
1071
  } else {
1113
1072
  container.classList.remove('jse-theme-dark');
1114
1073
  }
1115
- },
1116
-
1117
- deleteObjectLayer: async function ({ Elements } = {}) {
1118
- const objectLayerId = this.Data.objectLayer?._id;
1074
+ }
1075
+ static async deleteObjectLayer({ appStore } = {}) {
1076
+ const objectLayerId = ObjectLayerEngineViewer.Data.objectLayer?._id;
1119
1077
  if (!objectLayerId) return;
1120
-
1121
- const itemId = this.Data.objectLayer?.data?.item?.id || objectLayerId;
1122
-
1078
+ const itemId = ObjectLayerEngineViewer.Data.objectLayer?.data?.item?.id || objectLayerId;
1123
1079
  const confirmResult = await Modal.RenderConfirm({
1124
1080
  id: 'delete-object-layer-confirm',
1125
1081
  html: async () => html`
@@ -1132,9 +1088,7 @@ const ObjectLayerEngineViewer = {
1132
1088
  </div>
1133
1089
  `,
1134
1090
  });
1135
-
1136
1091
  if (confirmResult.status !== 'confirm') return;
1137
-
1138
1092
  try {
1139
1093
  const result = await ObjectLayerService.delete({ id: objectLayerId });
1140
1094
  if (result.status === 'success') {
@@ -1142,20 +1096,18 @@ const ObjectLayerEngineViewer = {
1142
1096
  html: `Object layer "${itemId}" deleted successfully`,
1143
1097
  status: 'success',
1144
1098
  });
1145
-
1146
1099
  // Clean up JSON editor and its theme event
1147
- if (this.Data.metadataJsonEditor) {
1148
- this.Data.metadataJsonEditor.destroy();
1149
- this.Data.metadataJsonEditor = null;
1100
+ if (ObjectLayerEngineViewer.Data.metadataJsonEditor) {
1101
+ ObjectLayerEngineViewer.Data.metadataJsonEditor.destroy();
1102
+ ObjectLayerEngineViewer.Data.metadataJsonEditor = null;
1150
1103
  }
1151
1104
  delete ThemeEvents['metadata-json-editor-theme'];
1152
-
1153
1105
  // Navigate back to list
1154
- this.Data.currentObjectId = undefined;
1155
- this.Data.objectLayer = null;
1156
- this.Data.webp = null;
1157
- this.Data.webpMetadata = null;
1158
- this.Data.atlasSpriteSheet = null;
1106
+ ObjectLayerEngineViewer.Data.currentObjectId = undefined;
1107
+ ObjectLayerEngineViewer.Data.objectLayer = null;
1108
+ ObjectLayerEngineViewer.Data.webp = null;
1109
+ ObjectLayerEngineViewer.Data.webpMetadata = null;
1110
+ ObjectLayerEngineViewer.Data.atlasSpriteSheet = null;
1159
1111
  setQueryParams({ id: null }, { replace: false });
1160
1112
  } else {
1161
1113
  throw new Error(result.message || 'Failed to delete object layer');
@@ -1167,109 +1119,100 @@ const ObjectLayerEngineViewer = {
1167
1119
  status: 'error',
1168
1120
  });
1169
1121
  }
1170
- },
1171
-
1172
- attachEventListeners: function ({ Elements }) {
1122
+ }
1123
+ static attachEventListeners({ appStore }) {
1173
1124
  // Direction buttons
1174
1125
  const directionButtons = document.querySelectorAll('[data-direction]');
1175
1126
  directionButtons.forEach((btn) => {
1176
1127
  btn.addEventListener('click', async (e) => {
1177
1128
  if (e.currentTarget.disabled) return;
1178
1129
  const direction = e.currentTarget.getAttribute('data-direction');
1179
- if (direction !== this.Data.currentDirection) {
1180
- this.Data.currentDirection = direction;
1181
- await this.renderViewer({ Elements });
1182
- // attachEventListeners is already called inside renderViewer
1183
- await this.generateWebp();
1130
+ if (direction !== ObjectLayerEngineViewer.Data.currentDirection) {
1131
+ ObjectLayerEngineViewer.Data.currentDirection = direction;
1132
+ // Update button active states without re-rendering the full viewer (prevents flicker)
1133
+ ObjectLayerEngineViewer._updateControlsState();
1134
+ await ObjectLayerEngineViewer.generateWebp();
1184
1135
  }
1185
1136
  });
1186
1137
  });
1187
-
1188
1138
  // Mode buttons
1189
1139
  const modeButtons = document.querySelectorAll('[data-mode]');
1190
1140
  modeButtons.forEach((btn) => {
1191
1141
  btn.addEventListener('click', async (e) => {
1192
1142
  if (e.currentTarget.disabled) return;
1193
1143
  const mode = e.currentTarget.getAttribute('data-mode');
1194
- if (mode !== this.Data.currentMode) {
1195
- this.Data.currentMode = mode;
1196
- await this.renderViewer({ Elements });
1197
- // attachEventListeners is already called inside renderViewer
1198
- await this.generateWebp();
1144
+ if (mode !== ObjectLayerEngineViewer.Data.currentMode) {
1145
+ ObjectLayerEngineViewer.Data.currentMode = mode;
1146
+ // Update button active states without re-rendering the full viewer (prevents flicker)
1147
+ ObjectLayerEngineViewer._updateControlsState();
1148
+ await ObjectLayerEngineViewer.generateWebp();
1199
1149
  }
1200
1150
  });
1201
1151
  });
1202
-
1203
1152
  // Download button
1204
1153
  const downloadBtn = s('#download-webp-btn');
1205
1154
  if (downloadBtn) {
1206
1155
  downloadBtn.addEventListener('click', () => {
1207
- this.downloadWebp();
1156
+ ObjectLayerEngineViewer.downloadWebp();
1208
1157
  });
1209
1158
  }
1210
-
1211
1159
  // Return to list button
1212
1160
  const listBtn = s('#return-to-list-btn');
1213
1161
  if (listBtn) {
1214
1162
  listBtn.addEventListener('click', async () => {
1215
1163
  // Clear object data and reset state
1216
- this.Data.webp = null;
1217
- this.Data.webpMetadata = null;
1218
- this.Data.objectLayer = null;
1219
- this.Data.frameCounts = null;
1220
-
1164
+ ObjectLayerEngineViewer.Data.webp = null;
1165
+ ObjectLayerEngineViewer.Data.webpMetadata = null;
1166
+ ObjectLayerEngineViewer.Data.objectLayer = null;
1167
+ ObjectLayerEngineViewer.Data.frameCounts = null;
1221
1168
  // Set currentObjectId to null BEFORE setQueryParams so the
1222
1169
  // listenQueryParamsChange listener sees the id already matches
1223
1170
  // and skips calling Reload (avoids double-render race condition)
1224
- this.Data.currentObjectId = null;
1225
-
1171
+ ObjectLayerEngineViewer.Data.currentObjectId = null;
1226
1172
  // Update the URL to remove the id parameter
1227
1173
  setQueryParams({ id: null }, { replace: false });
1228
-
1229
1174
  // Directly render the list view instead of relying on the
1230
1175
  // listener → Reload → renderEmpty chain which can silently
1231
1176
  // fail when the URL was already clean or currentObjectId
1232
1177
  // was already null
1233
- await this.renderEmpty({ Elements });
1178
+ await ObjectLayerEngineViewer.renderEmpty({ appStore });
1234
1179
  });
1235
1180
  }
1236
-
1237
1181
  // Edit button
1238
1182
  const editBtn = s('#edit-object-layer-btn');
1239
1183
  if (editBtn) {
1240
1184
  editBtn.addEventListener('click', () => {
1241
- this.toEngine();
1185
+ ObjectLayerEngineViewer.toEngine();
1242
1186
  });
1243
1187
  }
1244
-
1245
1188
  // Delete button
1246
1189
  const deleteBtn = s('#delete-object-layer-btn');
1247
1190
  if (deleteBtn) {
1248
1191
  deleteBtn.addEventListener('click', async () => {
1249
- await this.deleteObjectLayer({ Elements });
1192
+ await ObjectLayerEngineViewer.deleteObjectLayer({ appStore });
1250
1193
  });
1251
1194
  }
1252
-
1253
1195
  // Atlas buttons
1254
1196
  if (s('#generate-atlas-btn')) {
1255
1197
  EventsUI.onClick('#generate-atlas-btn', async () => {
1256
- await this.generateAtlas({ Elements });
1198
+ await ObjectLayerEngineViewer.generateAtlas({ appStore });
1257
1199
  });
1258
1200
  }
1259
-
1260
1201
  const removeAtlasBtn = s('#remove-atlas-btn');
1261
1202
  if (removeAtlasBtn) {
1262
1203
  removeAtlasBtn.addEventListener('click', async () => {
1263
- await this.removeAtlas({ Elements });
1204
+ await ObjectLayerEngineViewer.removeAtlas({ appStore });
1264
1205
  });
1265
1206
  }
1266
-
1267
1207
  const downloadAtlasPngBtn = s('#download-atlas-png-btn');
1268
1208
  if (downloadAtlasPngBtn) {
1269
1209
  downloadAtlasPngBtn.addEventListener('click', () => {
1270
1210
  const fileId =
1271
- this.Data && this.Data.atlasSpriteSheet && this.Data.atlasSpriteSheet.fileId
1272
- ? this.Data.atlasSpriteSheet.fileId._id || this.Data.atlasSpriteSheet.fileId
1211
+ ObjectLayerEngineViewer.Data &&
1212
+ ObjectLayerEngineViewer.Data.atlasSpriteSheet &&
1213
+ ObjectLayerEngineViewer.Data.atlasSpriteSheet.fileId
1214
+ ? ObjectLayerEngineViewer.Data.atlasSpriteSheet.fileId._id ||
1215
+ ObjectLayerEngineViewer.Data.atlasSpriteSheet.fileId
1273
1216
  : null;
1274
1217
  if (!fileId) {
1275
1218
  NotificationManager.Push({
@@ -1281,47 +1224,43 @@ const ObjectLayerEngineViewer = {
1281
1224
  const url = `${getProxyPath()}api/file/blob/${fileId}`;
1282
1225
  const a = document.createElement('a');
1283
1226
  a.href = url;
1284
- a.download = `${this.Data.atlasSpriteSheet.metadata.itemKey}-atlas.png`;
1227
+ a.download = `${ObjectLayerEngineViewer.Data.atlasSpriteSheet.metadata.itemKey}-atlas.png`;
1285
1228
  document.body.appendChild(a);
1286
1229
  a.click();
1287
1230
  document.body.removeChild(a);
1288
1231
  });
1289
1232
  }
1290
-
1291
1233
  const downloadAtlasJsonBtn = s('#download-atlas-json-btn');
1292
1234
  if (downloadAtlasJsonBtn) {
1293
1235
  downloadAtlasJsonBtn.addEventListener('click', () => {
1294
- const blob = new Blob([JSON.stringify(this.Data.atlasSpriteSheet.metadata, null, 2)], {
1236
+ const blob = new Blob([JSON.stringify(ObjectLayerEngineViewer.Data.atlasSpriteSheet.metadata, null, 2)], {
1295
1237
  type: 'application/json',
1296
1238
  });
1297
1239
  const url = URL.createObjectURL(blob);
1298
1240
  const a = document.createElement('a');
1299
1241
  a.href = url;
1300
- a.download = `${this.Data.atlasSpriteSheet.metadata.itemKey}-atlas-metadata.json`;
1242
+ a.download = `${ObjectLayerEngineViewer.Data.atlasSpriteSheet.metadata.itemKey}-atlas-metadata.json`;
1301
1243
  document.body.appendChild(a);
1302
1244
  a.click();
1303
1245
  document.body.removeChild(a);
1304
1246
  URL.revokeObjectURL(url);
1305
1247
  });
1306
1248
  }
1307
- },
1308
-
1309
- generateAtlas: async function ({ Elements } = {}) {
1310
- const objectLayerId = this.Data.objectLayer._id;
1311
- this.Data.isGeneratingAtlas = true;
1312
- await this.renderViewer({ Elements });
1313
-
1249
+ }
1250
+ static async generateAtlas({ appStore } = {}) {
1251
+ const objectLayerId = ObjectLayerEngineViewer.Data.objectLayer._id;
1252
+ ObjectLayerEngineViewer.Data.isGeneratingAtlas = true;
1253
+ await ObjectLayerEngineViewer.renderViewer({ appStore });
1314
1254
  try {
1315
1255
  const { status, data, message } = await AtlasSpriteSheetService.generateAtlas({ id: objectLayerId });
1316
-
1317
1256
  if (status === 'success') {
1318
1257
  NotificationManager.Push({
1319
1258
  html: 'Atlas sprite sheet generated successfully',
1320
1259
  status: 'success',
1321
1260
  });
1322
1261
  // Reset generating flag before reload so renderViewer shows updated content
1323
- this.Data.isGeneratingAtlas = false;
1324
- await this.Reload({ Elements, force: true, skipWebp: true });
1262
+ ObjectLayerEngineViewer.Data.isGeneratingAtlas = false;
1263
+ await ObjectLayerEngineViewer.Reload({ appStore, force: true, skipWebp: true });
1325
1264
  return;
1326
1265
  } else {
1327
1266
  throw new Error(message || 'Failed to generate atlas');
@@ -1333,14 +1272,13 @@ const ObjectLayerEngineViewer = {
1333
1272
  status: 'error',
1334
1273
  });
1335
1274
  } finally {
1336
- if (this.Data.isGeneratingAtlas) {
1337
- this.Data.isGeneratingAtlas = false;
1338
- await this.renderViewer({ Elements });
1275
+ if (ObjectLayerEngineViewer.Data.isGeneratingAtlas) {
1276
+ ObjectLayerEngineViewer.Data.isGeneratingAtlas = false;
1277
+ await ObjectLayerEngineViewer.renderViewer({ appStore });
1339
1278
  }
1340
1279
  }
1341
- },
1342
-
1343
- removeAtlas: async function ({ Elements } = {}) {
1280
+ }
1281
+ static async removeAtlas({ appStore } = {}) {
1344
1282
  const confirmResult = await Modal.RenderConfirm({
1345
1283
  id: 'remove-atlas-confirm',
1346
1284
  html: async () => html`
@@ -1349,26 +1287,22 @@ const ObjectLayerEngineViewer = {
1349
1287
  </div>
1350
1288
  `,
1351
1289
  });
1352
-
1353
1290
  if (confirmResult.status !== 'confirm') {
1354
1291
  return;
1355
1292
  }
1356
-
1357
- const objectLayerId = this.Data.objectLayer._id;
1358
- this.Data.isGeneratingAtlas = true;
1359
- await this.renderViewer({ Elements });
1360
-
1293
+ const objectLayerId = ObjectLayerEngineViewer.Data.objectLayer._id;
1294
+ ObjectLayerEngineViewer.Data.isGeneratingAtlas = true;
1295
+ await ObjectLayerEngineViewer.renderViewer({ appStore });
1361
1296
  try {
1362
1297
  const { status, message } = await AtlasSpriteSheetService.deleteByObjectLayerId({ id: objectLayerId });
1363
-
1364
1298
  if (status === 'success') {
1365
1299
  NotificationManager.Push({
1366
1300
  html: 'Atlas sprite sheet removed successfully',
1367
1301
  status: 'success',
1368
1302
  });
1369
1303
  // Reset generating flag before reload so renderViewer shows updated content
1370
- this.Data.isGeneratingAtlas = false;
1371
- await this.Reload({ Elements, force: true, skipWebp: true });
1304
+ ObjectLayerEngineViewer.Data.isGeneratingAtlas = false;
1305
+ await ObjectLayerEngineViewer.Reload({ appStore, force: true, skipWebp: true });
1372
1306
  return;
1373
1307
  } else {
1374
1308
  throw new Error(message || 'Failed to remove atlas');
@@ -1380,21 +1314,18 @@ const ObjectLayerEngineViewer = {
1380
1314
  status: 'error',
1381
1315
  });
1382
1316
  } finally {
1383
- if (this.Data.isGeneratingAtlas) {
1384
- this.Data.isGeneratingAtlas = false;
1385
- await this.renderViewer({ Elements });
1317
+ if (ObjectLayerEngineViewer.Data.isGeneratingAtlas) {
1318
+ ObjectLayerEngineViewer.Data.isGeneratingAtlas = false;
1319
+ await ObjectLayerEngineViewer.renderViewer({ appStore });
1386
1320
  }
1387
1321
  }
1388
- },
1389
-
1390
- generateWebp: async function () {
1391
- if (this.Data.isGenerating) return;
1392
-
1393
- const { objectLayer, frameCounts, currentDirection, currentMode } = this.Data;
1322
+ }
1323
+ static async generateWebp() {
1324
+ if (ObjectLayerEngineViewer.Data.isGenerating) return;
1325
+ const { objectLayer, frameCounts, currentDirection, currentMode } = ObjectLayerEngineViewer.Data;
1394
1326
  if (!objectLayer || !frameCounts) return;
1395
-
1396
1327
  // Get numeric direction code
1397
- const numericCode = this.getDirectionCode(currentDirection, currentMode);
1328
+ const numericCode = ObjectLayerEngineViewer.getDirectionCode(currentDirection, currentMode);
1398
1329
  if (!numericCode) {
1399
1330
  NotificationManager.Push({
1400
1331
  html: `Invalid direction/mode combination: ${currentDirection} ${currentMode}`,
@@ -1402,9 +1333,7 @@ const ObjectLayerEngineViewer = {
1402
1333
  });
1403
1334
  return;
1404
1335
  }
1405
-
1406
1336
  const frameCount = frameCounts[numericCode];
1407
-
1408
1337
  if (!frameCount || frameCount === 0) {
1409
1338
  NotificationManager.Push({
1410
1339
  html: `No frames available for ${currentDirection} ${currentMode}`,
@@ -1412,13 +1341,11 @@ const ObjectLayerEngineViewer = {
1412
1341
  });
1413
1342
  return;
1414
1343
  }
1415
-
1416
1344
  const itemType = objectLayer.data.item.type;
1417
1345
  const itemId = objectLayer.data.item.id;
1418
1346
  const frameDuration = objectLayer.objectLayerRenderFramesId?.frame_duration || 100;
1419
-
1420
- this.Data.isGenerating = true;
1421
- this.showLoading(true, 'Generating WebP...');
1347
+ ObjectLayerEngineViewer.Data.isGenerating = true;
1348
+ ObjectLayerEngineViewer.showLoading(true, 'Generating WebP...');
1422
1349
  try {
1423
1350
  // Call the WebP generation API endpoint
1424
1351
  const { status, data } = await ObjectLayerService.generateWebp({
@@ -1426,21 +1353,18 @@ const ObjectLayerEngineViewer = {
1426
1353
  itemId,
1427
1354
  directionCode: numericCode,
1428
1355
  });
1429
-
1430
1356
  if (status === 'success' && data) {
1431
1357
  // Store the blob URL and metadata
1432
- this.Data.webp = data;
1433
- this.Data.webpMetadata = {
1358
+ ObjectLayerEngineViewer.Data.webp = data;
1359
+ ObjectLayerEngineViewer.Data.webpMetadata = {
1434
1360
  frameCount,
1435
1361
  frameDuration,
1436
1362
  currentDirection,
1437
1363
  currentMode,
1438
1364
  numericCode,
1439
1365
  };
1440
-
1441
1366
  // Display the WebP in the viewer
1442
- await this.displayWebp();
1443
-
1367
+ await ObjectLayerEngineViewer.displayWebp();
1444
1368
  // NotificationManager.Push({
1445
1369
  // html: `WebP generated successfully (${frameCount} frames, ${frameDuration}ms duration)`,
1446
1370
  // status: 'success',
@@ -1448,21 +1372,51 @@ const ObjectLayerEngineViewer = {
1448
1372
  } else {
1449
1373
  throw new Error('Failed to generate WebP');
1450
1374
  }
1451
-
1452
- this.Data.isGenerating = false;
1453
- this.showLoading(false);
1375
+ ObjectLayerEngineViewer.Data.isGenerating = false;
1376
+ ObjectLayerEngineViewer.showLoading(false);
1454
1377
  } catch (error) {
1455
1378
  logger.error('Error generating WebP:', error);
1456
1379
  NotificationManager.Push({
1457
1380
  html: `Failed to generate WebP: ${error.message}`,
1458
1381
  status: 'error',
1459
1382
  });
1460
- this.Data.isGenerating = false;
1461
- this.showLoading(false);
1383
+ ObjectLayerEngineViewer.Data.isGenerating = false;
1384
+ ObjectLayerEngineViewer.showLoading(false);
1462
1385
  }
1463
- },
1464
-
1465
- showLoading: function (show, message = 'Generating WebP...') {
1386
+ }
1387
+ /**
1388
+ * Updates direction/mode button active states and disabled flags in-place,
1389
+ * without re-rendering the viewer. Prevents layout flicker when switching
1390
+ * direction or mode while the WebP canvas and surrounding structure stay intact.
1391
+ */
1392
+ static _updateControlsState() {
1393
+ const { currentDirection, currentMode, frameCounts } = ObjectLayerEngineViewer.Data;
1394
+ const hasFrames = (direction, mode) => {
1395
+ const code = ObjectLayerEngineViewer.getDirectionCode(direction, mode);
1396
+ return !!(code && frameCounts && frameCounts[code] && frameCounts[code] > 0);
1397
+ };
1398
+ const getFrameCount = (direction, mode) => {
1399
+ const code = ObjectLayerEngineViewer.getDirectionCode(direction, mode);
1400
+ return code ? (frameCounts && frameCounts[code]) || 0 : 0;
1401
+ };
1402
+ document.querySelectorAll('[data-direction]').forEach((btn) => {
1403
+ const d = btn.getAttribute('data-direction');
1404
+ btn.classList.toggle('active', d === currentDirection);
1405
+ const hasFr = hasFrames(d, currentMode);
1406
+ btn.disabled = !hasFr;
1407
+ const countEl = btn.querySelector('.frame-count');
1408
+ if (countEl) countEl.textContent = hasFr ? `(${getFrameCount(d, currentMode)})` : '';
1409
+ });
1410
+ document.querySelectorAll('[data-mode]').forEach((btn) => {
1411
+ const m = btn.getAttribute('data-mode');
1412
+ btn.classList.toggle('active', m === currentMode);
1413
+ const hasFr = hasFrames(currentDirection, m);
1414
+ btn.disabled = !hasFr;
1415
+ const countEl = btn.querySelector('.frame-count');
1416
+ if (countEl) countEl.textContent = hasFr ? `(${getFrameCount(currentDirection, m)})` : '';
1417
+ });
1418
+ }
1419
+ static showLoading(show, message = 'Generating WebP...') {
1466
1420
  const overlay = s('#webp-loading-overlay');
1467
1421
  if (overlay) {
1468
1422
  overlay.style.display = show ? 'flex' : 'none';
@@ -1471,94 +1425,76 @@ const ObjectLayerEngineViewer = {
1471
1425
  loadingText.textContent = message;
1472
1426
  }
1473
1427
  }
1474
-
1475
1428
  const downloadBtn = s('#download-webp-btn');
1476
1429
  if (downloadBtn) {
1477
1430
  downloadBtn.disabled = show;
1478
1431
  }
1479
-
1480
- // Remove old info badge if exists
1481
- const oldBadge = s('.webp-info-badge');
1482
- if (oldBadge && show) {
1483
- oldBadge.remove();
1484
- }
1485
- },
1486
-
1487
- downloadWebp: function () {
1488
- if (!this.Data.webp) {
1432
+ // Keep existing info badge visible during loading (removes the layout-shift flicker)
1433
+ }
1434
+ static downloadWebp() {
1435
+ if (!ObjectLayerEngineViewer.Data.webp) {
1489
1436
  NotificationManager.Push({
1490
1437
  html: 'No WebP available to download',
1491
1438
  status: 'warning',
1492
1439
  });
1493
1440
  return;
1494
1441
  }
1495
-
1496
- const { objectLayer, currentDirection, currentMode } = this.Data;
1497
- const numericCode = this.getDirectionCode(currentDirection, currentMode);
1442
+ const { objectLayer, currentDirection, currentMode } = ObjectLayerEngineViewer.Data;
1443
+ const numericCode = ObjectLayerEngineViewer.getDirectionCode(currentDirection, currentMode);
1498
1444
  const filename = `${objectLayer.data.item.id}_${currentDirection}_${currentMode}_${numericCode}.webp`;
1499
-
1500
1445
  // Create a temporary anchor element to trigger download
1501
1446
  const a = document.createElement('a');
1502
- a.href = this.Data.webp;
1447
+ a.href = ObjectLayerEngineViewer.Data.webp;
1503
1448
  a.download = filename;
1504
1449
  document.body.appendChild(a);
1505
1450
  a.click();
1506
1451
  document.body.removeChild(a);
1507
-
1508
1452
  NotificationManager.Push({
1509
1453
  html: `WebP downloaded: ${filename}`,
1510
1454
  status: 'success',
1511
1455
  });
1512
- },
1513
-
1514
- toEngine: function () {
1515
- const { objectLayer } = this.Data;
1456
+ }
1457
+ static toEngine() {
1458
+ const { objectLayer } = ObjectLayerEngineViewer.Data;
1516
1459
  if (!objectLayer || !objectLayer._id) return;
1517
-
1518
1460
  // Navigate to editor route first
1519
1461
  setPath(`${getProxyPath()}object-layer-engine`);
1520
1462
  // Then add query param without replacing history
1521
1463
  setQueryParams({ id: objectLayer._id }, { replace: true });
1522
-
1523
1464
  if (s(`.modal-object-layer-engine`)) {
1524
1465
  ObjectLayerEngineModal.Reload();
1525
1466
  } else {
1526
1467
  s(`.main-btn-object-layer-engine`)?.click();
1527
1468
  }
1528
- },
1529
-
1530
- Reload: async function (options = {}) {
1531
- const { Elements, force = false, skipWebp = false } = options;
1469
+ }
1470
+ static async Reload(options = {}) {
1471
+ const { appStore, force = false, skipWebp = false } = options;
1532
1472
  const queryParams = getQueryParams();
1533
1473
  const objectId = queryParams.id || null;
1534
-
1535
1474
  // Only reload if object id actually changed (same logic as listener) or forced
1536
- if (objectId !== this.Data.currentObjectId || force) {
1537
- if (objectId !== this.Data.currentObjectId && !skipWebp) {
1538
- this.Data.webp = null;
1539
- this.Data.webpMetadata = null;
1475
+ if (objectId !== ObjectLayerEngineViewer.Data.currentObjectId || force) {
1476
+ if (objectId !== ObjectLayerEngineViewer.Data.currentObjectId && !skipWebp) {
1477
+ ObjectLayerEngineViewer.Data.webp = null;
1478
+ ObjectLayerEngineViewer.Data.webpMetadata = null;
1540
1479
  }
1541
- this.Data.currentObjectId = objectId;
1542
-
1480
+ ObjectLayerEngineViewer.Data.currentObjectId = objectId;
1543
1481
  if (objectId) {
1544
- await this.loadObjectLayer(objectId, Elements, { skipWebp });
1482
+ await ObjectLayerEngineViewer.loadObjectLayer(objectId, appStore, { skipWebp });
1545
1483
  } else {
1546
- await this.renderEmpty({ Elements });
1484
+ await ObjectLayerEngineViewer.renderEmpty({ appStore });
1547
1485
  }
1548
- } else if (!objectId && (this.Data.currentObjectId === null || force)) {
1486
+ } else if (!objectId && (ObjectLayerEngineViewer.Data.currentObjectId === null || force)) {
1549
1487
  // Special case: if we're already in empty state but DOM might have been reset
1550
1488
  // (e.g., modal reopened), force render the table if DOM is missing
1551
1489
  const id = 'object-layer-engine-viewer';
1552
1490
  const idModal = 'modal-object-layer-engine-viewer';
1553
1491
  const gridId = `object-layer-engine-management-grid-${idModal}`;
1554
1492
  const gridDomExists = s(`.${gridId}`);
1555
-
1556
1493
  if (!gridDomExists) {
1557
1494
  // DOM was reset (e.g., modal HTML reloaded), re-render the table
1558
- await this.renderEmpty({ Elements });
1495
+ await ObjectLayerEngineViewer.renderEmpty({ appStore });
1559
1496
  }
1560
1497
  }
1561
- },
1562
- };
1563
-
1498
+ }
1499
+ }
1564
1500
  export { ObjectLayerEngineViewer };