cyberia 2.8.885

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 (525) hide show
  1. package/.dockerignore +15 -0
  2. package/.env.development +45 -0
  3. package/.env.production +50 -0
  4. package/.env.test +45 -0
  5. package/.github/workflows/engine-cyberia.cd.yml +31 -0
  6. package/.github/workflows/engine-cyberia.ci.yml +60 -0
  7. package/.github/workflows/ghpkg.ci.yml +87 -0
  8. package/.github/workflows/npmpkg.ci.yml +72 -0
  9. package/.github/workflows/publish.ci.yml +84 -0
  10. package/.github/workflows/publish.cyberia.ci.yml +84 -0
  11. package/.github/workflows/pwa-microservices-template-page.cd.yml +72 -0
  12. package/.github/workflows/pwa-microservices-template-test.ci.yml +33 -0
  13. package/.github/workflows/release.cd.yml +37 -0
  14. package/.nycrc +9 -0
  15. package/.prettierignore +13 -0
  16. package/.prettierrc +9 -0
  17. package/.vscode/extensions.json +51 -0
  18. package/.vscode/settings.json +87 -0
  19. package/AUTHORS.md +21 -0
  20. package/CHANGELOG.md +205 -0
  21. package/Dockerfile +28 -0
  22. package/LICENSE +21 -0
  23. package/README.md +85 -0
  24. package/bin/build.js +209 -0
  25. package/bin/cron.js +47 -0
  26. package/bin/cyberia.js +145 -0
  27. package/bin/db.js +199 -0
  28. package/bin/deploy.js +1293 -0
  29. package/bin/file.js +197 -0
  30. package/bin/hwt.js +49 -0
  31. package/bin/index.js +145 -0
  32. package/bin/ssl.js +63 -0
  33. package/bin/util.js +80 -0
  34. package/bin/vs.js +74 -0
  35. package/cli.md +714 -0
  36. package/conf.js +204 -0
  37. package/deployment.yaml +138 -0
  38. package/jsconfig.json +7 -0
  39. package/jsdoc.json +32 -0
  40. package/manifests/deployment/adminer/deployment.yaml +32 -0
  41. package/manifests/deployment/adminer/kustomization.yaml +7 -0
  42. package/manifests/deployment/adminer/service.yaml +13 -0
  43. package/manifests/deployment/dd-default-development/deployment.yaml +167 -0
  44. package/manifests/deployment/dd-default-development/proxy.yaml +46 -0
  45. package/manifests/deployment/dd-test-development/deployment.yaml +174 -0
  46. package/manifests/deployment/dd-test-development/proxy.yaml +51 -0
  47. package/manifests/deployment/fastapi/backend-deployment.yml +120 -0
  48. package/manifests/deployment/fastapi/backend-service.yml +19 -0
  49. package/manifests/deployment/fastapi/frontend-deployment.yml +54 -0
  50. package/manifests/deployment/fastapi/frontend-service.yml +15 -0
  51. package/manifests/deployment/fastapi/initial_data.sh +56 -0
  52. package/manifests/deployment/kafka/deployment.yaml +69 -0
  53. package/manifests/deployment/mongo-express/deployment.yaml +60 -0
  54. package/manifests/deployment/phpmyadmin/deployment.yaml +54 -0
  55. package/manifests/deployment/spark/spark-pi-py.yaml +21 -0
  56. package/manifests/deployment/tensorflow/tf-gpu-test.yaml +65 -0
  57. package/manifests/envoy-service-nodeport.yaml +23 -0
  58. package/manifests/grafana/deployment.yaml +57 -0
  59. package/manifests/grafana/kustomization.yaml +7 -0
  60. package/manifests/grafana/pvc.yaml +12 -0
  61. package/manifests/grafana/service.yaml +14 -0
  62. package/manifests/kind-config-dev.yaml +12 -0
  63. package/manifests/kind-config.yaml +12 -0
  64. package/manifests/kubeadm-calico-config.yaml +119 -0
  65. package/manifests/kubelet-config.yaml +65 -0
  66. package/manifests/letsencrypt-prod.yaml +15 -0
  67. package/manifests/lxd/lxd-admin-profile.yaml +17 -0
  68. package/manifests/lxd/lxd-preseed.yaml +30 -0
  69. package/manifests/lxd/underpost-setup.sh +163 -0
  70. package/manifests/mariadb/config.yaml +10 -0
  71. package/manifests/mariadb/kustomization.yaml +9 -0
  72. package/manifests/mariadb/pv.yaml +12 -0
  73. package/manifests/mariadb/pvc.yaml +10 -0
  74. package/manifests/mariadb/secret.yaml +8 -0
  75. package/manifests/mariadb/service.yaml +10 -0
  76. package/manifests/mariadb/statefulset.yaml +56 -0
  77. package/manifests/mariadb/storage-class.yaml +10 -0
  78. package/manifests/mongodb/backup-access.yaml +16 -0
  79. package/manifests/mongodb/backup-cronjob.yaml +42 -0
  80. package/manifests/mongodb/backup-pv-pvc.yaml +22 -0
  81. package/manifests/mongodb/configmap.yaml +26 -0
  82. package/manifests/mongodb/headless-service.yaml +10 -0
  83. package/manifests/mongodb/kustomization.yaml +11 -0
  84. package/manifests/mongodb/pv-pvc.yaml +23 -0
  85. package/manifests/mongodb/statefulset.yaml +126 -0
  86. package/manifests/mongodb/storage-class.yaml +9 -0
  87. package/manifests/mongodb-4.4/kustomization.yaml +7 -0
  88. package/manifests/mongodb-4.4/pv-pvc.yaml +23 -0
  89. package/manifests/mongodb-4.4/service-deployment.yaml +63 -0
  90. package/manifests/mysql/kustomization.yaml +7 -0
  91. package/manifests/mysql/pv-pvc.yaml +27 -0
  92. package/manifests/mysql/statefulset.yaml +55 -0
  93. package/manifests/postgresql/configmap.yaml +9 -0
  94. package/manifests/postgresql/kustomization.yaml +10 -0
  95. package/manifests/postgresql/pv.yaml +15 -0
  96. package/manifests/postgresql/pvc.yaml +13 -0
  97. package/manifests/postgresql/service.yaml +10 -0
  98. package/manifests/postgresql/statefulset.yaml +37 -0
  99. package/manifests/prometheus/deployment.yaml +82 -0
  100. package/manifests/valkey/kustomization.yaml +7 -0
  101. package/manifests/valkey/service.yaml +11 -0
  102. package/manifests/valkey/statefulset.yaml +38 -0
  103. package/nodemon.json +6 -0
  104. package/package.json +118 -0
  105. package/proxy.yaml +35 -0
  106. package/scripts/device-scan.sh +43 -0
  107. package/scripts/gpu-diag.sh +19 -0
  108. package/scripts/maas-setup.sh +120 -0
  109. package/scripts/nat-iptables.sh +26 -0
  110. package/scripts/nvim.sh +91 -0
  111. package/scripts/snap-clean.sh +26 -0
  112. package/scripts/ssh-cluster-info.sh +14 -0
  113. package/scripts/ssl.sh +164 -0
  114. package/src/api/blockchain/blockchain.controller.js +51 -0
  115. package/src/api/blockchain/blockchain.model.js +90 -0
  116. package/src/api/blockchain/blockchain.router.js +21 -0
  117. package/src/api/blockchain/blockchain.service.js +24 -0
  118. package/src/api/core/core.controller.js +69 -0
  119. package/src/api/core/core.model.js +11 -0
  120. package/src/api/core/core.router.js +24 -0
  121. package/src/api/core/core.service.js +35 -0
  122. package/src/api/crypto/crypto.controller.js +51 -0
  123. package/src/api/crypto/crypto.model.js +23 -0
  124. package/src/api/crypto/crypto.router.js +20 -0
  125. package/src/api/crypto/crypto.service.js +64 -0
  126. package/src/api/default/default.controller.js +74 -0
  127. package/src/api/default/default.model.js +20 -0
  128. package/src/api/default/default.router.js +27 -0
  129. package/src/api/default/default.service.js +40 -0
  130. package/src/api/document/document.controller.js +66 -0
  131. package/src/api/document/document.model.js +51 -0
  132. package/src/api/document/document.router.js +24 -0
  133. package/src/api/document/document.service.js +133 -0
  134. package/src/api/file/file.controller.js +67 -0
  135. package/src/api/file/file.model.js +19 -0
  136. package/src/api/file/file.router.js +22 -0
  137. package/src/api/file/file.service.js +100 -0
  138. package/src/api/instance/instance.controller.js +69 -0
  139. package/src/api/instance/instance.model.js +40 -0
  140. package/src/api/instance/instance.router.js +34 -0
  141. package/src/api/instance/instance.service.js +70 -0
  142. package/src/api/ipfs/ipfs.controller.js +51 -0
  143. package/src/api/ipfs/ipfs.model.js +17 -0
  144. package/src/api/ipfs/ipfs.router.js +20 -0
  145. package/src/api/ipfs/ipfs.service.js +25 -0
  146. package/src/api/object-layer/README.md +85 -0
  147. package/src/api/object-layer/object-layer.controller.js +69 -0
  148. package/src/api/object-layer/object-layer.model.js +181 -0
  149. package/src/api/object-layer/object-layer.router.js +29 -0
  150. package/src/api/object-layer/object-layer.service.js +49 -0
  151. package/src/api/test/test.controller.js +59 -0
  152. package/src/api/test/test.model.js +14 -0
  153. package/src/api/test/test.router.js +21 -0
  154. package/src/api/test/test.service.js +35 -0
  155. package/src/api/user/postman_collection.json +216 -0
  156. package/src/api/user/user.build.js +16 -0
  157. package/src/api/user/user.controller.js +35 -0
  158. package/src/api/user/user.model.js +100 -0
  159. package/src/api/user/user.router.js +400 -0
  160. package/src/api/user/user.service.js +500 -0
  161. package/src/api.js +23 -0
  162. package/src/cli/baremetal.js +1310 -0
  163. package/src/cli/cloud-init.js +548 -0
  164. package/src/cli/cluster.js +834 -0
  165. package/src/cli/cron.js +95 -0
  166. package/src/cli/db.js +414 -0
  167. package/src/cli/deploy.js +661 -0
  168. package/src/cli/env.js +101 -0
  169. package/src/cli/fs.js +256 -0
  170. package/src/cli/image.js +156 -0
  171. package/src/cli/index.js +436 -0
  172. package/src/cli/lxd.js +402 -0
  173. package/src/cli/monitor.js +260 -0
  174. package/src/cli/repository.js +274 -0
  175. package/src/cli/run.js +728 -0
  176. package/src/cli/script.js +85 -0
  177. package/src/cli/secrets.js +71 -0
  178. package/src/cli/ssh.js +46 -0
  179. package/src/cli/test.js +159 -0
  180. package/src/client/Cyberia.index.js +50 -0
  181. package/src/client/CyberiaAdmin.index.js +34 -0
  182. package/src/client/CyberiaPortal.index.js +36 -0
  183. package/src/client/Default.index.js +84 -0
  184. package/src/client/components/core/404.js +20 -0
  185. package/src/client/components/core/500.js +20 -0
  186. package/src/client/components/core/Account.js +326 -0
  187. package/src/client/components/core/AgGrid.js +191 -0
  188. package/src/client/components/core/Alert.js +77 -0
  189. package/src/client/components/core/Auth.js +342 -0
  190. package/src/client/components/core/Badge.js +32 -0
  191. package/src/client/components/core/Blockchain.js +41 -0
  192. package/src/client/components/core/Blog.js +9 -0
  193. package/src/client/components/core/BtnIcon.js +111 -0
  194. package/src/client/components/core/CalendarCore.js +464 -0
  195. package/src/client/components/core/Chat.js +64 -0
  196. package/src/client/components/core/ColorPalette.js +5267 -0
  197. package/src/client/components/core/CommonJs.js +1010 -0
  198. package/src/client/components/core/Content.js +196 -0
  199. package/src/client/components/core/Css.js +1099 -0
  200. package/src/client/components/core/CssCore.js +882 -0
  201. package/src/client/components/core/D3Chart.js +44 -0
  202. package/src/client/components/core/Docs.js +376 -0
  203. package/src/client/components/core/DropDown.js +223 -0
  204. package/src/client/components/core/EventsUI.js +133 -0
  205. package/src/client/components/core/FileExplorer.js +707 -0
  206. package/src/client/components/core/FullScreen.js +36 -0
  207. package/src/client/components/core/Input.js +383 -0
  208. package/src/client/components/core/JoyStick.js +80 -0
  209. package/src/client/components/core/Keyboard.js +73 -0
  210. package/src/client/components/core/LoadingAnimation.js +159 -0
  211. package/src/client/components/core/LogIn.js +190 -0
  212. package/src/client/components/core/LogOut.js +63 -0
  213. package/src/client/components/core/Logger.js +29 -0
  214. package/src/client/components/core/Modal.js +2494 -0
  215. package/src/client/components/core/NotificationManager.js +84 -0
  216. package/src/client/components/core/ObjectLayerEngine.js +1229 -0
  217. package/src/client/components/core/ObjectLayerEngineModal.js +443 -0
  218. package/src/client/components/core/Pagination.js +207 -0
  219. package/src/client/components/core/Panel.js +772 -0
  220. package/src/client/components/core/PanelForm.js +627 -0
  221. package/src/client/components/core/Polyhedron.js +162 -0
  222. package/src/client/components/core/Recover.js +207 -0
  223. package/src/client/components/core/Responsive.js +82 -0
  224. package/src/client/components/core/RichText.js +43 -0
  225. package/src/client/components/core/Router.js +317 -0
  226. package/src/client/components/core/Scroll.js +76 -0
  227. package/src/client/components/core/SignUp.js +125 -0
  228. package/src/client/components/core/SocketIo.js +74 -0
  229. package/src/client/components/core/Stream.js +113 -0
  230. package/src/client/components/core/ToggleSwitch.js +101 -0
  231. package/src/client/components/core/ToolTip.js +90 -0
  232. package/src/client/components/core/Translate.js +522 -0
  233. package/src/client/components/core/Validator.js +115 -0
  234. package/src/client/components/core/VanillaJs.js +423 -0
  235. package/src/client/components/core/Wallet.js +106 -0
  236. package/src/client/components/core/WebComponent.js +44 -0
  237. package/src/client/components/core/Webhook.js +25 -0
  238. package/src/client/components/core/Worker.js +371 -0
  239. package/src/client/components/core/windowGetDimensions.js +269 -0
  240. package/src/client/components/cyberia/BagCyberia.js +1253 -0
  241. package/src/client/components/cyberia/BiomeCyberia.js +130 -0
  242. package/src/client/components/cyberia/CharacterCyberia.js +321 -0
  243. package/src/client/components/cyberia/CommonCyberia.js +1834 -0
  244. package/src/client/components/cyberia/CssCyberia.js +816 -0
  245. package/src/client/components/cyberia/ElementPreviewCyberia.js +183 -0
  246. package/src/client/components/cyberia/ElementsCyberia.js +146 -0
  247. package/src/client/components/cyberia/InteractionPanelCyberia.js +1043 -0
  248. package/src/client/components/cyberia/JoyStickCyberia.js +53 -0
  249. package/src/client/components/cyberia/LogInCyberia.js +68 -0
  250. package/src/client/components/cyberia/LogOutCyberia.js +24 -0
  251. package/src/client/components/cyberia/MainUserCyberia.js +424 -0
  252. package/src/client/components/cyberia/MapCyberia.js +160 -0
  253. package/src/client/components/cyberia/MatrixCyberia.js +147 -0
  254. package/src/client/components/cyberia/MenuCyberia.js +575 -0
  255. package/src/client/components/cyberia/PixiCyberia.js +1639 -0
  256. package/src/client/components/cyberia/PointAndClickMovementCyberia.js +146 -0
  257. package/src/client/components/cyberia/QuestCyberia.js +1420 -0
  258. package/src/client/components/cyberia/RoutesCyberia.js +47 -0
  259. package/src/client/components/cyberia/SettingsCyberia.js +16 -0
  260. package/src/client/components/cyberia/SignUpCyberia.js +14 -0
  261. package/src/client/components/cyberia/SkillCyberia.js +124 -0
  262. package/src/client/components/cyberia/SocketIoCyberia.js +211 -0
  263. package/src/client/components/cyberia/TileCyberia.js +685 -0
  264. package/src/client/components/cyberia/TranslateCyberia.js +96 -0
  265. package/src/client/components/cyberia/UniverseCyberia.js +14 -0
  266. package/src/client/components/cyberia/WebhookCyberia.js +13 -0
  267. package/src/client/components/cyberia/WikiCyberia.js +144 -0
  268. package/src/client/components/cyberia/WorldCyberia.js +680 -0
  269. package/src/client/components/cyberia-admin/BiomeCyberiaAdmin.js +978 -0
  270. package/src/client/components/cyberia-admin/CommonCyberiaAdmin.js +29 -0
  271. package/src/client/components/cyberia-admin/CssCyberiaAdmin.js +15 -0
  272. package/src/client/components/cyberia-admin/ElementsCyberiaAdmin.js +38 -0
  273. package/src/client/components/cyberia-admin/InstanceEngineCyberiaAdmin.js +180 -0
  274. package/src/client/components/cyberia-admin/LogInCyberiaAdmin.js +34 -0
  275. package/src/client/components/cyberia-admin/LogOutCyberiaAdmin.js +24 -0
  276. package/src/client/components/cyberia-admin/MenuCyberiaAdmin.js +660 -0
  277. package/src/client/components/cyberia-admin/RoutesCyberiaAdmin.js +57 -0
  278. package/src/client/components/cyberia-admin/ServerCyberiaAdmin.js +129 -0
  279. package/src/client/components/cyberia-admin/SettingsCyberiaAdmin.js +16 -0
  280. package/src/client/components/cyberia-admin/SignUpCyberiaAdmin.js +11 -0
  281. package/src/client/components/cyberia-admin/SocketIoCyberiaAdmin.js +53 -0
  282. package/src/client/components/cyberia-admin/TranslateCyberiaAdmin.js +7 -0
  283. package/src/client/components/cyberia-biome/CityCyberiaBiome.js +209 -0
  284. package/src/client/components/cyberia-biome/CityInteriorCyberiaBiome.js +253 -0
  285. package/src/client/components/cyberia-biome/ColorChaosCyberiaBiome.js +26 -0
  286. package/src/client/components/cyberia-biome/ForestCyberiaBiome.js +191 -0
  287. package/src/client/components/cyberia-biome/GridBaseCyberiaBiome.js +364 -0
  288. package/src/client/components/cyberia-biome/SeedCityCyberiaBiome.js +347 -0
  289. package/src/client/components/cyberia-biome/ShopCyberiaBiome.js +12 -0
  290. package/src/client/components/cyberia-biome/SpaceCyberiaBiome.js +58 -0
  291. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +29 -0
  292. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +132 -0
  293. package/src/client/components/cyberia-portal/ElementsCyberiaPortal.js +38 -0
  294. package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +18 -0
  295. package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +12 -0
  296. package/src/client/components/cyberia-portal/MenuCyberiaPortal.js +487 -0
  297. package/src/client/components/cyberia-portal/RoutesCyberiaPortal.js +45 -0
  298. package/src/client/components/cyberia-portal/ServerCyberiaPortal.js +136 -0
  299. package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +16 -0
  300. package/src/client/components/cyberia-portal/SignUpCyberiaPortal.js +11 -0
  301. package/src/client/components/cyberia-portal/SocketIoCyberiaPortal.js +52 -0
  302. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +12 -0
  303. package/src/client/components/default/CommonDefault.js +29 -0
  304. package/src/client/components/default/CssDefault.js +27 -0
  305. package/src/client/components/default/ElementsDefault.js +38 -0
  306. package/src/client/components/default/LogInDefault.js +14 -0
  307. package/src/client/components/default/LogOutDefault.js +10 -0
  308. package/src/client/components/default/MenuDefault.js +743 -0
  309. package/src/client/components/default/RoutesDefault.js +48 -0
  310. package/src/client/components/default/SettingsDefault.js +16 -0
  311. package/src/client/components/default/SignUpDefault.js +9 -0
  312. package/src/client/components/default/SocketIoDefault.js +54 -0
  313. package/src/client/components/default/TranslateDefault.js +7 -0
  314. package/src/client/public/default/android-chrome-144x144.png +0 -0
  315. package/src/client/public/default/android-chrome-192x192.png +0 -0
  316. package/src/client/public/default/android-chrome-256x256.png +0 -0
  317. package/src/client/public/default/android-chrome-36x36.png +0 -0
  318. package/src/client/public/default/android-chrome-384x384.png +0 -0
  319. package/src/client/public/default/android-chrome-48x48.png +0 -0
  320. package/src/client/public/default/android-chrome-512x512.png +0 -0
  321. package/src/client/public/default/android-chrome-72x72.png +0 -0
  322. package/src/client/public/default/android-chrome-96x96.png +0 -0
  323. package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
  324. package/src/client/public/default/apple-touch-icon-114x114-precomposed.png +0 -0
  325. package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
  326. package/src/client/public/default/apple-touch-icon-120x120-precomposed.png +0 -0
  327. package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
  328. package/src/client/public/default/apple-touch-icon-144x144-precomposed.png +0 -0
  329. package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
  330. package/src/client/public/default/apple-touch-icon-152x152-precomposed.png +0 -0
  331. package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
  332. package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
  333. package/src/client/public/default/apple-touch-icon-180x180-precomposed.png +0 -0
  334. package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
  335. package/src/client/public/default/apple-touch-icon-57x57-precomposed.png +0 -0
  336. package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
  337. package/src/client/public/default/apple-touch-icon-60x60-precomposed.png +0 -0
  338. package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
  339. package/src/client/public/default/apple-touch-icon-72x72-precomposed.png +0 -0
  340. package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
  341. package/src/client/public/default/apple-touch-icon-76x76-precomposed.png +0 -0
  342. package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
  343. package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
  344. package/src/client/public/default/apple-touch-icon.png +0 -0
  345. package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
  346. package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
  347. package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
  348. package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
  349. package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
  350. package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
  351. package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
  352. package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
  353. package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
  354. package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
  355. package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
  356. package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
  357. package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
  358. package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
  359. package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
  360. package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
  361. package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
  362. package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
  363. package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
  364. package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
  365. package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
  366. package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
  367. package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
  368. package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
  369. package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
  370. package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
  371. package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
  372. package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
  373. package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
  374. package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
  375. package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
  376. package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
  377. package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
  378. package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
  379. package/src/client/public/default/assets/background/dark.jpg +0 -0
  380. package/src/client/public/default/assets/background/dark.svg +557 -0
  381. package/src/client/public/default/assets/background/white.jpg +0 -0
  382. package/src/client/public/default/assets/background/white0-min.jpg +0 -0
  383. package/src/client/public/default/assets/background/white0.jpg +0 -0
  384. package/src/client/public/default/assets/logo/base-icon.png +0 -0
  385. package/src/client/public/default/assets/logo/underpost.gif +0 -0
  386. package/src/client/public/default/assets/mailer/api-user-check.png +0 -0
  387. package/src/client/public/default/assets/mailer/api-user-default-avatar.png +0 -0
  388. package/src/client/public/default/assets/mailer/api-user-invalid-token.png +0 -0
  389. package/src/client/public/default/assets/mailer/api-user-recover.png +0 -0
  390. package/src/client/public/default/browserconfig.xml +12 -0
  391. package/src/client/public/default/favicon-16x16.png +0 -0
  392. package/src/client/public/default/favicon-32x32.png +0 -0
  393. package/src/client/public/default/favicon-48x48.png +0 -0
  394. package/src/client/public/default/favicon.ico +0 -0
  395. package/src/client/public/default/manifest.webmanifest +69 -0
  396. package/src/client/public/default/mstile-144x144.png +0 -0
  397. package/src/client/public/default/mstile-150x150.png +0 -0
  398. package/src/client/public/default/mstile-310x150.png +0 -0
  399. package/src/client/public/default/mstile-310x310.png +0 -0
  400. package/src/client/public/default/mstile-70x70.png +0 -0
  401. package/src/client/public/default/plantuml/client-conf.svg +1 -0
  402. package/src/client/public/default/plantuml/client-schema.svg +1 -0
  403. package/src/client/public/default/plantuml/cron-conf.svg +1 -0
  404. package/src/client/public/default/plantuml/cron-schema.svg +1 -0
  405. package/src/client/public/default/plantuml/server-conf.svg +1 -0
  406. package/src/client/public/default/plantuml/server-schema.svg +1 -0
  407. package/src/client/public/default/plantuml/ssr-conf.svg +1 -0
  408. package/src/client/public/default/plantuml/ssr-schema.svg +1 -0
  409. package/src/client/public/default/safari-pinned-tab.svg +24 -0
  410. package/src/client/public/default/site.webmanifest +69 -0
  411. package/src/client/public/default/sitemap +148 -0
  412. package/src/client/public/default/yandex-browser-50x50.png +0 -0
  413. package/src/client/public/default/yandex-browser-manifest.json +9 -0
  414. package/src/client/public/doc/favicon.ico +0 -0
  415. package/src/client/public/doc/sitemap +148 -0
  416. package/src/client/public/test/favicon.ico +0 -0
  417. package/src/client/public/test/sitemap +148 -0
  418. package/src/client/services/blockchain/blockchain.service.js +73 -0
  419. package/src/client/services/core/core.service.js +165 -0
  420. package/src/client/services/crypto/crypto.service.js +73 -0
  421. package/src/client/services/default/default.management.js +450 -0
  422. package/src/client/services/default/default.service.js +98 -0
  423. package/src/client/services/document/document.service.js +97 -0
  424. package/src/client/services/file/file.service.js +72 -0
  425. package/src/client/services/instance/instance.management.js +78 -0
  426. package/src/client/services/instance/instance.service.js +97 -0
  427. package/src/client/services/ipfs/ipfs.service.js +73 -0
  428. package/src/client/services/object-layer/object-layer.service.js +93 -0
  429. package/src/client/services/test/test.service.js +73 -0
  430. package/src/client/services/user/user.management.js +56 -0
  431. package/src/client/services/user/user.service.js +108 -0
  432. package/src/client/ssr/Render.js +237 -0
  433. package/src/client/ssr/body/404.js +73 -0
  434. package/src/client/ssr/body/500.js +72 -0
  435. package/src/client/ssr/body/CacheControl.js +114 -0
  436. package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +90 -0
  437. package/src/client/ssr/body/CyberiaSplashScreenLore.js +424 -0
  438. package/src/client/ssr/body/DefaultSplashScreen.js +90 -0
  439. package/src/client/ssr/email/DefaultRecoverEmail.js +21 -0
  440. package/src/client/ssr/email/DefaultVerifyEmail.js +17 -0
  441. package/src/client/ssr/head/Css.js +241 -0
  442. package/src/client/ssr/head/CyberiaAdminScripts.js +6 -0
  443. package/src/client/ssr/head/CyberiaPortalScripts.js +6 -0
  444. package/src/client/ssr/head/CyberiaScripts.js +6 -0
  445. package/src/client/ssr/head/DefaultScripts.js +6 -0
  446. package/src/client/ssr/head/Microdata.js +11 -0
  447. package/src/client/ssr/head/Production.js +1 -0
  448. package/src/client/ssr/head/Pwa.js +146 -0
  449. package/src/client/ssr/head/Seo.js +15 -0
  450. package/src/client/ssr/mailer/DefaultRecoverEmail.js +21 -0
  451. package/src/client/ssr/mailer/DefaultVerifyEmail.js +17 -0
  452. package/src/client/ssr/offline/Maintenance.js +63 -0
  453. package/src/client/ssr/offline/NoNetworkConnection.js +67 -0
  454. package/src/client/ssr/pages/404.js +12 -0
  455. package/src/client/ssr/pages/500.js +12 -0
  456. package/src/client/ssr/pages/Test.js +198 -0
  457. package/src/client/ssr/pages/maintenance.js +14 -0
  458. package/src/client/ssr/pages/offline.js +21 -0
  459. package/src/client/sw/default.sw.js +108 -0
  460. package/src/client/sw/template.sw.js +84 -0
  461. package/src/client.build.js +22 -0
  462. package/src/client.dev.js +24 -0
  463. package/src/db/DataBaseProvider.js +98 -0
  464. package/src/db/mariadb/MariaDB.js +66 -0
  465. package/src/db/mongo/MongooseDB.js +70 -0
  466. package/src/index.js +198 -0
  467. package/src/mailer/EmailRender.js +116 -0
  468. package/src/mailer/MailerProvider.js +213 -0
  469. package/src/monitor.js +24 -0
  470. package/src/proxy.js +22 -0
  471. package/src/runtime/express/Express.js +256 -0
  472. package/src/runtime/lampp/Dockerfile +50 -0
  473. package/src/runtime/lampp/Lampp.js +343 -0
  474. package/src/server/auth.js +689 -0
  475. package/src/server/backup.js +96 -0
  476. package/src/server/client-build-docs.js +205 -0
  477. package/src/server/client-build-live.js +109 -0
  478. package/src/server/client-build.js +690 -0
  479. package/src/server/client-dev-server.js +87 -0
  480. package/src/server/client-formatted.js +87 -0
  481. package/src/server/client-icons.js +108 -0
  482. package/src/server/conf.js +1071 -0
  483. package/src/server/crypto.js +210 -0
  484. package/src/server/dns.js +276 -0
  485. package/src/server/downloader.js +74 -0
  486. package/src/server/json-schema.js +77 -0
  487. package/src/server/logger.js +197 -0
  488. package/src/server/network.js +72 -0
  489. package/src/server/object-layer.js +294 -0
  490. package/src/server/peer.js +69 -0
  491. package/src/server/process.js +171 -0
  492. package/src/server/proxy.js +110 -0
  493. package/src/server/runtime.js +170 -0
  494. package/src/server/ssr.js +127 -0
  495. package/src/server/start.js +161 -0
  496. package/src/server/tls.js +251 -0
  497. package/src/server/valkey.js +293 -0
  498. package/src/server.js +25 -0
  499. package/src/ws/IoInterface.js +139 -0
  500. package/src/ws/IoServer.js +88 -0
  501. package/src/ws/core/channels/core.ws.chat.js +23 -0
  502. package/src/ws/core/channels/core.ws.mailer.js +35 -0
  503. package/src/ws/core/channels/core.ws.stream.js +31 -0
  504. package/src/ws/core/core.ws.connection.js +62 -0
  505. package/src/ws/core/core.ws.emit.js +53 -0
  506. package/src/ws/core/core.ws.server.js +76 -0
  507. package/src/ws/core/management/core.ws.chat.js +8 -0
  508. package/src/ws/core/management/core.ws.mailer.js +16 -0
  509. package/src/ws/core/management/core.ws.stream.js +8 -0
  510. package/src/ws/cyberia/channels/cyberia.ws.bot.js +56 -0
  511. package/src/ws/cyberia/channels/cyberia.ws.skill.js +51 -0
  512. package/src/ws/cyberia/channels/cyberia.ws.user.js +437 -0
  513. package/src/ws/cyberia/cyberia.ws.connection.js +36 -0
  514. package/src/ws/cyberia/cyberia.ws.emit.js +14 -0
  515. package/src/ws/cyberia/cyberia.ws.server.js +67 -0
  516. package/src/ws/cyberia/management/cyberia.ws.bot.js +669 -0
  517. package/src/ws/cyberia/management/cyberia.ws.skill.js +441 -0
  518. package/src/ws/cyberia/management/cyberia.ws.user.js +188 -0
  519. package/src/ws/default/channels/default.ws.main.js +16 -0
  520. package/src/ws/default/default.ws.connection.js +22 -0
  521. package/src/ws/default/default.ws.emit.js +14 -0
  522. package/src/ws/default/default.ws.server.js +20 -0
  523. package/src/ws/default/management/default.ws.main.js +8 -0
  524. package/test/api.test.js +53 -0
  525. package/test/crypto.test.js +117 -0
@@ -0,0 +1,1229 @@
1
+ import { darkTheme, renderChessPattern } from './Css.js';
2
+ import { append, htmls } from './VanillaJs.js';
3
+
4
+ class ObjectLayerEngineElement extends HTMLElement {
5
+ constructor() {
6
+ super();
7
+ this.attachShadow({ mode: 'open' });
8
+ this.shadowRoot.innerHTML = html`
9
+ <style>
10
+ :host {
11
+ --border: 1px solid #bbb;
12
+ --gap: 8px;
13
+ display: inline-block;
14
+ font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial;
15
+ }
16
+ .wrap {
17
+ display: flex;
18
+ flex-direction: column;
19
+ gap: var(--gap);
20
+ align-items: flex-start;
21
+ }
22
+ .canvas-frame {
23
+ border: var(--border);
24
+ display: inline-block;
25
+ line-height: 0;
26
+ position: relative;
27
+ background: transparent;
28
+ }
29
+ canvas.canvas-layer {
30
+ display: block;
31
+ image-rendering: pixelated;
32
+ touch-action: none;
33
+ cursor: crosshair;
34
+ }
35
+ canvas.grid-layer {
36
+ position: absolute;
37
+ left: 0;
38
+ top: 0;
39
+ pointer-events: none;
40
+ }
41
+ .toolbar {
42
+ display: flex;
43
+ gap: 8px;
44
+ flex-wrap: wrap;
45
+ align-items: center;
46
+ }
47
+ .toolbar label {
48
+ display: inline-flex;
49
+ gap: 6px;
50
+ align-items: center;
51
+ }
52
+ .group {
53
+ display: inline-flex;
54
+ gap: 6px;
55
+ align-items: center;
56
+ }
57
+ </style>
58
+
59
+ <div class="wrap">
60
+ <div class="toolbar">
61
+ <input type="color" part="color" title="Brush color" value="#000000" />
62
+ <select part="tool">
63
+ <option value="pencil">pencil</option>
64
+ <option value="eraser">eraser</option>
65
+ <option value="fill">fill</option>
66
+ <option value="eyedropper">eyedropper</option>
67
+ </select>
68
+
69
+ <label>brush <input type="number" part="brush-size" min="1" value="1" /></label>
70
+ <label>pixel-size <input type="number" part="pixel-size" min="1" value="16" /></label>
71
+
72
+ <!-- New: cell dimensions (width x height) -->
73
+ <label
74
+ >cells <input type="number" part="cell-width" min="1" value="16" style="width:6ch" /> x
75
+ <input type="number" part="cell-height" min="1" value="16" style="width:6ch"
76
+ /></label>
77
+
78
+ <label class="switch"> <input type="checkbox" part="toggle-grid" /> grid </label>
79
+
80
+ <!-- New: transform tools -->
81
+ <div class="group">
82
+ <button part="flip-h" title="Flip horizontally">Flip H</button>
83
+ <button part="flip-v" title="Flip vertically">Flip V</button>
84
+ <button part="rot-ccw" title="Rotate -90°">⟲</button>
85
+ <button part="rot-cw" title="Rotate +90°">⟳</button>
86
+ </div>
87
+
88
+ <label
89
+ >opacity <input type="range" part="opacity" min="0" max="255" value="255" style="width:10rem" /><input
90
+ type="number"
91
+ part="opacity-num"
92
+ min="0"
93
+ max="255"
94
+ value="255"
95
+ style="width:5ch;margin-left:4px"
96
+ /></label>
97
+
98
+ <button part="clear" title="Clear (make fully transparent)">Clear</button>
99
+
100
+ <button part="export">Export PNG</button>
101
+ <button part="export-json">Export JSON</button>
102
+ <button part="import-json">Import JSON</button>
103
+ </div>
104
+ <div class="canvas-frame" style="${renderChessPattern()}">
105
+ <canvas part="canvas" class="canvas-layer"></canvas>
106
+ <canvas part="grid" class="grid-layer"></canvas>
107
+ </div>
108
+ </div>
109
+ `;
110
+
111
+ // DOM
112
+ this._pixelCanvas = this.shadowRoot.querySelector('canvas[part="canvas"]');
113
+ this._gridCanvas = this.shadowRoot.querySelector('canvas[part="grid"]');
114
+ this._colorInput = this.shadowRoot.querySelector('input[part="color"]');
115
+ this._toolSelect = this.shadowRoot.querySelector('select[part="tool"]');
116
+ this._brushSizeInput = this.shadowRoot.querySelector('input[part="brush-size"]');
117
+ this._pixelSizeInput = this.shadowRoot.querySelector('input[part="pixel-size"]');
118
+ this._exportBtn = this.shadowRoot.querySelector('button[part="export"]');
119
+ this._exportJsonBtn = this.shadowRoot.querySelector('button[part="export-json"]');
120
+ this._importJsonBtn = this.shadowRoot.querySelector('button[part="import-json"]');
121
+ this._toggleGrid = this.shadowRoot.querySelector('input[part="toggle-grid"]');
122
+
123
+ // new controls
124
+ this._widthInput = this.shadowRoot.querySelector('input[part="cell-width"]');
125
+ this._heightInput = this.shadowRoot.querySelector('input[part="cell-height"]');
126
+ this._flipHBtn = this.shadowRoot.querySelector('button[part="flip-h"]');
127
+ this._flipVBtn = this.shadowRoot.querySelector('button[part="flip-v"]');
128
+ this._rotCCWBtn = this.shadowRoot.querySelector('button[part="rot-ccw"]');
129
+ this._rotCWBtn = this.shadowRoot.querySelector('button[part="rot-cw"]');
130
+ this._clearBtn = this.shadowRoot.querySelector('button[part="clear"]');
131
+ this._opacityRange = this.shadowRoot.querySelector('input[part="opacity"]');
132
+ this._opacityNumber = this.shadowRoot.querySelector('input[part="opacity-num"]');
133
+
134
+ // internal state
135
+ this._width = 16;
136
+ this._height = 16;
137
+ this._pixelSize = 16;
138
+ this._brushSize = 1;
139
+ // brush color stored as [r,g,b,a]
140
+ this._brushColor = [0, 0, 0, 255];
141
+ this._matrix = this._createEmptyMatrix(this._width, this._height);
142
+
143
+ this._pixelCtx = null;
144
+ this._gridCtx = null;
145
+
146
+ this._isPointerDown = false;
147
+ this._tool = 'pencil';
148
+ this._showGrid = false;
149
+
150
+ // binds
151
+ this._onPointerDown = this._onPointerDown.bind(this);
152
+ this._onPointerMove = this._onPointerMove.bind(this);
153
+ this._onPointerUp = this._onPointerUp.bind(this);
154
+
155
+ // transform methods bound (useful if passing as callbacks)
156
+ this.flipHorizontal = this.flipHorizontal.bind(this);
157
+ this.flipVertical = this.flipVertical.bind(this);
158
+ this.rotateCW = this.rotateCW.bind(this);
159
+ this.rotateCCW = this.rotateCCW.bind(this);
160
+ }
161
+
162
+ static get observedAttributes() {
163
+ return ['width', 'height', 'pixel-size'];
164
+ }
165
+ attributeChangedCallback(name, oldV, newV) {
166
+ if (oldV === newV) return;
167
+ if (name === 'width') this.width = parseInt(newV, 10) || this._width;
168
+ if (name === 'height') this.height = parseInt(newV, 10) || this._height;
169
+ if (name === 'pixel-size') this.pixelSize = parseInt(newV, 10) || this._pixelSize;
170
+ }
171
+
172
+ connectedCallback() {
173
+ // respect attributes if present
174
+ if (this.hasAttribute('width')) this._width = Math.max(1, parseInt(this.getAttribute('width'), 10));
175
+ if (this.hasAttribute('height')) this._height = Math.max(1, parseInt(this.getAttribute('height'), 10));
176
+ if (this.hasAttribute('pixel-size')) this._pixelSize = Math.max(1, parseInt(this.getAttribute('pixel-size'), 10));
177
+
178
+ this._setupContextsAndSize();
179
+
180
+ // set initial UI control values (keeps in sync with attributes)
181
+ if (this._widthInput) this._widthInput.value = String(this._width);
182
+ if (this._heightInput) this._heightInput.value = String(this._height);
183
+ if (this._pixelSizeInput) this._pixelSizeInput.value = String(this._pixelSize);
184
+ if (this._brushSizeInput) this._brushSizeInput.value = String(this._brushSize);
185
+
186
+ // initialize color & opacity UI
187
+ if (this._colorInput) this._colorInput.value = this._rgbaToHex(this._brushColor);
188
+ if (this._opacityRange) this._opacityRange.value = String(this._brushColor[3]);
189
+ if (this._opacityNumber) this._opacityNumber.value = String(this._brushColor[3]);
190
+
191
+ // UI events
192
+ this._colorInput.addEventListener('input', (e) => {
193
+ const rgb = this._hexToRgba(e.target.value);
194
+ // keep current alpha
195
+ this.setBrushColor([rgb[0], rgb[1], rgb[2], this._brushColor[3]]);
196
+ });
197
+ this._toolSelect.addEventListener('change', (e) => this.setTool(e.target.value));
198
+ this._brushSizeInput.addEventListener('change', (e) => this.setBrushSize(parseInt(e.target.value, 10) || 1));
199
+ this._pixelSizeInput.addEventListener('change', (e) => {
200
+ this.pixelSize = Math.max(1, parseInt(e.target.value, 10) || 1);
201
+ });
202
+ this._toggleGrid.addEventListener('change', (e) => {
203
+ this._showGrid = !!e.target.checked;
204
+ this._renderGrid();
205
+ });
206
+
207
+ // opacity controls - keep range and number in sync
208
+ if (this._opacityRange) {
209
+ this._opacityRange.addEventListener('input', (e) => {
210
+ const v = Math.max(0, Math.min(255, parseInt(e.target.value, 10) || 0));
211
+ this.setBrushAlpha(v);
212
+ });
213
+ }
214
+ if (this._opacityNumber) {
215
+ this._opacityNumber.addEventListener('change', (e) => {
216
+ const v = Math.max(0, Math.min(255, parseInt(e.target.value, 10) || 0));
217
+ this.setBrushAlpha(v);
218
+ });
219
+ }
220
+
221
+ // width/height change -> resize (preserve existing content)
222
+ if (this._widthInput)
223
+ this._widthInput.addEventListener('change', (e) => {
224
+ const val = Math.max(1, parseInt(e.target.value, 10) || 1);
225
+ // keep value synced (will update input again in resize)
226
+ this.resize(val, this._height, { preserve: true });
227
+ });
228
+ if (this._heightInput)
229
+ this._heightInput.addEventListener('change', (e) => {
230
+ const val = Math.max(1, parseInt(e.target.value, 10) || 1);
231
+ this.resize(this._width, val, { preserve: true });
232
+ });
233
+
234
+ // transform buttons
235
+ if (this._flipHBtn) this._flipHBtn.addEventListener('click', this.flipHorizontal);
236
+ if (this._flipVBtn) this._flipVBtn.addEventListener('click', this.flipVertical);
237
+ if (this._rotCWBtn) this._rotCWBtn.addEventListener('click', this.rotateCW);
238
+ if (this._rotCCWBtn) this._rotCCWBtn.addEventListener('click', this.rotateCCW);
239
+
240
+ // clear button (makes canvas fully transparent)
241
+ if (this._clearBtn) this._clearBtn.addEventListener('click', () => this.clear([0, 0, 0, 0]));
242
+
243
+ // Export/Import
244
+ this._exportBtn.addEventListener('click', () => this.exportPNG());
245
+ this._exportJsonBtn.addEventListener('click', () => {
246
+ const json = this.exportMatrixJSON();
247
+ const blob = new Blob([json], { type: 'application/json' });
248
+ const url = URL.createObjectURL(blob);
249
+ const a = document.createElement('a');
250
+ a.href = url;
251
+ a.download = 'object-layer.json';
252
+ a.click();
253
+ URL.revokeObjectURL(url);
254
+ });
255
+
256
+ this._importJsonBtn.addEventListener('click', async () => {
257
+ const file = await this._pickFile();
258
+ if (!file) return;
259
+ const text = await file.text();
260
+ try {
261
+ this.importMatrixJSON(text);
262
+ } catch (err) {
263
+ console.error(err);
264
+ alert('Invalid JSON');
265
+ }
266
+ });
267
+
268
+ // Pointer events
269
+ this._pixelCanvas.addEventListener('pointerdown', this._onPointerDown);
270
+ window.addEventListener('pointermove', this._onPointerMove);
271
+ window.addEventListener('pointerup', this._onPointerUp);
272
+
273
+ this.render();
274
+ }
275
+
276
+ disconnectedCallback() {
277
+ this._pixelCanvas.removeEventListener('pointerdown', this._onPointerDown);
278
+ window.removeEventListener('pointermove', this._onPointerMove);
279
+ window.removeEventListener('pointerup', this._onPointerUp);
280
+
281
+ if (this._flipHBtn) this._flipHBtn.removeEventListener('click', this.flipHorizontal);
282
+ if (this._flipVBtn) this._flipVBtn.removeEventListener('click', this.flipVertical);
283
+ if (this._rotCWBtn) this._rotCWBtn.removeEventListener('click', this.rotateCW);
284
+ if (this._rotCCWBtn) this._rotCCWBtn.removeEventListener('click', this.rotateCCW);
285
+ if (this._clearBtn) this._clearBtn.removeEventListener('click', () => this.clear([0, 0, 0, 0]));
286
+ if (this._opacityRange) this._opacityRange.removeEventListener('input', () => {});
287
+ if (this._opacityNumber) this._opacityNumber.removeEventListener('change', () => {});
288
+ }
289
+
290
+ // ---------------- Matrix helpers ----------------
291
+ _createEmptyMatrix(w, h) {
292
+ const mat = new Array(h);
293
+ for (let y = 0; y < h; y++) {
294
+ mat[y] = new Array(w);
295
+ for (let x = 0; x < w; x++) mat[y][x] = [0, 0, 0, 0];
296
+ }
297
+ return mat;
298
+ }
299
+
300
+ createMatrix(width, height, fill = [0, 0, 0, 0]) {
301
+ const w = Math.max(1, Math.floor(width));
302
+ const h = Math.max(1, Math.floor(height));
303
+ const mat = this._createEmptyMatrix(w, h);
304
+ for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) mat[y][x] = fill.slice();
305
+ return mat;
306
+ }
307
+
308
+ loadMatrix(matrix) {
309
+ if (!Array.isArray(matrix) || matrix.length === 0) throw new TypeError('matrix must be non-empty 2D array');
310
+ const h = matrix.length;
311
+ const w = matrix[0].length;
312
+ for (let y = 0; y < h; y++) {
313
+ if (!Array.isArray(matrix[y]) || matrix[y].length !== w) throw new TypeError('matrix must be rectangular');
314
+ for (let x = 0; x < w; x++) {
315
+ const v = matrix[y][x];
316
+ if (!Array.isArray(v) || v.length !== 4) throw new TypeError('each cell must be [r,g,b,a]');
317
+ matrix[y][x] = v.map((n) => this._clampInt(n));
318
+ }
319
+ }
320
+ this._width = w;
321
+ this._height = h;
322
+ this._matrix = matrix.map((r) => r.map((c) => c.slice()));
323
+ this._setupContextsAndSize();
324
+ this.render();
325
+ this.dispatchEvent(new CustomEvent('matrixload', { detail: { width: w, height: h } }));
326
+ }
327
+
328
+ clear(fill = [0, 0, 0, 0]) {
329
+ for (let y = 0; y < this._height; y++) for (let x = 0; x < this._width; x++) this._matrix[y][x] = fill.slice();
330
+ this.render();
331
+ this.dispatchEvent(new CustomEvent('clear'));
332
+ }
333
+
334
+ resize(w, h, { preserve = true } = {}) {
335
+ const nw = Math.max(1, Math.floor(w));
336
+ const nh = Math.max(1, Math.floor(h));
337
+ const newMat = this._createEmptyMatrix(nw, nh);
338
+ if (preserve) {
339
+ const minW = Math.min(nw, this._width);
340
+ const minH = Math.min(nh, this._height);
341
+ for (let y = 0; y < minH; y++) for (let x = 0; x < minW; x++) newMat[y][x] = this._matrix[y][x].slice();
342
+ }
343
+ this._width = nw;
344
+ this._height = nh;
345
+ this._matrix = newMat;
346
+
347
+ // keep inputs and attributes in sync
348
+ if (this._widthInput) this._widthInput.value = String(this._width);
349
+ if (this._heightInput) this._heightInput.value = String(this._height);
350
+ this.setAttribute('width', String(this._width));
351
+ this.setAttribute('height', String(this._height));
352
+
353
+ this._setupContextsAndSize();
354
+ this.render();
355
+ this.dispatchEvent(new CustomEvent('resize', { detail: { width: nw, height: nh } }));
356
+ }
357
+
358
+ setPixel(x, y, rgba, renderNow = true) {
359
+ if (!this._inBounds(x, y)) return false;
360
+ this._matrix[y][x] = rgba.map((n) => this._clampInt(n));
361
+ if (renderNow) this.render();
362
+ this.dispatchEvent(new CustomEvent('pixelchange', { detail: { x, y, rgba: this._matrix[y][x].slice() } }));
363
+ return true;
364
+ }
365
+
366
+ getPixel(x, y) {
367
+ return this._inBounds(x, y) ? this._matrix[y][x].slice() : null;
368
+ }
369
+ _inBounds(x, y) {
370
+ return x >= 0 && y >= 0 && x < this._width && y < this._height;
371
+ }
372
+ _clampInt(v) {
373
+ const n = Number(v) || 0;
374
+ return Math.min(255, Math.max(0, Math.floor(n)));
375
+ }
376
+
377
+ // ---------------- Canvas sizing and contexts ----------------
378
+ _setupContextsAndSize() {
379
+ // logical canvas (one logical pixel per image pixel). CSS scales by pixelSize.
380
+ this._pixelCanvas.width = this._width;
381
+ this._pixelCanvas.height = this._height;
382
+ this._pixelCanvas.style.width = `${this._width * this._pixelSize}px`;
383
+ this._pixelCanvas.style.height = `${this._height * this._pixelSize}px`;
384
+
385
+ // grid overlay uses CSS pixel coordinates
386
+ this._gridCanvas.width = this._width * this._pixelSize;
387
+ this._gridCanvas.height = this._height * this._pixelSize;
388
+ this._gridCanvas.style.width = this._pixelCanvas.style.width;
389
+ this._gridCanvas.style.height = this._pixelCanvas.style.height;
390
+
391
+ this._pixelCtx = this._pixelCanvas.getContext('2d');
392
+ this._gridCtx = this._gridCanvas.getContext('2d');
393
+ try {
394
+ this._pixelCtx.imageSmoothingEnabled = false;
395
+ this._gridCtx.imageSmoothingEnabled = false;
396
+ } catch (e) {}
397
+ this._renderGrid();
398
+ }
399
+
400
+ render() {
401
+ // sanity: ensure matrix shape matches
402
+ if (
403
+ !Array.isArray(this._matrix) ||
404
+ this._matrix.length !== this._height ||
405
+ !Array.isArray(this._matrix[0]) ||
406
+ this._matrix[0].length !== this._width
407
+ ) {
408
+ this._matrix = this._createEmptyMatrix(this._width, this._height);
409
+ }
410
+
411
+ // detect transparency (fast bailout)
412
+ let hasTransparent = false;
413
+ for (let y = 0; y < this._height && !hasTransparent; y++) {
414
+ for (let x = 0; x < this._width; x++) {
415
+ const a = this._matrix[y] && this._matrix[y][x] ? this._matrix[y][x][3] : 0;
416
+ if (a !== 255) {
417
+ hasTransparent = true;
418
+ break;
419
+ }
420
+ }
421
+ }
422
+
423
+ // clear and optionally draw checkerboard (visual only)
424
+ this._pixelCtx.clearRect(0, 0, this._pixelCanvas.width, this._pixelCanvas.height);
425
+ if (hasTransparent) this._drawCheckerboard();
426
+
427
+ // write image data
428
+ const img = this._pixelCtx.createImageData(this._width, this._height);
429
+ const data = img.data;
430
+ let p = 0;
431
+ for (let y = 0; y < this._height; y++) {
432
+ for (let x = 0; x < this._width; x++) {
433
+ const cell = this._matrix[y] && this._matrix[y][x] ? this._matrix[y][x] : [0, 0, 0, 0];
434
+ data[p++] = this._clampInt(cell[0]);
435
+ data[p++] = this._clampInt(cell[1]);
436
+ data[p++] = this._clampInt(cell[2]);
437
+ data[p++] = this._clampInt(cell[3]);
438
+ }
439
+ }
440
+ this._pixelCtx.putImageData(img, 0, 0);
441
+
442
+ if (this._showGrid) this._renderGrid();
443
+ }
444
+
445
+ _drawCheckerboard() {
446
+ const ctx = this._pixelCtx;
447
+ const w = this._width;
448
+ const h = this._height;
449
+ const light = '#e9e9e9',
450
+ dark = '#cfcfcf';
451
+ // draw one logical pixel per matrix cell
452
+ for (let y = 0; y < h; y++) {
453
+ for (let x = 0; x < w; x++) {
454
+ ctx.fillStyle = ((x + y) & 1) === 0 ? light : dark;
455
+ ctx.fillRect(x, y, 1, 1);
456
+ }
457
+ }
458
+ }
459
+
460
+ _renderGrid() {
461
+ const ctx = this._gridCtx;
462
+ if (!ctx) return;
463
+ const w = this._gridCanvas.width;
464
+ const h = this._gridCanvas.height;
465
+ ctx.clearRect(0, 0, w, h);
466
+ if (!this._showGrid) return;
467
+ const ps = this._pixelSize;
468
+ ctx.save();
469
+ ctx.strokeStyle = darkTheme ? '#e1e1e1' : '#272727';
470
+ ctx.lineWidth = 2;
471
+ ctx.beginPath();
472
+ for (let x = 0; x <= this._width; x++) {
473
+ const xx = x * ps + 0.5;
474
+ ctx.moveTo(xx, 0);
475
+ ctx.lineTo(xx, h);
476
+ }
477
+ for (let y = 0; y <= this._height; y++) {
478
+ const yy = y * ps + 0.5;
479
+ ctx.moveTo(0, yy);
480
+ ctx.lineTo(w, yy);
481
+ }
482
+ ctx.stroke();
483
+ ctx.restore();
484
+ }
485
+
486
+ // ---------------- Tools & painting ----------------
487
+ setTool(name) {
488
+ this._tool = name;
489
+ if (this._toolSelect) this._toolSelect.value = name;
490
+ }
491
+
492
+ // set full RGBA brush color (alpha optional)
493
+ setBrushColor(rgba) {
494
+ if (!Array.isArray(rgba) || rgba.length < 3) return;
495
+ const r = this._clampInt(rgba[0]);
496
+ const g = this._clampInt(rgba[1]);
497
+ const b = this._clampInt(rgba[2]);
498
+ const a = typeof rgba[3] === 'number' ? this._clampInt(rgba[3]) : this._brushColor[3];
499
+ this._brushColor = [r, g, b, a];
500
+ if (this._colorInput) this._colorInput.value = this._rgbaToHex(this._brushColor);
501
+ if (this._opacityRange) this._opacityRange.value = String(this._brushColor[3]);
502
+ if (this._opacityNumber) this._opacityNumber.value = String(this._brushColor[3]);
503
+ }
504
+
505
+ // set brush alpha (0-255)
506
+ setBrushAlpha(a) {
507
+ const v = Math.max(0, Math.min(255, Math.floor(Number(a) || 0)));
508
+ this._brushColor[3] = v;
509
+ if (this._opacityRange) this._opacityRange.value = String(v);
510
+ if (this._opacityNumber) this._opacityNumber.value = String(v);
511
+ // keep color input (hex) representing rgb only
512
+ if (this._colorInput) this._colorInput.value = this._rgbaToHex(this._brushColor);
513
+ }
514
+ getBrushAlpha() {
515
+ return this._brushColor[3];
516
+ }
517
+
518
+ setBrushSize(n) {
519
+ this._brushSize = Math.max(1, Math.floor(n));
520
+ if (this._brushSizeInput) this._brushSizeInput.value = this._brushSize;
521
+ }
522
+
523
+ _applyBrush(x, y, color, renderAfter = false) {
524
+ const half = Math.floor(this._brushSize / 2);
525
+ for (let oy = -half; oy <= half; oy++)
526
+ for (let ox = -half; ox <= half; ox++) {
527
+ const tx = x + ox,
528
+ ty = y + oy;
529
+ if (this._inBounds(tx, ty)) this._matrix[ty][tx] = color.slice();
530
+ }
531
+ if (renderAfter) this.render();
532
+ }
533
+
534
+ fillBucket(x, y, targetColor = null) {
535
+ if (!this._inBounds(x, y)) return;
536
+ const src = this.getPixel(x, y);
537
+ const newColor = targetColor ? targetColor.slice() : this._brushColor.slice();
538
+ if (this._colorsEqual(src, newColor)) return;
539
+ const stack = [[x, y]];
540
+ while (stack.length) {
541
+ const [cx, cy] = stack.pop();
542
+ if (!this._inBounds(cx, cy)) continue;
543
+ const cur = this.getPixel(cx, cy);
544
+ if (!this._colorsEqual(cur, src)) continue;
545
+ this._matrix[cy][cx] = newColor.slice();
546
+ stack.push([cx + 1, cy], [cx - 1, cy], [cx, cy + 1], [cx, cy - 1]);
547
+ }
548
+ this.render();
549
+ this.dispatchEvent(new CustomEvent('fill', { detail: { x, y } }));
550
+ }
551
+
552
+ _colorsEqual(a, b) {
553
+ if (!a || !b) return false;
554
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
555
+ }
556
+
557
+ // ---------------- Pointer handling ----------------
558
+ _toGridCoords(evt) {
559
+ const rect = this._pixelCanvas.getBoundingClientRect();
560
+ const cssX = evt.clientX - rect.left;
561
+ const cssY = evt.clientY - rect.top;
562
+ const scaleX = this._pixelCanvas.width / rect.width;
563
+ const scaleY = this._pixelCanvas.height / rect.height;
564
+ const x = Math.floor(cssX * scaleX);
565
+ const y = Math.floor(cssY * scaleY);
566
+ return [x, y];
567
+ }
568
+
569
+ _onPointerDown(evt) {
570
+ evt.preventDefault();
571
+ this._isPointerDown = true;
572
+ try {
573
+ this._pixelCanvas.setPointerCapture(evt.pointerId);
574
+ } catch (e) {}
575
+ const [x, y] = this._toGridCoords(evt);
576
+ this._applyToolAt(x, y, evt);
577
+ }
578
+ _onPointerMove(evt) {
579
+ if (!this._isPointerDown) return;
580
+ const [x, y] = this._toGridCoords(evt);
581
+ this._applyToolAt(x, y, evt, true);
582
+ }
583
+ _onPointerUp(evt) {
584
+ this._isPointerDown = false;
585
+ try {
586
+ this._pixelCanvas.releasePointerCapture(evt.pointerId);
587
+ } catch (e) {}
588
+ }
589
+
590
+ _applyToolAt(x, y, evt, continuous = false) {
591
+ if (!this._inBounds(x, y)) return;
592
+ switch (this._tool) {
593
+ case 'pencil':
594
+ this._applyBrush(x, y, this._brushColor, true);
595
+ break;
596
+ case 'eraser':
597
+ this._applyBrush(x, y, [0, 0, 0, 0], true);
598
+ break;
599
+ case 'fill':
600
+ if (!continuous) this.fillBucket(x, y);
601
+ break;
602
+ case 'eyedropper':
603
+ const picked = this.getPixel(x, y);
604
+ if (picked) this.setBrushColor(picked);
605
+ break;
606
+ }
607
+ }
608
+
609
+ // ---------------- Import / Export ----------------
610
+ exportMatrixJSON() {
611
+ return JSON.stringify({ width: this._width, height: this._height, matrix: this._matrix });
612
+ }
613
+ importMatrixJSON(json) {
614
+ const data = typeof json === 'string' ? JSON.parse(json) : json;
615
+ if (!data || !Array.isArray(data.matrix)) throw new TypeError('Invalid matrix JSON');
616
+ this.loadMatrix(data.matrix);
617
+ }
618
+
619
+ async _pickFile() {
620
+ return new Promise((resolve) => {
621
+ const input = document.createElement('input');
622
+ input.type = 'file';
623
+ input.accept = 'application/json';
624
+ input.addEventListener('change', () => {
625
+ resolve(input.files && input.files[0] ? input.files[0] : null);
626
+ });
627
+ input.click();
628
+ });
629
+ }
630
+
631
+ // Create a PNG data URL at the requested scale (scale = number of CSS pixels per logical pixel)
632
+ toDataURL(scale = this._pixelSize) {
633
+ const w = this._width,
634
+ h = this._height;
635
+ const outW = Math.max(1, Math.floor(w * scale));
636
+ const outH = Math.max(1, Math.floor(h * scale));
637
+
638
+ // create logical image at native resolution
639
+ const src = document.createElement('canvas');
640
+ src.width = w;
641
+ src.height = h;
642
+ const sctx = src.getContext('2d');
643
+ const img = sctx.createImageData(w, h);
644
+ const data = img.data;
645
+ let p = 0;
646
+ for (let y = 0; y < h; y++)
647
+ for (let x = 0; x < w; x++) {
648
+ const c = this._matrix[y][x] || [0, 0, 0, 0];
649
+ data[p++] = this._clampInt(c[0]);
650
+ data[p++] = this._clampInt(c[1]);
651
+ data[p++] = this._clampInt(c[2]);
652
+ data[p++] = this._clampInt(c[3]);
653
+ }
654
+ sctx.putImageData(img, 0, 0);
655
+
656
+ // scale into output canvas (nearest-neighbor)
657
+ const out = document.createElement('canvas');
658
+ out.width = outW;
659
+ out.height = outH;
660
+ const octx = out.getContext('2d');
661
+ try {
662
+ octx.imageSmoothingEnabled = false;
663
+ } catch (e) {}
664
+ octx.drawImage(src, 0, 0, outW, outH);
665
+ return out.toDataURL('image/png');
666
+ }
667
+
668
+ // Async blob version (recommended for large images)
669
+ toBlob(scale = this._pixelSize) {
670
+ return new Promise((resolve) => {
671
+ const w = this._width,
672
+ h = this._height;
673
+ const outW = Math.max(1, Math.floor(w * scale));
674
+ const outH = Math.max(1, Math.floor(h * scale));
675
+ const src = document.createElement('canvas');
676
+ src.width = w;
677
+ src.height = h;
678
+ const sctx = src.getContext('2d');
679
+ const img = sctx.createImageData(w, h);
680
+ const data = img.data;
681
+ let p = 0;
682
+ for (let y = 0; y < h; y++)
683
+ for (let x = 0; x < w; x++) {
684
+ const c = this._matrix[y][x] || [0, 0, 0, 0];
685
+ data[p++] = this._clampInt(c[0]);
686
+ data[p++] = this._clampInt(c[1]);
687
+ data[p++] = this._clampInt(c[2]);
688
+ data[p++] = this._clampInt(c[3]);
689
+ }
690
+ sctx.putImageData(img, 0, 0);
691
+ const out = document.createElement('canvas');
692
+ out.width = outW;
693
+ out.height = outH;
694
+ const octx = out.getContext('2d');
695
+ try {
696
+ octx.imageSmoothingEnabled = false;
697
+ } catch (e) {}
698
+ octx.drawImage(src, 0, 0, outW, outH);
699
+ out.toBlob((b) => resolve(b), 'image/png');
700
+ });
701
+ }
702
+
703
+ // Trigger download of PNG (uses blob to avoid huge data URLs on big exports)
704
+ async exportPNG(filename = 'object-layer.png', scale = this._pixelSize) {
705
+ const blob = await this.toBlob(scale);
706
+ const url = URL.createObjectURL(blob);
707
+ const a = document.createElement('a');
708
+ a.href = url;
709
+ a.download = filename;
710
+ a.click();
711
+ // revoke after a tick to ensure download started
712
+ setTimeout(() => URL.revokeObjectURL(url), 5000);
713
+ }
714
+
715
+ // ---------------- Helpers ----------------
716
+ _hexToRgba(hex) {
717
+ const h = (hex || '').replace('#', '');
718
+ if (h.length === 3) {
719
+ return [parseInt(h[0] + h[0], 16), parseInt(h[1] + h[1], 16), parseInt(h[2] + h[2], 16), 255];
720
+ }
721
+ if (h.length === 6) {
722
+ return [parseInt(h.substring(0, 2), 16), parseInt(h.substring(2, 4), 16), parseInt(h.substring(4, 6), 16), 255];
723
+ }
724
+ return [0, 0, 0, 255];
725
+ }
726
+ _rgbaToHex(rgba) {
727
+ const [r, g, b] = rgba;
728
+ return `#${((1 << 24) + (this._clampInt(r) << 16) + (this._clampInt(g) << 8) + this._clampInt(b))
729
+ .toString(16)
730
+ .slice(1)}`;
731
+ }
732
+
733
+ // ---------------- Transform helpers (flip/rotate) ----------------
734
+ flipHorizontal() {
735
+ // reverse each row (mirror horizontally)
736
+ for (let y = 0; y < this._height; y++) {
737
+ this._matrix[y].reverse();
738
+ }
739
+ this.render();
740
+ this.dispatchEvent(new CustomEvent('transform', { detail: { type: 'flip-horizontal' } }));
741
+ }
742
+
743
+ flipVertical() {
744
+ // reverse the order of rows (mirror vertically)
745
+ this._matrix.reverse();
746
+ this.render();
747
+ this.dispatchEvent(new CustomEvent('transform', { detail: { type: 'flip-vertical' } }));
748
+ }
749
+
750
+ rotateCW() {
751
+ // rotate +90 degrees (clockwise)
752
+ const oldH = this._height;
753
+ const oldW = this._width;
754
+ const newW = oldH;
755
+ const newH = oldW;
756
+ const newMat = this._createEmptyMatrix(newW, newH);
757
+ for (let y = 0; y < oldH; y++) {
758
+ for (let x = 0; x < oldW; x++) {
759
+ const px = this._matrix[y][x] ? this._matrix[y][x].slice() : [0, 0, 0, 0];
760
+ const newX = oldH - 1 - y; // column in new matrix
761
+ const newY = x; // row in new matrix
762
+ newMat[newY][newX] = px;
763
+ }
764
+ }
765
+ this._width = newW;
766
+ this._height = newH;
767
+ this._matrix = newMat;
768
+ // keep inputs/attributes in sync
769
+ if (this._widthInput) this._widthInput.value = String(this._width);
770
+ if (this._heightInput) this._heightInput.value = String(this._height);
771
+ this.setAttribute('width', String(this._width));
772
+ this.setAttribute('height', String(this._height));
773
+
774
+ this._setupContextsAndSize();
775
+ this.render();
776
+ this.dispatchEvent(
777
+ new CustomEvent('transform', { detail: { type: 'rotate-cw', width: this._width, height: this._height } }),
778
+ );
779
+ }
780
+
781
+ rotateCCW() {
782
+ // rotate -90 degrees (counter-clockwise)
783
+ const oldH = this._height;
784
+ const oldW = this._width;
785
+ const newW = oldH;
786
+ const newH = oldW;
787
+ const newMat = this._createEmptyMatrix(newW, newH);
788
+ for (let y = 0; y < oldH; y++) {
789
+ for (let x = 0; x < oldW; x++) {
790
+ const px = this._matrix[y][x] ? this._matrix[y][x].slice() : [0, 0, 0, 0];
791
+ const newX = y; // column in new matrix
792
+ const newY = oldW - 1 - x; // row in new matrix
793
+ newMat[newY][newX] = px;
794
+ }
795
+ }
796
+ this._width = newW;
797
+ this._height = newH;
798
+ this._matrix = newMat;
799
+ if (this._widthInput) this._widthInput.value = String(this._width);
800
+ if (this._heightInput) this._heightInput.value = String(this._height);
801
+ this.setAttribute('width', String(this._width));
802
+ this.setAttribute('height', String(this._height));
803
+
804
+ this._setupContextsAndSize();
805
+ this.render();
806
+ this.dispatchEvent(
807
+ new CustomEvent('transform', { detail: { type: 'rotate-ccw', width: this._width, height: this._height } }),
808
+ );
809
+ }
810
+
811
+ // ---------------- Properties ----------------
812
+ get width() {
813
+ return this._width;
814
+ }
815
+ set width(v) {
816
+ this._width = Math.max(1, Math.floor(v));
817
+ this.setAttribute('width', String(this._width));
818
+ this._setupContextsAndSize();
819
+ this.render();
820
+ }
821
+ get height() {
822
+ return this._height;
823
+ }
824
+ set height(v) {
825
+ this._height = Math.max(1, Math.floor(v));
826
+ this.setAttribute('height', String(this._height));
827
+ this._setupContextsAndSize();
828
+ this.render();
829
+ }
830
+ get pixelSize() {
831
+ return this._pixelSize;
832
+ }
833
+ set pixelSize(v) {
834
+ this._pixelSize = Math.max(1, Math.floor(v));
835
+ this.setAttribute('pixel-size', String(this._pixelSize));
836
+ this._setupContextsAndSize();
837
+ this.render();
838
+ }
839
+ get brushSize() {
840
+ return this._brushSize;
841
+ }
842
+ set brushSize(v) {
843
+ this.setBrushSize(v);
844
+ }
845
+ get matrix() {
846
+ return this._matrix.map((row) => row.map((c) => c.slice()));
847
+ }
848
+
849
+ exportJSON() {
850
+ return this.exportMatrixJSON();
851
+ }
852
+ importJSON(json) {
853
+ return this.importMatrixJSON(json);
854
+ }
855
+ }
856
+
857
+ customElements.define('object-layer-engine', ObjectLayerEngineElement);
858
+
859
+ /*
860
+ Example usage:
861
+ <object-layer-engine id="ole" width="20" height="12" pixel-size="20"></object-layer-engine>
862
+ */
863
+
864
+ class ObjectLayerPngLoader extends HTMLElement {
865
+ constructor() {
866
+ super();
867
+ this.attachShadow({ mode: 'open' });
868
+ this.shadowRoot.innerHTML = html`
869
+ <style>
870
+ :host {
871
+ display: block;
872
+ font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Arial;
873
+ }
874
+ .wrap {
875
+ display: flex;
876
+ flex-direction: column;
877
+ gap: 8px;
878
+ }
879
+ .controls {
880
+ display: flex;
881
+ gap: 8px;
882
+ align-items: center;
883
+ flex-wrap: wrap;
884
+ }
885
+ .drop-area {
886
+ border: 2px dashed #999;
887
+ padding: 12px;
888
+ border-radius: 8px;
889
+ text-align: center;
890
+ color: #555;
891
+ user-select: none;
892
+ }
893
+ .drop-area.dragover {
894
+ border-color: #4a90e2;
895
+ color: #1a73e8;
896
+ background: rgba(74, 144, 226, 0.04);
897
+ }
898
+ input[type='file'] {
899
+ display: inline-block;
900
+ }
901
+ .hint {
902
+ font-size: 0.9rem;
903
+ color: #666;
904
+ }
905
+ </style>
906
+
907
+ <div class="wrap">
908
+ <div class="controls">
909
+ <label title="Load PNG file">
910
+ <input type="file" accept="image/png" part="file-input" />
911
+ <span class="btn">Choose PNG</span>
912
+ </label>
913
+ <div class="hint">Only PNG images accepted. Drop PNG onto the box below.</div>
914
+ </div>
915
+
916
+ <div class="drop-area" part="drop-area">Drop PNG here or click "Choose PNG"</div>
917
+ </div>
918
+ `;
919
+
920
+ this._fileInput = this.shadowRoot.querySelector('input[type="file"]');
921
+ this._dropArea = this.shadowRoot.querySelector('.drop-area');
922
+
923
+ this._editor = null; // will hold external editor instance
924
+ this._options = { fitMode: 'contain' };
925
+
926
+ // Bind handlers
927
+ this._onFileChange = this._onFileChange.bind(this);
928
+ this._onDrop = this._onDrop.bind(this);
929
+ this._onDragOver = this._onDragOver.bind(this);
930
+ this._onDragLeave = this._onDragLeave.bind(this);
931
+ }
932
+
933
+ static get observedAttributes() {
934
+ return ['editor-selector', 'fit-mode', 'target-cells-x', 'target-cells-y'];
935
+ }
936
+
937
+ attributeChangedCallback(name, oldVal, newVal) {
938
+ if (name === 'editor-selector' && newVal) {
939
+ const el = document.querySelector(newVal);
940
+ if (el) this.setEditor(el);
941
+ }
942
+ if (name === 'fit-mode') {
943
+ this._options.fitMode = newVal || 'contain';
944
+ }
945
+ }
946
+
947
+ connectedCallback() {
948
+ this._fileInput.addEventListener('change', this._onFileChange);
949
+ this._dropArea.addEventListener('dragover', this._onDragOver);
950
+ this._dropArea.addEventListener('dragleave', this._onDragLeave);
951
+ this._dropArea.addEventListener('drop', this._onDrop);
952
+ this.addEventListener('dragover', this._onDragOver);
953
+ this.addEventListener('dragleave', this._onDragLeave);
954
+ this.addEventListener('drop', this._onDrop);
955
+
956
+ // If editor-selector attribute was present at creation, try to resolve
957
+ const sel = this.getAttribute('editor-selector');
958
+ if (sel) {
959
+ const target = document.querySelector(sel);
960
+ if (target) this.setEditor(target);
961
+ }
962
+
963
+ // read fit-mode
964
+ const fit = this.getAttribute('fit-mode');
965
+ if (fit) this._options.fitMode = fit;
966
+ }
967
+
968
+ disconnectedCallback() {
969
+ this._fileInput.removeEventListener('change', this._onFileChange);
970
+ this._dropArea.removeEventListener('dragover', this._onDragOver);
971
+ this._dropArea.removeEventListener('dragleave', this._onDragLeave);
972
+ this._dropArea.removeEventListener('drop', this._onDrop);
973
+ this.removeEventListener('dragover', this._onDragOver);
974
+ this.removeEventListener('dragleave', this._onDragLeave);
975
+ this.removeEventListener('drop', this._onDrop);
976
+ }
977
+
978
+ // ----------------- Public API -----------------
979
+ setEditor(editor) {
980
+ if (!editor) throw new Error('Editor cannot be null/undefined');
981
+ if (typeof editor.loadMatrix !== 'function') {
982
+ throw new Error('Provided editor does not expose loadMatrix(matrix)');
983
+ }
984
+ this._editor = editor;
985
+ this.dispatchEvent(new CustomEvent('editorconnected', { detail: { editor } }));
986
+ }
987
+
988
+ setOptions(options = {}) {
989
+ if (options.fitMode) this._options.fitMode = options.fitMode;
990
+ if (options.targetCellsX) this.setAttribute('target-cells-x', String(options.targetCellsX));
991
+ if (options.targetCellsY) this.setAttribute('target-cells-y', String(options.targetCellsY));
992
+ }
993
+
994
+ get editor() {
995
+ return this._editor;
996
+ }
997
+
998
+ // ----------------- Events -----------------
999
+ _onFileChange(e) {
1000
+ const file = e.target.files && e.target.files[0] ? e.target.files[0] : null;
1001
+ if (!file) return;
1002
+ this._handleFile(file);
1003
+ this._fileInput.value = '';
1004
+ }
1005
+
1006
+ _onDragOver(e) {
1007
+ e.preventDefault();
1008
+ e.dataTransfer.dropEffect = 'copy';
1009
+ this._dropArea.classList.add('dragover');
1010
+ }
1011
+ _onDragLeave(e) {
1012
+ e.preventDefault();
1013
+ this._dropArea.classList.remove('dragover');
1014
+ }
1015
+
1016
+ _onDrop(e) {
1017
+ e.preventDefault();
1018
+ this._dropArea.classList.remove('dragover');
1019
+ const file = e.dataTransfer.files && e.dataTransfer.files[0] ? e.dataTransfer.files[0] : null;
1020
+ if (!file) return;
1021
+ this._handleFile(file);
1022
+ }
1023
+
1024
+ // ----------------- File handling -----------------
1025
+ async _handleFile(file) {
1026
+ const isPngByType = file.type === 'image/png';
1027
+ const isPngByName = file.name && file.name.toLowerCase().endsWith('.png');
1028
+ if (!isPngByType && !isPngByName) {
1029
+ this._showError('Only PNG files are supported.');
1030
+ return;
1031
+ }
1032
+
1033
+ if (!this._editor) {
1034
+ this._showError('No editor connected. Use setEditor(editor) or provide editor-selector attribute.');
1035
+ return;
1036
+ }
1037
+
1038
+ try {
1039
+ await this._loadPngToEditorAdaptive(file);
1040
+ this._dispatchLoadedEvent(file.name);
1041
+ } catch (err) {
1042
+ console.error('Failed to load PNG', err);
1043
+ this._showError('Failed to load PNG (see console).');
1044
+ }
1045
+ }
1046
+
1047
+ _showError(msg) {
1048
+ alert(msg);
1049
+ }
1050
+ _dispatchLoadedEvent(filename) {
1051
+ this.dispatchEvent(new CustomEvent('pngloaded', { detail: { filename } }));
1052
+ }
1053
+
1054
+ // ----------------- Adaptive load -----------------
1055
+ _readEditorConfig() {
1056
+ const ed = this._editor;
1057
+ const cfg = { pixelSize: null, cellsX: null, cellsY: null };
1058
+
1059
+ if (!ed) return cfg;
1060
+
1061
+ // pixel size detection (try multiple forms)
1062
+ cfg.pixelSize = ed.pixelSize || ed.pixel_size || null;
1063
+ if (!cfg.pixelSize) {
1064
+ const attr = ed.getAttribute && (ed.getAttribute('pixel-size') || ed.getAttribute('pixelSize'));
1065
+ if (attr) cfg.pixelSize = parseInt(attr, 10);
1066
+ }
1067
+ if (typeof cfg.pixelSize === 'string') cfg.pixelSize = parseInt(cfg.pixelSize, 10);
1068
+
1069
+ // cells detection (common attribute names: width/height on engine represent cells)
1070
+ const widthAttr = ed.getAttribute && ed.getAttribute('width');
1071
+ const heightAttr = ed.getAttribute && ed.getAttribute('height');
1072
+ if (widthAttr && heightAttr) {
1073
+ cfg.cellsX = parseInt(widthAttr, 10);
1074
+ cfg.cellsY = parseInt(heightAttr, 10);
1075
+ }
1076
+
1077
+ // alternative property names
1078
+ cfg.cellsX = cfg.cellsX || ed.cellsX || ed.cellCountX || (ed.cells && ed.cells.x) || null;
1079
+ cfg.cellsY = cfg.cellsY || ed.cellsY || ed.cellCountY || (ed.cells && ed.cells.y) || null;
1080
+
1081
+ // if editor exposes getCells() prefer that
1082
+ try {
1083
+ if ((!cfg.cellsX || !cfg.cellsY) && typeof ed.getCells === 'function') {
1084
+ const c = ed.getCells();
1085
+ if (c && c.x && c.y) {
1086
+ cfg.cellsX = cfg.cellsX || c.x;
1087
+ cfg.cellsY = cfg.cellsY || c.y;
1088
+ }
1089
+ }
1090
+ } catch (e) {
1091
+ /* ignore */
1092
+ }
1093
+
1094
+ return cfg;
1095
+ }
1096
+
1097
+ // core adaptive loader: scales image to editor cells (or computes fallback)
1098
+ async _loadPngToEditorAdaptive(blobOrFile) {
1099
+ const imgBitmap = await createImageBitmap(blobOrFile);
1100
+ const srcW = imgBitmap.width;
1101
+ const srcH = imgBitmap.height;
1102
+
1103
+ // read editor config and loader explicit overrides
1104
+ const editorCfg = this._readEditorConfig();
1105
+ const overrideX = this.getAttribute('target-cells-x');
1106
+ const overrideY = this.getAttribute('target-cells-y');
1107
+
1108
+ let targetCellsX = overrideX ? parseInt(overrideX, 10) : editorCfg.cellsX || null;
1109
+ let targetCellsY = overrideY ? parseInt(overrideY, 10) : editorCfg.cellsY || null;
1110
+
1111
+ // if cells unknown but pixelSize known, compute approximate cells from image dimensions
1112
+ if ((!targetCellsX || !targetCellsY) && editorCfg.pixelSize) {
1113
+ const px = parseInt(editorCfg.pixelSize, 10);
1114
+ if (px > 0) {
1115
+ if (!targetCellsX) targetCellsX = Math.max(1, Math.round(srcW / px));
1116
+ if (!targetCellsY) targetCellsY = Math.max(1, Math.round(srcH / px));
1117
+ }
1118
+ }
1119
+
1120
+ // if still missing, fallback to native image pixels
1121
+ if (!targetCellsX) targetCellsX = srcW;
1122
+ if (!targetCellsY) targetCellsY = srcH;
1123
+
1124
+ // Decide fit mode
1125
+ const fitMode = this._options.fitMode || this.getAttribute('fit-mode') || 'contain';
1126
+
1127
+ // Create a small canvas sized to the target cells (we will render the image into this canvas
1128
+ // with smoothing disabled to preserve blocky/pixel look). Then read each pixel as a cell.
1129
+ const small = document.createElement('canvas');
1130
+ small.width = targetCellsX;
1131
+ small.height = targetCellsY;
1132
+ const sctx = small.getContext('2d');
1133
+
1134
+ // nearest-neighbour / crisp scaling
1135
+ sctx.imageSmoothingEnabled = false;
1136
+ sctx.clearRect(0, 0, small.width, small.height);
1137
+
1138
+ if (fitMode === 'stretch') {
1139
+ // non-uniform scale to fill exactly
1140
+ sctx.drawImage(imgBitmap, 0, 0, srcW, srcH, 0, 0, small.width, small.height);
1141
+ } else {
1142
+ // compute uniform scale to contain or cover
1143
+ let scaleX = small.width / srcW;
1144
+ let scaleY = small.height / srcH;
1145
+ let scale = fitMode === 'cover' ? Math.max(scaleX, scaleY) : Math.min(scaleX, scaleY);
1146
+ // compute destination size in small-canvas pixels
1147
+ const destW = Math.max(1, Math.round(srcW * scale));
1148
+ const destH = Math.max(1, Math.round(srcH * scale));
1149
+ const dx = Math.floor((small.width - destW) / 2);
1150
+ const dy = Math.floor((small.height - destH) / 2);
1151
+ sctx.drawImage(imgBitmap, 0, 0, srcW, srcH, dx, dy, destW, destH);
1152
+ }
1153
+
1154
+ // read pixel data from the small canvas
1155
+ const imageData = sctx.getImageData(0, 0, small.width, small.height).data;
1156
+
1157
+ // build matrix[y][x] = [r,g,b,a]
1158
+ const matrix = new Array(small.height);
1159
+ let p = 0;
1160
+ for (let y = 0; y < small.height; y++) {
1161
+ const row = new Array(small.width);
1162
+ for (let x = 0; x < small.width; x++) {
1163
+ const r = imageData[p++];
1164
+ const g = imageData[p++];
1165
+ const b = imageData[p++];
1166
+ const a = imageData[p++];
1167
+ row[x] = [r, g, b, a];
1168
+ }
1169
+ matrix[y] = row;
1170
+ }
1171
+
1172
+ // attempt to optionally align editor settings (best-effort)
1173
+ try {
1174
+ // if editor has setCells(x,y) or setCellCount, call it
1175
+ if (typeof this._editor.setCells === 'function') {
1176
+ this._editor.setCells(small.width, small.height);
1177
+ } else if (typeof this._editor.setCellCount === 'function') {
1178
+ this._editor.setCellCount(small.width, small.height);
1179
+ } else {
1180
+ // try common attribute setter
1181
+ if (this._editor.setAttribute) {
1182
+ this._editor.setAttribute('width', String(small.width));
1183
+ this._editor.setAttribute('height', String(small.height));
1184
+ }
1185
+ }
1186
+
1187
+ // if editor has setPixelSize and editorCfg.pixelSize exists, keep it
1188
+ if (editorCfg.pixelSize && typeof this._editor.setPixelSize === 'function') {
1189
+ this._editor.setPixelSize(parseInt(editorCfg.pixelSize, 10));
1190
+ }
1191
+ } catch (e) {
1192
+ // non-critical; continue
1193
+ console.warn('Failed to align editor config:', e);
1194
+ }
1195
+
1196
+ // finally, hand matrix to editor
1197
+ if (!this._editor || typeof this._editor.loadMatrix !== 'function') {
1198
+ throw new Error('Editor disconnected or does not expose loadMatrix');
1199
+ }
1200
+
1201
+ this._editor.loadMatrix(matrix);
1202
+ }
1203
+
1204
+ // Public helpers
1205
+ async loadPngBlob(blob) {
1206
+ return this._handleFile(blob);
1207
+ }
1208
+ async loadPngUrl(url) {
1209
+ const resp = await fetch(url);
1210
+ const blob = await resp.blob();
1211
+ return this._handleFile(blob);
1212
+ }
1213
+ }
1214
+
1215
+ customElements.define('object-layer-png-loader', ObjectLayerPngLoader);
1216
+
1217
+ /* Example wiring (NOT code repeated in canvas):
1218
+
1219
+ // HTML
1220
+ <object-layer-engine id="editor"></object-layer-engine>
1221
+ <object-layer-png-loader id="loader" editor-selector="#editor"></object-layer-png-loader>
1222
+
1223
+ // JS (programmatic)
1224
+ const editor = document.getElementById('editor');
1225
+ const loader = document.getElementById('loader');
1226
+ // Alternatively: loader.setEditor(editor);
1227
+ loader.addEventListener('pngloaded', (e) => console.log('Loaded', e.detail.filename));
1228
+
1229
+ */