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
@@ -1,6 +1,6 @@
1
1
  import { CoreService } from '../../services/core/core.service.js';
2
2
  import { BtnIcon } from '../core/BtnIcon.js';
3
- import { borderChar, dynamicCol } from '../core/Css.js';
3
+ import { borderChar, Css, dynamicCol, Themes } from '../core/Css.js';
4
4
  import { DropDown } from '../core/DropDown.js';
5
5
  import { EventsUI } from '../core/EventsUI.js';
6
6
  import { Translate } from '../core/Translate.js';
@@ -16,22 +16,525 @@ import { Modal } from '../core/Modal.js';
16
16
  import { LoadingAnimation } from '../core/LoadingAnimation.js';
17
17
  import { DefaultManagement } from '../../services/default/default.management.js';
18
18
  import * as _ from '../cyberia/ObjectLayerEngine.js';
19
+ import '../core/ColorPaletteElement.js';
19
20
 
20
- const ObjectLayerEngineModal = {
21
- selectItemType: 'skin',
22
- itemActivable: false,
23
- renderIsStateless: false,
24
- renderFrameDuration: 100,
25
- existingObjectLayerId: null,
26
- originalDirectionCodes: [],
27
- templates: [
21
+ const CANVAS_BEHAVIOR_ICON = 'fa-solid fa-shapes';
22
+
23
+ const DISTORTION_TYPES = Object.freeze([
24
+ {
25
+ value: 'position-jitter',
26
+ label: 'Position Jitter',
27
+ group: 'distortion',
28
+ icon: CANVAS_BEHAVIOR_ICON,
29
+ },
30
+ {
31
+ value: 'rotation-drift',
32
+ label: 'Rotation Drift',
33
+ group: 'distortion',
34
+ icon: CANVAS_BEHAVIOR_ICON,
35
+ },
36
+ {
37
+ value: 'scale-wobble',
38
+ label: 'Scale Wobble',
39
+ group: 'distortion',
40
+ icon: CANVAS_BEHAVIOR_ICON,
41
+ },
42
+ {
43
+ value: 'particle-drift',
44
+ label: 'Particle Drift',
45
+ group: 'distortion',
46
+ icon: CANVAS_BEHAVIOR_ICON,
47
+ },
48
+ ]);
49
+
50
+ const MOSAIC_TYPES = Object.freeze([
51
+ {
52
+ value: 'mosaic-diamond-checker',
53
+ label: 'Diamond Checker',
54
+ group: 'mosaic',
55
+ icon: CANVAS_BEHAVIOR_ICON,
56
+ },
57
+ {
58
+ value: 'mosaic-rhombus-lattice',
59
+ label: 'Rhombus Lattice',
60
+ group: 'mosaic',
61
+ icon: CANVAS_BEHAVIOR_ICON,
62
+ },
63
+ {
64
+ value: 'mosaic-zigzag-rows',
65
+ label: 'Zigzag Rows',
66
+ group: 'mosaic',
67
+ icon: CANVAS_BEHAVIOR_ICON,
68
+ },
69
+ {
70
+ value: 'mosaic-staggered-tiles',
71
+ label: 'Staggered Tiles',
72
+ group: 'mosaic',
73
+ icon: CANVAS_BEHAVIOR_ICON,
74
+ },
75
+ {
76
+ value: 'mosaic-brick-offset',
77
+ label: 'Brick Offset',
78
+ group: 'mosaic',
79
+ icon: CANVAS_BEHAVIOR_ICON,
80
+ },
81
+ ]);
82
+
83
+ const CANVAS_BEHAVIORS = Object.freeze([...DISTORTION_TYPES, ...MOSAIC_TYPES]);
84
+
85
+ const DEFAULT_DISTORTION_TYPE = DISTORTION_TYPES[0].value;
86
+ const DEFAULT_DISTORTION_STATUS =
87
+ 'Applies the selected canvas behavior directly to the current editor frame. factorA controls distortion density or mosaic tile scale.';
88
+ const DEFAULT_DISTORTION_FACTOR_A = 0.12;
89
+ const DEFAULT_STAT_RANDOM_MIN = 0;
90
+ const DEFAULT_STAT_RANDOM_MAX = 10;
91
+ const UNIFORM_OPACITY_TOGGLE_ID = 'ol-uniform-opacity-lock';
92
+ const DIRECTION_PREVIEW_MODAL_ID = 'modal-object-layer-direction-preview';
93
+ const CANVAS_BEHAVIOR_BY_VALUE = Object.freeze(
94
+ Object.fromEntries(CANVAS_BEHAVIORS.map((entry) => [entry.value, entry])),
95
+ );
96
+ const isMosaicBehavior = (value = '') => CANVAS_BEHAVIOR_BY_VALUE[value]?.group === 'mosaic';
97
+ const getCanvasBehaviorDisplay = (value = DEFAULT_DISTORTION_TYPE) => {
98
+ const behavior = CANVAS_BEHAVIOR_BY_VALUE[value] || CANVAS_BEHAVIOR_BY_VALUE[DEFAULT_DISTORTION_TYPE];
99
+ return `<i class="${CANVAS_BEHAVIOR_ICON}"></i> ${behavior.label}`;
100
+ };
101
+
102
+ const hashString = (str = '') => {
103
+ let hash = 0;
104
+ for (let i = 0; i < str.length; i++) {
105
+ hash = (Math.imul(31, hash) + str.charCodeAt(i)) | 0;
106
+ }
107
+ return hash;
108
+ };
109
+
110
+ const clampNumber = (value, min, max) => (value < min ? min : value > max ? max : value);
111
+ const lerp = (start, end, factor) => start + (end - start) * factor;
112
+ const smoothstep = (value) => value * value * (3 - 2 * value);
113
+ const normalizedHash = (seed, x, y) => ((hashString(`${seed}:${x}:${y}`) >>> 0) / 4294967295) * 2 - 1;
114
+
115
+ const sampleSmoothNoise = (seed, x, y) => {
116
+ const x0 = Math.floor(x);
117
+ const y0 = Math.floor(y);
118
+ const x1 = x0 + 1;
119
+ const y1 = y0 + 1;
120
+
121
+ const sx = smoothstep(x - x0);
122
+ const sy = smoothstep(y - y0);
123
+ const n00 = normalizedHash(seed, x0, y0);
124
+ const n10 = normalizedHash(seed, x1, y0);
125
+ const n01 = normalizedHash(seed, x0, y1);
126
+ const n11 = normalizedHash(seed, x1, y1);
127
+
128
+ return lerp(lerp(n00, n10, sx), lerp(n01, n11, sx), sy);
129
+ };
130
+
131
+ const isVisibleCell = (cell) => Array.isArray(cell) && (cell[3] || 0) > 0;
132
+ const cloneMatrix = (matrix = []) => matrix.map((row) => row.map((cell) => cell.slice()));
133
+ const modulo = (value, divisor) => {
134
+ if (!divisor) return 0;
135
+ return ((value % divisor) + divisor) % divisor;
136
+ };
137
+ const normalizeColorCell = (cell = [255, 0, 0, 255]) => [
138
+ clampNumber(Math.round(cell[0] || 0), 0, 255),
139
+ clampNumber(Math.round(cell[1] || 0), 0, 255),
140
+ clampNumber(Math.round(cell[2] || 0), 0, 255),
141
+ clampNumber(Math.round(cell[3] ?? 255), 0, 255),
142
+ ];
143
+ const getRenderedInputNode = (id = '') => s(`.${id}`) || s(`#${id}-name`) || s(`#${id}`);
144
+
145
+ const countChangedCells = (baseMatrix = [], nextMatrix = []) => {
146
+ const height = Math.max(baseMatrix.length, nextMatrix.length);
147
+ const width = Math.max(baseMatrix[0]?.length || 0, nextMatrix[0]?.length || 0);
148
+ let changedCells = 0;
149
+
150
+ for (let y = 0; y < height; y++) {
151
+ for (let x = 0; x < width; x++) {
152
+ const baseCell = baseMatrix[y]?.[x] || [0, 0, 0, 0];
153
+ const nextCell = nextMatrix[y]?.[x] || [0, 0, 0, 0];
154
+ if (
155
+ baseCell[0] !== nextCell[0] ||
156
+ baseCell[1] !== nextCell[1] ||
157
+ baseCell[2] !== nextCell[2] ||
158
+ baseCell[3] !== nextCell[3]
159
+ ) {
160
+ changedCells++;
161
+ }
162
+ }
163
+ }
164
+
165
+ return changedCells;
166
+ };
167
+
168
+ const deriveMatrixLayerSeed = (matrix = []) => {
169
+ const signature = [];
170
+ for (let y = 0; y < matrix.length; y++) {
171
+ for (let x = 0; x < (matrix[y]?.length || 0); x++) {
172
+ const cell = matrix[y][x];
173
+ if (!isVisibleCell(cell)) continue;
174
+ signature.push(`${x}:${y}:${cell.join(',')}`);
175
+ }
176
+ }
177
+ return hashString(`${matrix[0]?.length || 0}x${matrix.length}:${signature.join('|')}`);
178
+ };
179
+
180
+ const colorDistance = (left = [0, 0, 0, 0], right = [0, 0, 0, 0]) => {
181
+ const deltaRed = (left[0] - right[0]) / 255;
182
+ const deltaGreen = (left[1] - right[1]) / 255;
183
+ const deltaBlue = (left[2] - right[2]) / 255;
184
+ const deltaAlpha = ((left[3] || 0) - (right[3] || 0)) / 255;
185
+ return (
186
+ Math.sqrt(deltaRed * deltaRed + deltaGreen * deltaGreen + deltaBlue * deltaBlue + deltaAlpha * deltaAlpha * 0.35) /
187
+ 1.83
188
+ );
189
+ };
190
+
191
+ const getNeighborEntries = (matrix, x, y) => {
192
+ const neighbors = [];
193
+ const sourceCell = matrix[y]?.[x] || [0, 0, 0, 0];
194
+
195
+ for (let offsetY = -1; offsetY <= 1; offsetY++) {
196
+ for (let offsetX = -1; offsetX <= 1; offsetX++) {
197
+ if (offsetX === 0 && offsetY === 0) continue;
198
+
199
+ const neighborX = x + offsetX;
200
+ const neighborY = y + offsetY;
201
+ if (
202
+ neighborY < 0 ||
203
+ neighborX < 0 ||
204
+ neighborY >= matrix.length ||
205
+ neighborX >= (matrix[neighborY]?.length || 0)
206
+ ) {
207
+ continue;
208
+ }
209
+
210
+ const neighborCell = matrix[neighborY][neighborX];
211
+ const visible = isVisibleCell(neighborCell);
212
+ neighbors.push({
213
+ x: neighborX,
214
+ y: neighborY,
215
+ dx: offsetX,
216
+ dy: offsetY,
217
+ cell: neighborCell.slice(),
218
+ visible,
219
+ distance: visible ? colorDistance(sourceCell, neighborCell) : 1,
220
+ });
221
+ }
222
+ }
223
+
224
+ return neighbors;
225
+ };
226
+
227
+ const getDistortionTime = (distortionSeed = 0) => (Math.abs(Number(distortionSeed) || 0) % 4096) / 128 + 1;
228
+
229
+ const getDirectionalVector = ({ x, y, width, height, distortionType, frameSeed, distortionSeed }) => {
230
+ const distortionTime = getDistortionTime(distortionSeed);
231
+ const noiseX = sampleSmoothNoise(frameSeed + 211, x * 0.26 + distortionTime * 0.14, y * 0.26 + 1.9);
232
+ const noiseY = sampleSmoothNoise(frameSeed + 263, x * 0.26 + 7.1, y * 0.26 + distortionTime * 0.14);
233
+ const centerX = (width - 1) / 2;
234
+ const centerY = (height - 1) / 2;
235
+ const deltaX = x - centerX;
236
+ const deltaY = y - centerY;
237
+ const radialX = deltaX === 0 ? 0 : deltaX / Math.max(1, Math.abs(deltaX) + Math.abs(deltaY));
238
+ const radialY = deltaY === 0 ? 0 : deltaY / Math.max(1, Math.abs(deltaX) + Math.abs(deltaY));
239
+
240
+ if (distortionType === 'position-jitter') {
241
+ return { dx: noiseX, dy: noiseY };
242
+ }
243
+
244
+ if (distortionType === 'rotation-drift') {
245
+ const tangentDirection = sampleSmoothNoise(frameSeed + 307, distortionTime * 0.24, 3.7) >= 0 ? 1 : -1;
246
+ return {
247
+ dx: tangentDirection * -radialY + noiseX * 0.3,
248
+ dy: tangentDirection * radialX + noiseY * 0.3,
249
+ };
250
+ }
251
+
252
+ if (distortionType === 'scale-wobble') {
253
+ const radialDirection = sampleSmoothNoise(frameSeed + 359, distortionTime * 0.18, 4.6) >= 0 ? 1 : -1;
254
+ return {
255
+ dx: radialX * radialDirection + noiseX * 0.2,
256
+ dy: radialY * radialDirection + noiseY * 0.2,
257
+ };
258
+ }
259
+
260
+ return {
261
+ dx: (noiseX >= 0 ? 0.6 : -0.6) + noiseX * 0.6,
262
+ dy: (noiseY >= 0 ? 0.6 : -0.6) + noiseY * 0.6,
263
+ };
264
+ };
265
+
266
+ const collectVisibleCells = (matrix = [], layerSeed, frameSeed, distortionType, distortionSeed) => {
267
+ const distortionTime = getDistortionTime(distortionSeed);
268
+ const visibleCells = [];
269
+
270
+ for (let y = 0; y < matrix.length; y++) {
271
+ for (let x = 0; x < (matrix[y]?.length || 0); x++) {
272
+ const cell = matrix[y][x];
273
+ if (!isVisibleCell(cell)) continue;
274
+
275
+ const neighbors = getNeighborEntries(matrix, x, y);
276
+ const visibleNeighbors = neighbors.filter((neighbor) => neighbor.visible);
277
+ const edgeScore = neighbors.some((neighbor) => !neighbor.visible) ? 1 : 0;
278
+ const variationScore = visibleNeighbors.length
279
+ ? visibleNeighbors.reduce((sum, neighbor) => {
280
+ if (neighbor.distance < 0.01) return sum + 0.04;
281
+ if (neighbor.distance <= 0.35) return sum + (1 - Math.abs(neighbor.distance - 0.14) / 0.21);
282
+ if (neighbor.distance <= 0.55) return sum + 0.18;
283
+ return sum + 0.04;
284
+ }, 0) / visibleNeighbors.length
285
+ : 0;
286
+
287
+ const activity = (sampleSmoothNoise(frameSeed + 401, x * 0.24 + distortionTime * 0.1, y * 0.24 + 2.7) + 1) / 2;
288
+ const stabilityBias = 1 - (hashString(`${layerSeed}:${distortionType}:${x}:${y}`) >>> 0) / 4294967295;
289
+
290
+ visibleCells.push({
291
+ x,
292
+ y,
293
+ cell: cell.slice(),
294
+ neighbors,
295
+ edgeScore,
296
+ variationScore,
297
+ score: variationScore * 0.58 + edgeScore * 0.2 + activity * 0.18 + stabilityBias * 0.04,
298
+ });
299
+ }
300
+ }
301
+
302
+ return visibleCells.sort((left, right) => right.score - left.score);
303
+ };
304
+
305
+ const chooseDistortionTarget = ({ entry, matrix, distortionType, frameSeed, distortionSeed, reservedCells }) => {
306
+ const width = matrix[0]?.length || 0;
307
+ const height = matrix.length;
308
+ const vector = getDirectionalVector({
309
+ x: entry.x,
310
+ y: entry.y,
311
+ width,
312
+ height,
313
+ distortionType,
314
+ frameSeed,
315
+ distortionSeed,
316
+ });
317
+ const vectorLength = Math.hypot(vector.dx, vector.dy) || 1;
318
+
319
+ return [...entry.neighbors]
320
+ .map((neighbor) => {
321
+ const alignment =
322
+ (neighbor.dx * vector.dx + neighbor.dy * vector.dy) /
323
+ ((Math.hypot(neighbor.dx, neighbor.dy) || 1) * vectorLength);
324
+ let score = alignment * 0.55;
325
+
326
+ if (!neighbor.visible) {
327
+ score += 0.22 + entry.edgeScore * 0.16;
328
+ } else if (neighbor.distance >= 0.01 && neighbor.distance <= 0.38) {
329
+ score += 0.34 + (0.38 - neighbor.distance) * 0.45;
330
+ } else {
331
+ score -= 0.2;
332
+ }
333
+
334
+ if (distortionType === 'particle-drift' && Math.abs(neighbor.dx) === 1 && Math.abs(neighbor.dy) === 1) {
335
+ score += 0.16;
336
+ }
337
+ if (distortionType === 'rotation-drift' && Math.abs(neighbor.dx) + Math.abs(neighbor.dy) === 1) {
338
+ score += 0.06;
339
+ }
340
+ if (distortionType === 'scale-wobble' && !neighbor.visible) {
341
+ score += 0.08;
342
+ }
343
+ if (reservedCells.has(`${neighbor.x}:${neighbor.y}`)) {
344
+ score = -Infinity;
345
+ }
346
+
347
+ return { ...neighbor, score };
348
+ })
349
+ .sort((left, right) => right.score - left.score)
350
+ .find((candidate) => candidate.score > 0.02);
351
+ };
352
+
353
+ const buildDistortedMatrix = (
354
+ baseMatrix,
355
+ distortionType,
356
+ factorA = DEFAULT_DISTORTION_FACTOR_A,
357
+ distortionSeed = Date.now(),
358
+ ) => {
359
+ const layerSeed = deriveMatrixLayerSeed(baseMatrix);
360
+ const normalizedSeed = Number.isFinite(distortionSeed) ? distortionSeed : hashString(String(distortionSeed));
361
+ const frameSeed = hashString(`${layerSeed}:${distortionType}:${normalizedSeed}`);
362
+ const visibleCells = collectVisibleCells(baseMatrix, layerSeed, frameSeed, distortionType, normalizedSeed);
363
+ const result = cloneMatrix(baseMatrix);
364
+
365
+ if (!visibleCells.length) {
366
+ return { matrix: result, changedCells: 0, appliedDistortions: 0, layerSeed, frameSeed };
367
+ }
368
+
369
+ const densityFactor = clampNumber(Number.isFinite(factorA) ? factorA : DEFAULT_DISTORTION_FACTOR_A, 0.01, 1);
370
+ const maxBudget = Math.max(1, Math.round(visibleCells.length * 0.35));
371
+ const distortionBudget = clampNumber(Math.round(visibleCells.length * densityFactor), 1, maxBudget);
372
+ const reservedCells = new Set();
373
+ let appliedDistortions = 0;
374
+
375
+ for (const entry of visibleCells) {
376
+ if (appliedDistortions >= distortionBudget) break;
377
+
378
+ const sourceKey = `${entry.x}:${entry.y}`;
379
+ if (reservedCells.has(sourceKey)) continue;
380
+
381
+ const target = chooseDistortionTarget({
382
+ entry,
383
+ matrix: baseMatrix,
384
+ distortionType,
385
+ frameSeed,
386
+ distortionSeed: normalizedSeed,
387
+ reservedCells,
388
+ });
389
+
390
+ if (!target) continue;
391
+
392
+ if (target.visible) {
393
+ const nextCell = result[target.y][target.x].slice();
394
+ result[target.y][target.x] = entry.cell.slice();
395
+ result[entry.y][entry.x] = nextCell;
396
+ } else {
397
+ result[target.y][target.x] = entry.cell.slice();
398
+ result[entry.y][entry.x] = [0, 0, 0, 0];
399
+ }
400
+
401
+ reservedCells.add(sourceKey);
402
+ reservedCells.add(`${target.x}:${target.y}`);
403
+ appliedDistortions++;
404
+ }
405
+
406
+ let changedCells = countChangedCells(baseMatrix, result);
407
+
408
+ if (changedCells === 0) {
409
+ for (const entry of visibleCells) {
410
+ const transparentTarget = entry.neighbors.find((neighbor) => !neighbor.visible);
411
+ if (!transparentTarget) continue;
412
+ result[transparentTarget.y][transparentTarget.x] = entry.cell.slice();
413
+ result[entry.y][entry.x] = [0, 0, 0, 0];
414
+ appliedDistortions = 1;
415
+ changedCells = countChangedCells(baseMatrix, result);
416
+ break;
417
+ }
418
+ }
419
+
420
+ return {
421
+ matrix: result,
422
+ changedCells,
423
+ appliedDistortions,
424
+ densityFactor,
425
+ layerSeed,
426
+ frameSeed,
427
+ };
428
+ };
429
+
430
+ const getMosaicTileSize = (width, height, factorA = DEFAULT_DISTORTION_FACTOR_A) => {
431
+ const minDimension = Math.max(1, Math.min(width, height));
432
+ const normalizedFactor = clampNumber(Number.isFinite(factorA) ? factorA : DEFAULT_DISTORTION_FACTOR_A, 0.01, 1);
433
+ return clampNumber(
434
+ Math.round(1 + normalizedFactor * Math.max(2, Math.min(10, Math.floor(minDimension / 2)))),
435
+ 1,
436
+ minDimension,
437
+ );
438
+ };
439
+
440
+ const buildMosaicMatrix = (
441
+ baseMatrix,
442
+ mosaicType,
443
+ factorA = DEFAULT_DISTORTION_FACTOR_A,
444
+ brushCell = [255, 0, 0, 255],
445
+ ) => {
446
+ const height = baseMatrix.length;
447
+ const width = baseMatrix[0]?.length || 0;
448
+ const result = cloneMatrix(baseMatrix);
449
+
450
+ if (!height || !width) {
451
+ return { matrix: result, changedCells: 0, paintedCells: 0, tileSize: 1 };
452
+ }
453
+
454
+ const tileSize = getMosaicTileSize(width, height, factorA);
455
+ const gap = Math.max(1, Math.floor(tileSize / 2));
456
+ const thickness = Math.max(1, Math.ceil(tileSize / 3));
457
+ const paintCell = normalizeColorCell(brushCell);
458
+ let paintedCells = 0;
459
+
460
+ const shouldPaint = (x, y) => {
461
+ switch (mosaicType) {
462
+ case 'mosaic-diamond-checker': {
463
+ const span = tileSize * 2 + gap;
464
+ const tileX = Math.floor(x / span);
465
+ const tileY = Math.floor(y / span);
466
+ if ((tileX + tileY) % 2 !== 0) return false;
467
+ const localX = modulo(x, span) - tileSize;
468
+ const localY = modulo(y, span) - tileSize;
469
+ return Math.abs(localX) + Math.abs(localY) <= tileSize - 1;
470
+ }
471
+ case 'mosaic-rhombus-lattice': {
472
+ const period = tileSize * 2 + gap;
473
+ const diagonalA = modulo(x + y, period);
474
+ const diagonalB = modulo(x - y, period);
475
+ return diagonalA < thickness || diagonalB < thickness;
476
+ }
477
+ case 'mosaic-zigzag-rows': {
478
+ const zigzagWidth = Math.max(2, tileSize * 2 + gap);
479
+ const bandHeight = Math.max(1, tileSize);
480
+ const rowIndex = Math.floor(y / bandHeight);
481
+ const localX = modulo(x + (rowIndex % 2) * tileSize, zigzagWidth);
482
+ const ridge = modulo(y, bandHeight);
483
+ return Math.abs(localX - ridge) < thickness || Math.abs(localX - (zigzagWidth - ridge - 1)) < thickness;
484
+ }
485
+ case 'mosaic-staggered-tiles': {
486
+ const step = tileSize + gap;
487
+ const rowIndex = Math.floor(y / step);
488
+ const localX = modulo(x + (rowIndex % 2) * Math.floor(step / 2), step);
489
+ const localY = modulo(y, step);
490
+ return localX < tileSize && localY < tileSize;
491
+ }
492
+ case 'mosaic-brick-offset': {
493
+ const brickWidth = tileSize * 2 + gap;
494
+ const brickHeight = tileSize + gap;
495
+ const rowIndex = Math.floor(y / brickHeight);
496
+ const localX = modulo(x + (rowIndex % 2) * Math.floor(brickWidth / 2), brickWidth);
497
+ const localY = modulo(y, brickHeight);
498
+ return localX < brickWidth - gap && localY < tileSize;
499
+ }
500
+ default:
501
+ return false;
502
+ }
503
+ };
504
+
505
+ for (let y = 0; y < height; y++) {
506
+ for (let x = 0; x < width; x++) {
507
+ if (!shouldPaint(x, y)) continue;
508
+ paintedCells++;
509
+ result[y][x] = paintCell.slice();
510
+ }
511
+ }
512
+
513
+ return {
514
+ matrix: result,
515
+ changedCells: countChangedCells(baseMatrix, result),
516
+ paintedCells,
517
+ tileSize,
518
+ };
519
+ };
520
+
521
+ class ObjectLayerEngineModal {
522
+ static selectItemType = 'skin';
523
+ static itemActivable = false;
524
+ static renderFrameDuration = 100;
525
+ static existingObjectLayerId = null;
526
+ static originalDirectionCodes = [];
527
+ static selectedDistortionType = DEFAULT_DISTORTION_TYPE;
528
+ static distortionFactorA = DEFAULT_DISTORTION_FACTOR_A;
529
+ static uniformOpacityEnabled = false;
530
+ static templates = [
28
531
  {
29
532
  label: 'empty',
30
533
  id: 'empty',
31
534
  data: [],
32
535
  },
33
- ],
34
- statDescriptions: {
536
+ ];
537
+ static statDescriptions = {
35
538
  effect: {
36
539
  title: 'Effect',
37
540
  icon: 'fa-solid fa-burst',
@@ -69,9 +572,9 @@ const ObjectLayerEngineModal = {
69
572
  description: 'Reduces the cooldown time between actions, allowing for more frequent actions.',
70
573
  detail: 'It also increases the chance to trigger life-regeneration events.',
71
574
  },
72
- },
575
+ };
73
576
 
74
- RenderTemplate: (colorTemplate) => {
577
+ static RenderTemplate = (colorTemplate) => {
75
578
  const ole = s('object-layer-engine');
76
579
  if (!ole) {
77
580
  return;
@@ -84,17 +587,20 @@ const ObjectLayerEngineModal = {
84
587
 
85
588
  const matrix = colorTemplate.map((row) => row.map((hex) => [...hexToRgbA(hex), 255]));
86
589
  ole.loadMatrix(matrix);
87
- },
88
- ObjectLayerData: {},
89
- clearData: function () {
90
- // Clear all cached object layer data to prevent contamination between sessions
590
+ };
591
+
592
+ static ObjectLayerData = {};
593
+
594
+ static clearData() {
91
595
  this.ObjectLayerData = {};
92
596
  this.selectItemType = 'skin';
93
597
  this.itemActivable = false;
94
- this.renderIsStateless = false;
95
598
  this.renderFrameDuration = 100;
96
599
  this.existingObjectLayerId = null;
97
600
  this.originalDirectionCodes = [];
601
+ this.selectedDistortionType = DEFAULT_DISTORTION_TYPE;
602
+ this.distortionFactorA = DEFAULT_DISTORTION_FACTOR_A;
603
+ this.uniformOpacityEnabled = false;
98
604
  this.templates = [
99
605
  {
100
606
  label: 'empty',
@@ -103,13 +609,11 @@ const ObjectLayerEngineModal = {
103
609
  },
104
610
  ];
105
611
 
106
- // Clear the canvas if it exists
107
612
  const ole = s('object-layer-engine');
108
613
  if (ole && typeof ole.clear === 'function') {
109
614
  ole.clear();
110
615
  }
111
616
 
112
- // Clear all frame previews from DOM for all direction codes
113
617
  const directionCodes = ['08', '18', '02', '12', '04', '14', '06', '16'];
114
618
  for (const directionCode of directionCodes) {
115
619
  const framesContainer = s(`.frames-${directionCode}`);
@@ -132,24 +636,42 @@ const ObjectLayerEngineModal = {
132
636
  const activableCheckbox = s('#ol-toggle-item-activable');
133
637
  if (activableCheckbox) activableCheckbox.checked = false;
134
638
 
135
- const statelessCheckbox = s('#ol-toggle-render-is-stateless');
136
- if (statelessCheckbox) statelessCheckbox.checked = false;
137
-
138
639
  // Clear stat inputs with correct IDs
139
640
  const statTypes = Object.keys(ObjectLayerEngineModal.statDescriptions);
140
641
  for (const stat of statTypes) {
141
- const statInput = s(`#ol-input-item-stats-${stat}`);
642
+ const statInput = getRenderedInputNode(`ol-input-item-stats-${stat}`);
142
643
  if (statInput) statInput.value = '0';
143
644
  }
144
645
 
646
+ const statRandomMinInput = getRenderedInputNode('ol-input-stats-random-min');
647
+ if (statRandomMinInput) statRandomMinInput.value = String(DEFAULT_STAT_RANDOM_MIN);
648
+
649
+ const statRandomMaxInput = getRenderedInputNode('ol-input-stats-random-max');
650
+ if (statRandomMaxInput) statRandomMaxInput.value = String(DEFAULT_STAT_RANDOM_MAX);
651
+
145
652
  // Clear DropDown displays
146
653
  const templateDropdownCurrent = s(`.dropdown-current-ol-dropdown-template`);
147
654
  if (templateDropdownCurrent) templateDropdownCurrent.innerHTML = '';
148
655
 
149
656
  const itemTypeDropdownCurrent = s(`.dropdown-current-ol-dropdown-item-type`);
150
657
  if (itemTypeDropdownCurrent) itemTypeDropdownCurrent.innerHTML = 'skin';
151
- },
152
- loadFromDatabase: async (objectLayerId) => {
658
+
659
+ const distortionDropdownCurrent = s(`.dropdown-current-ol-dropdown-distortion-type`);
660
+ if (distortionDropdownCurrent) {
661
+ distortionDropdownCurrent.innerHTML = getCanvasBehaviorDisplay(DEFAULT_DISTORTION_TYPE);
662
+ }
663
+
664
+ const distortionFactorInput = s('#ol-input-distortion-factor-a') || s('.ol-input-distortion-factor-a');
665
+ if (distortionFactorInput) distortionFactorInput.value = String(DEFAULT_DISTORTION_FACTOR_A);
666
+
667
+ const distortionStatusNode = s(`.ol-distortion-status`);
668
+ if (distortionStatusNode) {
669
+ distortionStatusNode.style.color = '#888';
670
+ distortionStatusNode.innerHTML = DEFAULT_DISTORTION_STATUS;
671
+ }
672
+ }
673
+
674
+ static loadFromDatabase = async (objectLayerId) => {
153
675
  try {
154
676
  // Load metadata first (lightweight)
155
677
  const { status: metaStatus, data: metadata } = await ObjectLayerService.getMetadata({ id: objectLayerId });
@@ -184,12 +706,13 @@ const ObjectLayerEngineModal = {
184
706
  });
185
707
  return null;
186
708
  }
187
- },
188
- Render: async (options = { idModal: '', Elements: {} }) => {
709
+ };
710
+
711
+ static instance = async (options = { idModal: '', appStore: {} }) => {
189
712
  // Clear all cached data at the start of each render to prevent contamination
190
713
  ObjectLayerEngineModal.clearData();
191
714
 
192
- const { Elements } = options;
715
+ const { appStore } = options;
193
716
 
194
717
  const directionCodes = ['08', '18', '02', '12', '04', '14', '06', '16'];
195
718
  const directionCodeLabels = {
@@ -202,8 +725,14 @@ const ObjectLayerEngineModal = {
202
725
  '06': 'Right Idle',
203
726
  16: 'Right Walk',
204
727
  };
205
- const itemTypes = ['skin', 'weapon', 'armor', 'artifact', 'floor'];
728
+ const itemTypes = ['skin', 'weapon', 'armor', 'artifact', 'floor', 'resource', 'obstacle', 'foreground', 'portal'];
206
729
  const statTypes = ['effect', 'resistance', 'agility', 'range', 'intelligence', 'utility'];
730
+ const distortionDropdownId = 'ol-dropdown-distortion-type';
731
+ const distortionApplyBtnClass = 'ol-btn-apply-distortion';
732
+ const distortionStatusClass = 'ol-distortion-status';
733
+ const statsRandomizeBtnClass = 'ol-btn-randomize-stats';
734
+ const statsRandomMinInputId = 'ol-input-stats-random-min';
735
+ const statsRandomMaxInputId = 'ol-input-stats-random-max';
207
736
 
208
737
  // Check if we have an 'id' query parameter to load existing object layer
209
738
  const queryParams = getQueryParams();
@@ -213,6 +742,130 @@ const ObjectLayerEngineModal = {
213
742
  let editingFrameId = null;
214
743
  let editingDirectionCode = null;
215
744
 
745
+ const readDistortionFactorA = () => {
746
+ const factorInput = s('.ol-input-distortion-factor-a') || s('#ol-input-distortion-factor-a');
747
+ const parsedFactor = Number.parseFloat(factorInput?.value);
748
+ const normalizedFactor = clampNumber(
749
+ Number.isFinite(parsedFactor)
750
+ ? parsedFactor
751
+ : ObjectLayerEngineModal.distortionFactorA || DEFAULT_DISTORTION_FACTOR_A,
752
+ 0.01,
753
+ 1,
754
+ );
755
+
756
+ ObjectLayerEngineModal.distortionFactorA = normalizedFactor;
757
+ if (factorInput) {
758
+ factorInput.value = String(Math.round(normalizedFactor * 100) / 100);
759
+ }
760
+
761
+ return normalizedFactor;
762
+ };
763
+
764
+ const readRandomStatBounds = () => {
765
+ const minInput = getRenderedInputNode(statsRandomMinInputId);
766
+ const maxInput = getRenderedInputNode(statsRandomMaxInputId);
767
+ let minValue = Number.parseInt(minInput?.value, 10);
768
+ let maxValue = Number.parseInt(maxInput?.value, 10);
769
+
770
+ minValue = clampNumber(Number.isFinite(minValue) ? minValue : DEFAULT_STAT_RANDOM_MIN, 0, 10);
771
+ maxValue = clampNumber(Number.isFinite(maxValue) ? maxValue : DEFAULT_STAT_RANDOM_MAX, 0, 10);
772
+
773
+ if (minValue > maxValue) {
774
+ const nextMin = maxValue;
775
+ maxValue = minValue;
776
+ minValue = nextMin;
777
+ }
778
+
779
+ if (minInput) minInput.value = String(minValue);
780
+ if (maxInput) maxInput.value = String(maxValue);
781
+
782
+ return { minValue, maxValue };
783
+ };
784
+
785
+ const randomizeStatInputs = () => {
786
+ const { minValue, maxValue } = readRandomStatBounds();
787
+
788
+ for (const statType of statTypes) {
789
+ const statInput = getRenderedInputNode(`ol-input-item-stats-${statType}`);
790
+ if (!statInput) continue;
791
+
792
+ const randomValue = Math.floor(Math.random() * (maxValue - minValue + 1)) + minValue;
793
+ statInput.value = String(randomValue);
794
+ }
795
+
796
+ NotificationManager.Push({
797
+ html: `Stats randomized between ${minValue} and ${maxValue}.`,
798
+ status: 'success',
799
+ });
800
+ };
801
+
802
+ let uniformOpacitySyncInProgress = false;
803
+
804
+ const buildUniformOpacityMatrix = (matrix = [], targetAlpha = 255) => {
805
+ const clampedAlpha = clampNumber(Number(targetAlpha) || 0, 0, 255);
806
+ let changedCells = 0;
807
+ const nextMatrix = matrix.map((row) =>
808
+ row.map((cell) => {
809
+ const nextCell = cell.slice();
810
+ if (isVisibleCell(nextCell) && nextCell[3] !== clampedAlpha) {
811
+ nextCell[3] = clampedAlpha;
812
+ changedCells++;
813
+ }
814
+ return nextCell;
815
+ }),
816
+ );
817
+
818
+ return { matrix: nextMatrix, changedCells, alpha: clampedAlpha };
819
+ };
820
+
821
+ const applyUniformOpacityToEditor = ({ captureUndo = false } = {}) => {
822
+ const ole = s('object-layer-engine');
823
+ if (!ole || !ObjectLayerEngineModal.uniformOpacityEnabled || uniformOpacitySyncInProgress) return false;
824
+
825
+ let currentFrame = null;
826
+ try {
827
+ const exportedMatrix = ole.exportMatrixJSON();
828
+ currentFrame = typeof exportedMatrix === 'string' ? JSON.parse(exportedMatrix) : exportedMatrix;
829
+ } catch (error) {
830
+ return false;
831
+ }
832
+
833
+ const currentMatrix = currentFrame?.matrix;
834
+ if (!Array.isArray(currentMatrix) || !currentMatrix.length || !currentMatrix[0]?.length) {
835
+ return false;
836
+ }
837
+
838
+ const targetAlpha = clampNumber(ole.getBrushAlpha?.() ?? ole.getBrushColor?.()?.[3] ?? 255, 0, 255);
839
+ const nextFrame = buildUniformOpacityMatrix(currentMatrix, targetAlpha);
840
+ if (!nextFrame.changedCells) return false;
841
+
842
+ const nextSnapshot = {
843
+ width: currentFrame.width || currentMatrix[0].length,
844
+ height: currentFrame.height || currentMatrix.length,
845
+ matrix: nextFrame.matrix,
846
+ };
847
+ const beforeSnapshot = captureUndo && typeof ole._snapshot === 'function' ? ole._snapshot() : null;
848
+
849
+ if (
850
+ captureUndo &&
851
+ beforeSnapshot &&
852
+ typeof ole._pushUndo === 'function' &&
853
+ typeof ole._matricesEqual === 'function' &&
854
+ !ole._matricesEqual(beforeSnapshot, nextSnapshot)
855
+ ) {
856
+ ole._pushUndo(beforeSnapshot);
857
+ }
858
+
859
+ uniformOpacitySyncInProgress = true;
860
+ try {
861
+ ole.loadMatrix(nextFrame.matrix);
862
+ } finally {
863
+ uniformOpacitySyncInProgress = false;
864
+ }
865
+
866
+ return true;
867
+ };
868
+
216
869
  // Helper function to update UI when entering edit mode
217
870
  const enterEditMode = (frameId, directionCode) => {
218
871
  editingFrameId = frameId;
@@ -331,7 +984,6 @@ const ObjectLayerEngineModal = {
331
984
  }
332
985
  }
333
986
  if (objectLayerRenderFramesId) {
334
- ObjectLayerEngineModal.renderIsStateless = objectLayerRenderFramesId.is_stateless || false;
335
987
  ObjectLayerEngineModal.renderFrameDuration = objectLayerRenderFramesId.frame_duration || 100;
336
988
  }
337
989
  }
@@ -352,10 +1004,169 @@ const ObjectLayerEngineModal = {
352
1004
  }
353
1005
  }
354
1006
 
355
- const cells = 26;
356
- const pixelSize = parseInt(320 / cells);
1007
+ let cellsW = 26;
1008
+ let cellsH = 26;
1009
+ if (loadedData && loadedData.objectLayerRenderFramesId && loadedData.objectLayerRenderFramesId.frames) {
1010
+ const frames = loadedData.objectLayerRenderFramesId.frames;
1011
+ for (const direction of Object.keys(frames)) {
1012
+ if (frames[direction] && frames[direction].length > 0 && frames[direction][0].length > 0) {
1013
+ cellsH = frames[direction][0].length;
1014
+ cellsW = frames[direction][0][0].length;
1015
+ break;
1016
+ }
1017
+ }
1018
+ }
1019
+ const pixelSize = parseInt(320 / Math.max(cellsW, cellsH));
357
1020
  const idSectionA = 'template-section-a';
358
1021
  const idSectionB = 'template-section-b';
1022
+ const colorPaletteClass = 'ol-color-palette';
1023
+ let directionPreviewRuntime = null;
1024
+
1025
+ const cleanupDirectionPreviewRuntime = () => {
1026
+ if (!directionPreviewRuntime) return;
1027
+ if (directionPreviewRuntime.intervalId) {
1028
+ clearInterval(directionPreviewRuntime.intervalId);
1029
+ }
1030
+ for (const frameUrl of directionPreviewRuntime.frameUrls || []) {
1031
+ URL.revokeObjectURL(frameUrl);
1032
+ }
1033
+ directionPreviewRuntime = null;
1034
+ };
1035
+
1036
+ const cleanupDirectionPreviewModal = () => {
1037
+ cleanupDirectionPreviewRuntime();
1038
+ if (s(`.${DIRECTION_PREVIEW_MODAL_ID}`)) {
1039
+ Modal.removeModal(DIRECTION_PREVIEW_MODAL_ID);
1040
+ }
1041
+ };
1042
+
1043
+ if (Modal.Data[options.idModal]) {
1044
+ Modal.Data[options.idModal].onCloseListener[`${options.idModal}-direction-preview-cleanup`] = () => {
1045
+ cleanupDirectionPreviewModal();
1046
+ delete Modal.Data[options.idModal]?.onCloseListener?.[`${options.idModal}-direction-preview-cleanup`];
1047
+ };
1048
+ }
1049
+
1050
+ const getCurrentFrameDuration = () => {
1051
+ const durationInput = s('.ol-input-render-frame-duration') || s('#ol-input-render-frame-duration');
1052
+ const parsedDuration = Number.parseInt(durationInput?.value, 10);
1053
+ return Math.max(
1054
+ 100,
1055
+ Number.isFinite(parsedDuration) ? parsedDuration : ObjectLayerEngineModal.renderFrameDuration || 100,
1056
+ );
1057
+ };
1058
+
1059
+ const openDirectionPreviewModal = async (directionCode) => {
1060
+ const frames = ObjectLayerEngineModal.ObjectLayerData[directionCode] || [];
1061
+ if (!frames.length) {
1062
+ NotificationManager.Push({
1063
+ html: `No frames available yet for ${directionCodeLabels[directionCode] || directionCode}.`,
1064
+ status: 'warning',
1065
+ });
1066
+ return;
1067
+ }
1068
+
1069
+ const frameUrls = frames
1070
+ .map((frame) => {
1071
+ if (!frame?.image) return null;
1072
+ return URL.createObjectURL(frame.image);
1073
+ })
1074
+ .filter(Boolean);
1075
+
1076
+ if (!frameUrls.length) {
1077
+ NotificationManager.Push({
1078
+ html: `Could not build a local preview for ${directionCodeLabels[directionCode] || directionCode}.`,
1079
+ status: 'error',
1080
+ });
1081
+ return;
1082
+ }
1083
+
1084
+ cleanupDirectionPreviewModal();
1085
+
1086
+ const { barConfig } = await Themes[Css.currentTheme]();
1087
+ const frameDuration = getCurrentFrameDuration();
1088
+ const previewWidth = Math.max(180, cellsW * pixelSize);
1089
+ const previewHeight = Math.max(180, cellsH * pixelSize);
1090
+
1091
+ await Modal.instance({
1092
+ id: DIRECTION_PREVIEW_MODAL_ID,
1093
+ barConfig,
1094
+ title: `${directionCodeLabels[directionCode] || directionCode} Preview`,
1095
+ html: () => html`
1096
+ <div class="in section-mp" style="text-align: center; min-width: ${previewWidth}px;">
1097
+ <div class="in" style="font-size: 12px; color: #999; margin-bottom: 8px;">
1098
+ ${frames.length} frame${frames.length === 1 ? '' : 's'} at ${frameDuration}ms
1099
+ </div>
1100
+ <div
1101
+ class="in direction-preview-stage"
1102
+ style="display: inline-flex; align-items: center; justify-content: center; min-height: ${previewHeight}px; min-width: ${previewWidth}px; background-image: linear-gradient(45deg, rgba(255,255,255,0.08) 25%, transparent 25%), linear-gradient(-45deg, rgba(255,255,255,0.08) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, rgba(255,255,255,0.08) 75%), linear-gradient(-45deg, transparent 75%, rgba(255,255,255,0.08) 75%); background-size: 24px 24px; background-position: 0 0, 0 12px, 12px -12px, -12px 0; border: 1px solid rgba(255,255,255,0.15);"
1103
+ >
1104
+ <img
1105
+ class="direction-preview-image"
1106
+ src="${frameUrls[0]}"
1107
+ style="image-rendering: pixelated; width: ${previewWidth}px; height: ${previewHeight}px; object-fit: contain;"
1108
+ />
1109
+ </div>
1110
+ <div class="fl" style="justify-content: center; gap: 6px; flex-wrap: wrap; margin-top: 10px;">
1111
+ ${frameUrls.map(
1112
+ (frameUrl, frameIndex) => html`
1113
+ <img
1114
+ src="${frameUrl}"
1115
+ style="width: 48px; height: 48px; object-fit: contain; image-rendering: pixelated; border: 1px solid rgba(255,255,255,0.15); background: rgba(0,0,0,0.25);"
1116
+ title="Frame ${frameIndex + 1}"
1117
+ />
1118
+ `,
1119
+ )}
1120
+ </div>
1121
+ </div>
1122
+ `,
1123
+ style: {
1124
+ width: `${Math.max(300, previewWidth + 40)}px`,
1125
+ height: `${Math.max(260, previewHeight + 150)}px`,
1126
+ border: '1px solid rgba(255,255,255,0.15)',
1127
+ 'z-index': 10,
1128
+ },
1129
+ slideMenu: 'modal-menu',
1130
+ });
1131
+
1132
+ const previewImage = s(`.${DIRECTION_PREVIEW_MODAL_ID} .direction-preview-image`);
1133
+ if (!previewImage) {
1134
+ cleanupDirectionPreviewRuntime();
1135
+ return;
1136
+ }
1137
+
1138
+ let currentFrameIndex = 0;
1139
+ const advancePreviewFrame = () => {
1140
+ if (!previewImage) return;
1141
+ currentFrameIndex = (currentFrameIndex + 1) % frameUrls.length;
1142
+ previewImage.src = frameUrls[currentFrameIndex];
1143
+ };
1144
+
1145
+ directionPreviewRuntime = {
1146
+ frameUrls,
1147
+ intervalId: frameUrls.length > 1 ? setInterval(advancePreviewFrame, frameDuration) : null,
1148
+ };
1149
+
1150
+ if (Modal.Data[DIRECTION_PREVIEW_MODAL_ID]) {
1151
+ Modal.Data[DIRECTION_PREVIEW_MODAL_ID].onCloseListener[DIRECTION_PREVIEW_MODAL_ID] = () => {
1152
+ cleanupDirectionPreviewRuntime();
1153
+ delete Modal.Data[DIRECTION_PREVIEW_MODAL_ID]?.onCloseListener?.[DIRECTION_PREVIEW_MODAL_ID];
1154
+ };
1155
+ }
1156
+ };
1157
+
1158
+ const rgbaToHex = (rgba = [0, 0, 0]) => {
1159
+ const red = Math.max(0, Math.min(255, rgba[0] || 0))
1160
+ .toString(16)
1161
+ .padStart(2, '0');
1162
+ const green = Math.max(0, Math.min(255, rgba[1] || 0))
1163
+ .toString(16)
1164
+ .padStart(2, '0');
1165
+ const blue = Math.max(0, Math.min(255, rgba[2] || 0))
1166
+ .toString(16)
1167
+ .padStart(2, '0');
1168
+ return `#${red}${green}${blue}`.toUpperCase();
1169
+ };
359
1170
 
360
1171
  let directionsCodeBarRender = '';
361
1172
 
@@ -378,11 +1189,11 @@ const ObjectLayerEngineModal = {
378
1189
  src="${URL.createObjectURL(image)}"
379
1190
  data-direction-code="${capturedDirectionCode}"
380
1191
  />
381
- ${await BtnIcon.Render({
1192
+ ${await BtnIcon.instance({
382
1193
  label: html`<i class="fa-solid fa-edit"></i>`,
383
1194
  class: `abs direction-code-bar-edit-btn direction-code-bar-edit-btn-${id}`,
384
1195
  })}
385
- ${await BtnIcon.Render({
1196
+ ${await BtnIcon.instance({
386
1197
  label: html`<i class="fa-solid fa-trash"></i>`,
387
1198
  class: `abs direction-code-bar-trash-btn direction-code-bar-trash-btn-${id}`,
388
1199
  })}
@@ -527,14 +1338,18 @@ const ObjectLayerEngineModal = {
527
1338
  <div class="fl">
528
1339
  <div class="in fll">
529
1340
  <div class="in direction-code-bar-frames-title">${directionCodeLabels[directionCode]}</div>
530
- <div class="in direction-code-bar-frames-btn">
531
- ${await BtnIcon.Render({
1341
+ <div class="fl direction-code-bar-btn-row" style="gap: 6px;">
1342
+ ${await BtnIcon.instance({
532
1343
  label: html`
533
1344
  <i class="fa-solid fa-plus direction-code-bar-frames-btn-icon-add-${directionCode}"></i>
534
1345
  <i class="fa-solid fa-edit direction-code-bar-frames-btn-icon-edit-${directionCode} hide"></i>
535
1346
  `,
536
1347
  class: `direction-code-bar-frames-btn-add direction-code-bar-frames-btn-${directionCode}`,
537
1348
  })}
1349
+ ${await BtnIcon.instance({
1350
+ label: html`<i class="fa-solid fa-play"></i>`,
1351
+ class: `direction-code-bar-preview-btn direction-code-bar-preview-btn-${directionCode}`,
1352
+ })}
538
1353
  </div>
539
1354
  </div>
540
1355
  <div class="frames-${directionCode}"></div>
@@ -549,7 +1364,7 @@ const ObjectLayerEngineModal = {
549
1364
  const statValue = loadedData?.metadata?.data?.stats?.[statType] || 0;
550
1365
  statsInputsRender += html`
551
1366
  <div class="inl" style="margin-bottom: 10px; position: relative;">
552
- ${await Input.Render({
1367
+ ${await Input.instance({
553
1368
  id: `ol-input-item-stats-${statType}`,
554
1369
  label: html`<div
555
1370
  title="${statInfo.description} ${statInfo.detail}"
@@ -640,8 +1455,13 @@ const ObjectLayerEngineModal = {
640
1455
  }
641
1456
 
642
1457
  const buttonSelector = `.direction-code-bar-frames-btn-${currentDirectionCode}`;
1458
+ const previewButtonSelector = `.direction-code-bar-preview-btn-${currentDirectionCode}`;
643
1459
  console.log(`Registering click handler for: ${buttonSelector}`);
644
1460
 
1461
+ EventsUI.onClick(previewButtonSelector, async () => {
1462
+ await openDirectionPreviewModal(currentDirectionCode);
1463
+ });
1464
+
645
1465
  EventsUI.onClick(buttonSelector, async () => {
646
1466
  console.log(`Add frame button clicked for direction: ${currentDirectionCode}`);
647
1467
  const ole = s('object-layer-engine');
@@ -727,7 +1547,165 @@ const ObjectLayerEngineModal = {
727
1547
  await loadFrames();
728
1548
  s('object-layer-engine').clear();
729
1549
 
730
- EventsUI.onClick(`.ol-btn-save`, async () => {
1550
+ const editor = s('object-layer-engine');
1551
+ const colorPalette = s(`.${colorPaletteClass}`);
1552
+
1553
+ const syncPaletteFromEditor = (event = null) => {
1554
+ if (!colorPalette || !editor) {
1555
+ return;
1556
+ }
1557
+ const nextHex = event?.detail?.hex || rgbaToHex(editor.getBrushColor?.());
1558
+ if (nextHex && colorPalette.value !== nextHex) {
1559
+ colorPalette.value = nextHex;
1560
+ }
1561
+ };
1562
+
1563
+ const syncEditorFromPalette = (event) => {
1564
+ if (!editor) {
1565
+ return;
1566
+ }
1567
+ const nextHex = event.detail?.value || event.detail?.hex;
1568
+ if (!nextHex) {
1569
+ return;
1570
+ }
1571
+ const [red, green, blue] = hexToRgbA(nextHex);
1572
+ const alpha = editor.getBrushAlpha?.() ?? editor.getBrushColor?.()?.[3] ?? 255;
1573
+ editor.setBrushColor([red, green, blue, alpha]);
1574
+ };
1575
+
1576
+ if (colorPalette) {
1577
+ colorPalette.addEventListener('colorchange', syncEditorFromPalette);
1578
+ }
1579
+ if (editor) {
1580
+ editor.addEventListener('brushcolorchange', syncPaletteFromEditor);
1581
+ }
1582
+
1583
+ const syncUniformOpacityFromEditor = () => {
1584
+ applyUniformOpacityToEditor();
1585
+ };
1586
+
1587
+ if (editor) {
1588
+ editor.addEventListener('brushcolorchange', syncUniformOpacityFromEditor);
1589
+ editor.addEventListener('matrixload', syncUniformOpacityFromEditor);
1590
+ }
1591
+ syncPaletteFromEditor();
1592
+
1593
+ const setDistortionStatus = (message, tone = 'muted') => {
1594
+ const statusNode = s(`.${distortionStatusClass}`);
1595
+ if (!statusNode) return;
1596
+ statusNode.style.color = tone === 'success' ? '#8fd18c' : tone === 'error' ? '#ff8a8a' : '#888';
1597
+ statusNode.innerHTML = message;
1598
+ };
1599
+
1600
+ const applyDistortionToCurrentFrame = async () => {
1601
+ const ole = s('object-layer-engine');
1602
+ if (!ole || typeof ole.exportMatrixJSON !== 'function' || typeof ole.loadMatrix !== 'function') return;
1603
+
1604
+ let currentJson = null;
1605
+ let currentFrame = null;
1606
+
1607
+ try {
1608
+ currentJson = ole.exportMatrixJSON();
1609
+ currentFrame = typeof currentJson === 'string' ? JSON.parse(currentJson) : currentJson;
1610
+ } catch (error) {
1611
+ setDistortionStatus('Could not read the current frame from the editor.', 'error');
1612
+ return;
1613
+ }
1614
+
1615
+ const currentMatrix = currentFrame?.matrix;
1616
+ if (!Array.isArray(currentMatrix) || !currentMatrix.length || !currentMatrix[0]?.length) {
1617
+ setDistortionStatus('The current frame has no editable matrix data.', 'error');
1618
+ return;
1619
+ }
1620
+ const selectedDistortionType = ObjectLayerEngineModal.selectedDistortionType || DEFAULT_DISTORTION_TYPE;
1621
+ const selectedBehavior =
1622
+ CANVAS_BEHAVIOR_BY_VALUE[selectedDistortionType] || CANVAS_BEHAVIOR_BY_VALUE[DEFAULT_DISTORTION_TYPE];
1623
+ const distortionFactorA = readDistortionFactorA();
1624
+ const isMosaicMode = isMosaicBehavior(selectedDistortionType);
1625
+
1626
+ if (!isMosaicMode && !currentMatrix.some((row) => row.some((cell) => isVisibleCell(cell)))) {
1627
+ setDistortionStatus('Paint something on the current frame before applying a distortion.', 'error');
1628
+ return;
1629
+ }
1630
+
1631
+ const baseFrame = currentFrame;
1632
+ let transformationResult = null;
1633
+
1634
+ if (isMosaicMode) {
1635
+ const activeBrushColor = normalizeColorCell(ole.getBrushColor?.() || [255, 0, 0, 255]);
1636
+ transformationResult = buildMosaicMatrix(
1637
+ baseFrame.matrix,
1638
+ selectedDistortionType,
1639
+ distortionFactorA,
1640
+ activeBrushColor,
1641
+ );
1642
+ } else {
1643
+ const seedBase = Date.now() + hashString(currentJson);
1644
+ for (let attempt = 0; attempt < 4; attempt++) {
1645
+ const distortionSeed = seedBase + attempt * 977;
1646
+ transformationResult = buildDistortedMatrix(
1647
+ baseFrame.matrix,
1648
+ selectedDistortionType,
1649
+ distortionFactorA,
1650
+ distortionSeed,
1651
+ );
1652
+ if (transformationResult.changedCells > 0) break;
1653
+ }
1654
+ }
1655
+
1656
+ if (!transformationResult || transformationResult.changedCells === 0) {
1657
+ setDistortionStatus(
1658
+ isMosaicMode
1659
+ ? 'No visible mosaic was painted on the current frame. Try another factorA value or a different active color.'
1660
+ : 'No visible distortion was produced for the current frame. Try applying again or edit the frame shape first.',
1661
+ 'error',
1662
+ );
1663
+ return;
1664
+ }
1665
+
1666
+ const nextSnapshot = {
1667
+ width: baseFrame.width,
1668
+ height: baseFrame.height,
1669
+ matrix: transformationResult.matrix,
1670
+ };
1671
+ const beforeSnapshot = typeof ole._snapshot === 'function' ? ole._snapshot() : null;
1672
+
1673
+ if (
1674
+ beforeSnapshot &&
1675
+ typeof ole._pushUndo === 'function' &&
1676
+ typeof ole._matricesEqual === 'function' &&
1677
+ !ole._matricesEqual(beforeSnapshot, nextSnapshot)
1678
+ ) {
1679
+ ole._pushUndo(beforeSnapshot);
1680
+ }
1681
+
1682
+ ole.loadMatrix(transformationResult.matrix);
1683
+
1684
+ if (isMosaicMode) {
1685
+ setDistortionStatus(
1686
+ `${selectedBehavior.label} painted on the current frame with the active color. tileSize ${transformationResult.tileSize}, ${transformationResult.paintedCells || 0} pattern cells, ${transformationResult.changedCells} cells changed, factorA=${distortionFactorA.toFixed(2)}.`,
1687
+ 'success',
1688
+ );
1689
+ } else {
1690
+ setDistortionStatus(
1691
+ `${selectedBehavior.label} applied to the current frame. ${transformationResult.appliedDistortions || 0} local shifts, ${transformationResult.changedCells} cells changed, factorA=${distortionFactorA.toFixed(2)}.`,
1692
+ 'success',
1693
+ );
1694
+ }
1695
+ };
1696
+
1697
+ EventsUI.onClick(`.${distortionApplyBtnClass}`, async () => {
1698
+ await applyDistortionToCurrentFrame();
1699
+ });
1700
+ setDistortionStatus(DEFAULT_DISTORTION_STATUS);
1701
+
1702
+ EventsUI.onClick(`.${statsRandomizeBtnClass}`, async () => {
1703
+ randomizeStatInputs();
1704
+ });
1705
+
1706
+ const persistObjectLayer = async ({ clone = false } = {}) => {
1707
+ const isUpdateMode = Boolean(ObjectLayerEngineModal.existingObjectLayerId) && !clone;
1708
+
731
1709
  // Validate minimum frame_duration 100ms
732
1710
  const frameDuration = parseInt(s(`.ol-input-render-frame-duration`).value);
733
1711
  if (!frameDuration || frameDuration < 100) {
@@ -765,7 +1743,6 @@ const ObjectLayerEngineModal = {
765
1743
  frames: {},
766
1744
  colors: [],
767
1745
  frame_duration: ObjectLayerEngineModal.renderFrameDuration,
768
- is_stateless: ObjectLayerEngineModal.renderIsStateless,
769
1746
  };
770
1747
 
771
1748
  const objectLayer = {
@@ -811,7 +1788,6 @@ const ObjectLayerEngineModal = {
811
1788
  }
812
1789
  }
813
1790
  objectLayerRenderFramesData.frame_duration = parseInt(s(`.ol-input-render-frame-duration`).value);
814
- objectLayerRenderFramesData.is_stateless = ObjectLayerEngineModal.renderIsStateless;
815
1791
  objectLayer.data.stats = {
816
1792
  effect: parseInt(s(`.ol-input-item-stats-effect`).value),
817
1793
  resistance: parseInt(s(`.ol-input-item-stats-resistance`).value),
@@ -827,18 +1803,18 @@ const ObjectLayerEngineModal = {
827
1803
  description: s(`.ol-input-item-description`).value,
828
1804
  };
829
1805
 
830
- // Add _id if we're updating an existing object layer
831
- if (ObjectLayerEngineModal.existingObjectLayerId) {
1806
+ // Add _id only when updating the existing object layer.
1807
+ if (isUpdateMode) {
832
1808
  objectLayer._id = ObjectLayerEngineModal.existingObjectLayerId;
833
1809
  }
834
1810
 
835
1811
  console.warn(
836
1812
  'objectLayer',
837
1813
  objectLayer,
838
- ObjectLayerEngineModal.existingObjectLayerId ? '(UPDATE MODE)' : '(CREATE MODE)',
1814
+ clone ? '(CLONE MODE)' : isUpdateMode ? '(UPDATE MODE)' : '(CREATE MODE)',
839
1815
  );
840
1816
 
841
- if (Elements.Data.user.main.model.user.role === 'guest') {
1817
+ if (appStore.Data.user.main.model.user.role === 'guest') {
842
1818
  NotificationManager.Push({
843
1819
  html: 'Guests cannot save object layers. Please log in.',
844
1820
  status: 'warning',
@@ -852,14 +1828,14 @@ const ObjectLayerEngineModal = {
852
1828
  const directionCodesToUpload = Object.keys(ObjectLayerEngineModal.ObjectLayerData);
853
1829
 
854
1830
  // In UPDATE mode, also include original direction codes that may have been cleared
855
- const allDirectionCodes = ObjectLayerEngineModal.existingObjectLayerId
1831
+ const allDirectionCodes = isUpdateMode
856
1832
  ? [...new Set([...directionCodesToUpload, ...ObjectLayerEngineModal.originalDirectionCodes])]
857
1833
  : directionCodesToUpload;
858
1834
 
859
1835
  console.warn(
860
1836
  `Uploading frames for ${allDirectionCodes.length} directions:`,
861
1837
  allDirectionCodes,
862
- ObjectLayerEngineModal.existingObjectLayerId ? '(UPDATE MODE)' : '(CREATE MODE)',
1838
+ clone ? '(CLONE MODE)' : isUpdateMode ? '(UPDATE MODE)' : '(CREATE MODE)',
863
1839
  );
864
1840
 
865
1841
  for (const directionCode of allDirectionCodes) {
@@ -884,7 +1860,7 @@ const ObjectLayerEngineModal = {
884
1860
 
885
1861
  // Send all frames for this direction in one request (even if empty, to remove frames)
886
1862
  try {
887
- if (ObjectLayerEngineModal.existingObjectLayerId) {
1863
+ if (isUpdateMode) {
888
1864
  // UPDATE: use PUT endpoint with object layer ID
889
1865
  const { status, data } = await ObjectLayerService.put({
890
1866
  id: `${ObjectLayerEngineModal.existingObjectLayerId}/frame-image/${objectLayer.data.item.type}/${objectLayer.data.item.id}/${directionCode}`,
@@ -925,7 +1901,7 @@ const ObjectLayerEngineModal = {
925
1901
  };
926
1902
 
927
1903
  let response;
928
- if (ObjectLayerEngineModal.existingObjectLayerId) {
1904
+ if (isUpdateMode) {
929
1905
  // UPDATE existing object layer
930
1906
  console.warn(
931
1907
  'PUT path:',
@@ -946,22 +1922,28 @@ const ObjectLayerEngineModal = {
946
1922
  const { status, data, message } = response;
947
1923
 
948
1924
  if (status === 'success') {
1925
+ const successAction = clone ? 'cloned' : isUpdateMode ? 'updated' : 'created';
949
1926
  NotificationManager.Push({
950
- html: `Object layer "${objectLayer.data.item.id}" ${
951
- ObjectLayerEngineModal.existingObjectLayerId ? 'updated' : 'created'
952
- } successfully!`,
1927
+ html: `Object layer "${objectLayer.data.item.id}" ${successAction} successfully!`,
953
1928
  status: 'success',
954
1929
  });
955
1930
  ObjectLayerEngineModal.toManagement(data?._id || ObjectLayerEngineModal.existingObjectLayerId);
956
1931
  } else {
1932
+ const errorAction = clone ? 'cloning' : isUpdateMode ? 'updating' : 'creating';
957
1933
  NotificationManager.Push({
958
- html: `Error ${
959
- ObjectLayerEngineModal.existingObjectLayerId ? 'updating' : 'creating'
960
- } object layer: ${message}`,
1934
+ html: `Error ${errorAction} object layer: ${message}`,
961
1935
  status: 'error',
962
1936
  });
963
1937
  }
964
1938
  }
1939
+ };
1940
+
1941
+ EventsUI.onClick(`.ol-btn-save`, async () => {
1942
+ await persistObjectLayer();
1943
+ });
1944
+
1945
+ EventsUI.onClick(`.ol-btn-clone`, async () => {
1946
+ await persistObjectLayer({ clone: true });
965
1947
  });
966
1948
 
967
1949
  // Add reset button event listener
@@ -1029,6 +2011,10 @@ const ObjectLayerEngineModal = {
1029
2011
  color: white;
1030
2012
  border: none !important;
1031
2013
  }
2014
+ .direction-code-bar-preview-btn {
2015
+ color: white;
2016
+ border: none !important;
2017
+ }
1032
2018
  .direction-code-bar-trash-btn:hover {
1033
2019
  background: none !important;
1034
2020
  color: red;
@@ -1041,6 +2027,10 @@ const ObjectLayerEngineModal = {
1041
2027
  background: none !important;
1042
2028
  color: #c7ff58;
1043
2029
  }
2030
+ .direction-code-bar-preview-btn:hover {
2031
+ background: none !important;
2032
+ color: #5ee6ff;
2033
+ }
1044
2034
  .ol-btn-save {
1045
2035
  width: 120px;
1046
2036
  padding: 0.5rem;
@@ -1053,6 +2043,16 @@ const ObjectLayerEngineModal = {
1053
2043
  font-size: 20px;
1054
2044
  min-height: 50px;
1055
2045
  }
2046
+ .ol-btn-clone {
2047
+ width: 120px;
2048
+ padding: 0.5rem;
2049
+ font-size: 20px;
2050
+ min-height: 50px;
2051
+ }
2052
+ .ol-btn-randomize-stats {
2053
+ min-height: 50px;
2054
+ padding: 0.5rem 0.75rem;
2055
+ }
1056
2056
  .ol-number-label {
1057
2057
  width: 120px;
1058
2058
  font-size: 16px;
@@ -1091,6 +2091,7 @@ const ObjectLayerEngineModal = {
1091
2091
  '.direction-code-bar-edit-btn',
1092
2092
  '.direction-code-bar-trash-btn',
1093
2093
  '.direction-code-bar-frames-btn-add',
2094
+ '.direction-code-bar-preview-btn',
1094
2095
  ])}
1095
2096
  <div class="in frame-editor-container-loading">
1096
2097
  <div class="abs center frame-editor-container-loading-center"></div>
@@ -1098,22 +2099,119 @@ const ObjectLayerEngineModal = {
1098
2099
  <div class="in section-mp section-mp-border frame-editor-container">
1099
2100
  <div class="in sub-title-modal"><i class="fa-solid fa-table-cells-large"></i> Frame editor</div>
1100
2101
 
1101
- <object-layer-engine id="ole" width="${cells}" height="${cells}" pixel-size="${pixelSize}">
2102
+ <object-layer-engine id="ole" width="${cellsW}" height="${cellsH}" pixel-size="${pixelSize}">
1102
2103
  </object-layer-engine>
2104
+ <div class="in section-mp-border" style="margin-top: 10px;">
2105
+ <div class="in sub-title-modal"><i class="fa-solid fa-palette"></i> Brush palette</div>
2106
+ <color-palette class="${colorPaletteClass}" value="#FF0000"></color-palette>
2107
+ <div class="fl" style="align-items: center; gap: 8px; margin-top: 8px;">
2108
+ ${await ToggleSwitch.instance({
2109
+ id: UNIFORM_OPACITY_TOGGLE_ID,
2110
+ type: 'checkbox',
2111
+ displayMode: 'checkbox',
2112
+ containerClass: 'in fll',
2113
+ checked: ObjectLayerEngineModal.uniformOpacityEnabled,
2114
+ on: {
2115
+ checked: () => {
2116
+ ObjectLayerEngineModal.uniformOpacityEnabled = true;
2117
+ applyUniformOpacityToEditor({ captureUndo: true });
2118
+ },
2119
+ unchecked: () => {
2120
+ ObjectLayerEngineModal.uniformOpacityEnabled = false;
2121
+ },
2122
+ },
2123
+ })}
2124
+ <div class="section-mp" style="font-size: 14px;">
2125
+ Keep all visible cells at the current opacity bar value
2126
+ </div>
2127
+ </div>
2128
+ </div>
2129
+ <div class="in section-mp-border" style="margin-top: 10px;">
2130
+ <div class="in sub-title-modal"><i class="fa-solid fa-wand-magic-sparkles"></i> Canvas macro</div>
2131
+ <div class="fl" style="align-items: flex-start; gap: 8px; flex-wrap: wrap;">
2132
+ <div class="in fll" style="min-width: 240px;">
2133
+ ${await DropDown.instance({
2134
+ id: distortionDropdownId,
2135
+ value: ObjectLayerEngineModal.selectedDistortionType,
2136
+ label: html`Select behavior`,
2137
+ disableSearchBox: true,
2138
+ data: [
2139
+ {
2140
+ kind: 'group',
2141
+ value: 'group-distortion-behaviors',
2142
+ display: html`<div style="padding: 0 6px; color: #9d9d9d;">Distortion behaviors</div>`,
2143
+ },
2144
+ ...DISTORTION_TYPES.map((distortion) => ({
2145
+ value: distortion.value,
2146
+ display: html`<i class="${CANVAS_BEHAVIOR_ICON}"></i> ${distortion.label}`,
2147
+ onClick: async () => {
2148
+ ObjectLayerEngineModal.selectedDistortionType = distortion.value;
2149
+ readDistortionFactorA();
2150
+ const statusNode = s(`.${distortionStatusClass}`);
2151
+ if (statusNode) {
2152
+ statusNode.style.color = '#888';
2153
+ statusNode.innerHTML = `${distortion.label} ready for direct canvas apply. factorA controls local distortion density.`;
2154
+ }
2155
+ },
2156
+ })),
2157
+ {
2158
+ kind: 'group',
2159
+ value: 'group-mosaic-behaviors',
2160
+ display: html`<div style="padding: 0 6px; color: #9d9d9d;">Mosaic drawing behaviors</div>`,
2161
+ },
2162
+ ...MOSAIC_TYPES.map((mosaic) => ({
2163
+ value: mosaic.value,
2164
+ display: html`<i class="${CANVAS_BEHAVIOR_ICON}"></i> ${mosaic.label}`,
2165
+ onClick: async () => {
2166
+ ObjectLayerEngineModal.selectedDistortionType = mosaic.value;
2167
+ readDistortionFactorA();
2168
+ const statusNode = s(`.${distortionStatusClass}`);
2169
+ if (statusNode) {
2170
+ statusNode.style.color = '#888';
2171
+ statusNode.innerHTML = `${mosaic.label} ready for direct canvas apply. factorA controls tile size and density.`;
2172
+ }
2173
+ },
2174
+ })),
2175
+ ],
2176
+ })}
2177
+ </div>
2178
+ <div class="in fll" style="width: 120px;">
2179
+ ${await Input.instance({
2180
+ id: `ol-input-distortion-factor-a`,
2181
+ label: html`factorA`,
2182
+ containerClass: 'inl',
2183
+ type: 'number',
2184
+ min: 0.01,
2185
+ max: 1,
2186
+ step: 0.01,
2187
+ value: ObjectLayerEngineModal.distortionFactorA,
2188
+ })}
2189
+ </div>
2190
+ <div class="in fll">
2191
+ ${await BtnIcon.instance({
2192
+ class: distortionApplyBtnClass,
2193
+ label: html`<i class="fa-solid fa-bolt"></i> Apply To Frame`,
2194
+ })}
2195
+ </div>
2196
+ </div>
2197
+ <div class="in ${distortionStatusClass}" style="margin-top: 6px; font-size: 12px; color: #888;">
2198
+ ${DEFAULT_DISTORTION_STATUS}
2199
+ </div>
2200
+ </div>
1103
2201
  <object-layer-png-loader id="loader" editor-selector="#ole"></object-layer-png-loader>
1104
2202
  </div>
1105
2203
 
1106
2204
  <div class="in section-mp section-mp-border">
1107
- <div class="in sub-title-modal"><i class="fa-solid fa-database"></i> Render data</div>
2205
+ <div class="in sub-title-modal"><i class="fa-solid fa-database"></i> instance data</div>
1108
2206
  ${dynamicCol({ containerSelector: options.idModal, id: idSectionA })}
1109
2207
 
1110
2208
  <div class="fl">
1111
2209
  <div class="in fll ${idSectionA}-col-a">
1112
2210
  <div class="in section-mp">
1113
- ${await DropDown.Render({
2211
+ ${await DropDown.instance({
1114
2212
  id: 'ol-dropdown-template',
1115
2213
  value: ObjectLayerEngineModal.templates[0].id,
1116
- label: html`${Translate.Render('select-template')}`,
2214
+ label: html`${Translate.instance('select-template')}`,
1117
2215
  data: ObjectLayerEngineModal.templates.map((template) => {
1118
2216
  return {
1119
2217
  value: template.id,
@@ -1128,7 +2226,7 @@ const ObjectLayerEngineModal = {
1128
2226
  </div>
1129
2227
  <div class="in fll ${idSectionA}-col-b">
1130
2228
  <div class="in section-mp-border" style="width: 135px;">
1131
- ${await Input.Render({
2229
+ ${await Input.instance({
1132
2230
  id: `ol-input-render-frame-duration`,
1133
2231
  label: html`<div class="inl ol-number-label">
1134
2232
  <i class="fa-solid fa-chart-simple"></i> Frame duration
@@ -1141,25 +2239,6 @@ const ObjectLayerEngineModal = {
1141
2239
  value: ObjectLayerEngineModal.renderFrameDuration,
1142
2240
  })}
1143
2241
  </div>
1144
- <div class="in section-mp">
1145
- ${await ToggleSwitch.Render({
1146
- id: 'ol-toggle-render-is-stateless',
1147
- wrapper: true,
1148
- wrapperLabel: html`${Translate.Render('is-stateless')}`,
1149
- disabledOnClick: true,
1150
- checked: ObjectLayerEngineModal.renderIsStateless,
1151
- on: {
1152
- unchecked: () => {
1153
- ObjectLayerEngineModal.renderIsStateless = false;
1154
- console.warn('renderIsStateless', ObjectLayerEngineModal.renderIsStateless);
1155
- },
1156
- checked: () => {
1157
- ObjectLayerEngineModal.renderIsStateless = true;
1158
- console.warn('renderIsStateless', ObjectLayerEngineModal.renderIsStateless);
1159
- },
1160
- },
1161
- })}
1162
- </div>
1163
2242
  </div>
1164
2243
  </div>
1165
2244
  ${directionsCodeBarRender}
@@ -1170,25 +2249,25 @@ const ObjectLayerEngineModal = {
1170
2249
  <div class="in fll ${idSectionB}-col-a">
1171
2250
  <div class="in section-mp section-mp-border">
1172
2251
  <div class="in sub-title-modal"><i class="fa-solid fa-database"></i> Item data</div>
1173
- ${await Input.Render({
2252
+ ${await Input.instance({
1174
2253
  id: `ol-input-item-id`,
1175
- label: html`<i class="fa-solid fa-pen-to-square"></i> ${Translate.Render('item-id')}`,
2254
+ label: html`<i class="fa-solid fa-pen-to-square"></i> ${Translate.instance('item-id')}`,
1176
2255
  containerClass: '',
1177
2256
  placeholder: true,
1178
2257
  value: loadedData?.metadata?.data?.item?.id || '',
1179
2258
  })}
1180
- ${await Input.Render({
2259
+ ${await Input.instance({
1181
2260
  id: `ol-input-item-description`,
1182
- label: html`<i class="fa-solid fa-pen-to-square"></i> ${Translate.Render('item-description')}`,
2261
+ label: html`<i class="fa-solid fa-pen-to-square"></i> ${Translate.instance('item-description')}`,
1183
2262
  containerClass: '',
1184
2263
  placeholder: true,
1185
2264
  value: loadedData?.metadata?.data?.item?.description || '',
1186
2265
  })}
1187
2266
  <div class="in section-mp">
1188
- ${await DropDown.Render({
2267
+ ${await DropDown.instance({
1189
2268
  id: 'ol-dropdown-item-type',
1190
2269
  value: ObjectLayerEngineModal.selectItemType,
1191
- label: html`${Translate.Render('select-item-type')}`,
2270
+ label: html`${Translate.instance('select-item-type')}`,
1192
2271
  data: itemTypes.map((itemType) => {
1193
2272
  return {
1194
2273
  value: itemType,
@@ -1202,10 +2281,10 @@ const ObjectLayerEngineModal = {
1202
2281
  })}
1203
2282
  </div>
1204
2283
  <div class="in section-mp">
1205
- ${await ToggleSwitch.Render({
2284
+ ${await ToggleSwitch.instance({
1206
2285
  id: 'ol-toggle-item-activable',
1207
2286
  wrapper: true,
1208
- wrapperLabel: html`${Translate.Render('item-activable')}`,
2287
+ wrapperLabel: html`${Translate.instance('item-activable')}`,
1209
2288
  disabledOnClick: true,
1210
2289
  checked: ObjectLayerEngineModal.itemActivable,
1211
2290
  on: {
@@ -1225,25 +2304,65 @@ const ObjectLayerEngineModal = {
1225
2304
  <div class="in fll ${idSectionB}-col-b">
1226
2305
  <div class="in section-mp section-mp-border">
1227
2306
  <div class="in sub-title-modal"><i class="fa-solid fa-database"></i> Stats data</div>
2307
+ <div class="fl" style="align-items: flex-end; gap: 8px; flex-wrap: wrap; margin-bottom: 10px;">
2308
+ <div class="in fll" style="width: 110px;">
2309
+ ${await Input.instance({
2310
+ id: statsRandomMinInputId,
2311
+ label: html`Random min`,
2312
+ containerClass: 'inl',
2313
+ type: 'number',
2314
+ min: 0,
2315
+ max: 10,
2316
+ placeholder: true,
2317
+ value: DEFAULT_STAT_RANDOM_MIN,
2318
+ })}
2319
+ </div>
2320
+ <div class="in fll" style="width: 110px;">
2321
+ ${await Input.instance({
2322
+ id: statsRandomMaxInputId,
2323
+ label: html`Random max`,
2324
+ containerClass: 'inl',
2325
+ type: 'number',
2326
+ min: 0,
2327
+ max: 10,
2328
+ placeholder: true,
2329
+ value: DEFAULT_STAT_RANDOM_MAX,
2330
+ })}
2331
+ </div>
2332
+ <div class="in fll">
2333
+ ${await BtnIcon.instance({
2334
+ label: html`<i class="fa-solid fa-dice"></i> Randomize`,
2335
+ class: statsRandomizeBtnClass,
2336
+ })}
2337
+ </div>
2338
+ </div>
1228
2339
  ${statsInputsRender}
1229
2340
  </div>
1230
2341
  </div>
1231
2342
  </div>
1232
2343
 
1233
2344
  <div class="fl section-mp">
1234
- ${await BtnIcon.Render({
1235
- label: html`<i class="submit-btn-icon fa-solid fa-folder-open"></i> ${Translate.Render('save')}`,
2345
+ ${await BtnIcon.instance({
2346
+ label: html`<i class="submit-btn-icon fa-solid fa-folder-open"></i>
2347
+ ${ObjectLayerEngineModal.existingObjectLayerId ? 'Update' : Translate.instance('save')}`,
1236
2348
  class: `in flr ol-btn-save`,
1237
2349
  })}
1238
- ${await BtnIcon.Render({
1239
- label: html`<i class="submit-btn-icon fa-solid fa-broom"></i> ${Translate.Render('reset')}`,
2350
+ ${ObjectLayerEngineModal.existingObjectLayerId
2351
+ ? await BtnIcon.instance({
2352
+ label: html`<i class="submit-btn-icon fa-solid fa-clone"></i> Clone`,
2353
+ class: `in flr ol-btn-clone`,
2354
+ })
2355
+ : ''}
2356
+ ${await BtnIcon.instance({
2357
+ label: html`<i class="submit-btn-icon fa-solid fa-broom"></i> ${Translate.instance('reset')}`,
1240
2358
  class: `in flr ol-btn-reset`,
1241
2359
  })}
1242
2360
  </div>
1243
2361
  <div class="in section-mp"></div>
1244
2362
  `;
1245
- },
1246
- getDirectionsFromDirectionCode(directionCode = '08') {
2363
+ };
2364
+
2365
+ static getDirectionsFromDirectionCode(directionCode = '08') {
1247
2366
  let objectLayerFrameDirections = [];
1248
2367
 
1249
2368
  switch (directionCode) {
@@ -1274,8 +2393,9 @@ const ObjectLayerEngineModal = {
1274
2393
  }
1275
2394
 
1276
2395
  return objectLayerFrameDirections;
1277
- },
1278
- toManagement: async (id = null) => {
2396
+ }
2397
+
2398
+ static toManagement = async (id = null) => {
1279
2399
  await ObjectLayerEngineModal.clearData();
1280
2400
  const subModalId = 'management';
1281
2401
  const modalId = `modal-object-layer-engine-${subModalId}`;
@@ -1302,8 +2422,9 @@ const ObjectLayerEngineModal = {
1302
2422
  await DefaultManagement.waitGridReady(modalId);
1303
2423
  await DefaultManagement.loadTable(modalId, { force: true, reload: true });
1304
2424
  });
1305
- },
1306
- Reload: async function () {
2425
+ };
2426
+
2427
+ static async Reload() {
1307
2428
  // Clear data before reload to prevent contamination
1308
2429
  ObjectLayerEngineModal.clearData();
1309
2430
  const idModal = 'modal-object-layer-engine';
@@ -1312,7 +2433,7 @@ const ObjectLayerEngineModal = {
1312
2433
  idModal,
1313
2434
  html: await Modal.Data[idModal].options.html(),
1314
2435
  });
1315
- },
1316
- };
2436
+ }
2437
+ }
1317
2438
 
1318
2439
  export { ObjectLayerEngineModal };