acinguiux-preact-components 0.0.1

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 (313) hide show
  1. package/package.json +56 -0
  2. package/src/content/themes/theme-acinguiux-amg/theme-acinguiux-amg.css +23 -0
  3. package/src/content/themes/theme-acinguiux-cafe/theme-acinguiux-cafe.css +47 -0
  4. package/src/content/themes/theme-acinguiux-energy/theme-acinguiux-energy.css +45 -0
  5. package/src/content/themes/theme-acinguiux-livewire/theme-acinguiux-livewire.css +22 -0
  6. package/src/content/themes/theme-acinguiux-livewire-italy/theme-acinguiux-livewire-italy.css +22 -0
  7. package/src/content/themes/theme-acinguiux-recharge/theme-acinguiux-recharge.css +49 -0
  8. package/src/content/themes/theme-allon/theme-allon.css +25 -0
  9. package/src/content/themes/theme-atlas/theme-atlas.css +31 -0
  10. package/src/content/themes/theme-aurvana/resources/favicon/apple-touch-icon.png +0 -0
  11. package/src/content/themes/theme-aurvana/resources/favicon/favico.ico +0 -0
  12. package/src/content/themes/theme-aurvana/resources/favicon/favicon-96x96.png +0 -0
  13. package/src/content/themes/theme-aurvana/resources/favicon/favicon.ico +0 -0
  14. package/src/content/themes/theme-aurvana/resources/favicon/favicon.png +0 -0
  15. package/src/content/themes/theme-aurvana/resources/favicon/favicon.svg +13 -0
  16. package/src/content/themes/theme-aurvana/resources/favicon/google-touch-icon.png +0 -0
  17. package/src/content/themes/theme-aurvana/resources/favicon/manifest.json +14 -0
  18. package/src/content/themes/theme-aurvana/resources/favicon/site.webmanifest +21 -0
  19. package/src/content/themes/theme-aurvana/resources/favicon/web-app-manifest-192x192.png +0 -0
  20. package/src/content/themes/theme-aurvana/resources/favicon/web-app-manifest-512x512.png +0 -0
  21. package/src/content/themes/theme-aurvana/theme-aurvana.css +49 -0
  22. package/src/content/themes/theme-base/theme-base.css +49 -0
  23. package/src/content/themes/theme-base2/resources/favicon/android-chrome-192x192.png +0 -0
  24. package/src/content/themes/theme-base2/resources/favicon/android-chrome-512x512.png +0 -0
  25. package/src/content/themes/theme-base2/resources/favicon/apple-touch-icon.png +0 -0
  26. package/src/content/themes/theme-base2/resources/favicon/favico.ico +0 -0
  27. package/src/content/themes/theme-base2/resources/favicon/favicon-16x16.png +0 -0
  28. package/src/content/themes/theme-base2/resources/favicon/favicon-32x32.png +0 -0
  29. package/src/content/themes/theme-base2/resources/favicon/favicon-96x96.png +0 -0
  30. package/src/content/themes/theme-base2/resources/favicon/favicon.ico +0 -0
  31. package/src/content/themes/theme-base2/resources/favicon/favicon.png +0 -0
  32. package/src/content/themes/theme-base2/resources/favicon/favicon.svg +9 -0
  33. package/src/content/themes/theme-base2/resources/favicon/google-touch-icon.png +0 -0
  34. package/src/content/themes/theme-base2/resources/favicon/manifest.json +14 -0
  35. package/src/content/themes/theme-base2/resources/favicon/site.webmanifest +1 -0
  36. package/src/content/themes/theme-base2/resources/favicon/web-app-manifest-192x192.png +0 -0
  37. package/src/content/themes/theme-base2/resources/favicon/web-app-manifest-512x512.png +0 -0
  38. package/src/content/themes/theme-base2/resources/fonts/acinguiux-typeface-la-heavy-221208.woff2 +0 -0
  39. package/src/content/themes/theme-base2/theme-base2.css +47 -0
  40. package/src/content/themes/theme-eco-marathon/theme-eco-marathon.css +22 -0
  41. package/src/content/themes/theme-energy-transition-campus-amsterdam/theme-energy-transition-campus-amsterdam.css +26 -0
  42. package/src/content/themes/theme-evpass/theme-evpass.css +46 -0
  43. package/src/content/themes/theme-nam-2025/resources/favicon/apple-touch-icon.png +0 -0
  44. package/src/content/themes/theme-nam-2025/resources/favicon/favico.ico +0 -0
  45. package/src/content/themes/theme-nam-2025/resources/favicon/favicon-96x96.png +0 -0
  46. package/src/content/themes/theme-nam-2025/resources/favicon/favicon.ico +0 -0
  47. package/src/content/themes/theme-nam-2025/resources/favicon/favicon.png +0 -0
  48. package/src/content/themes/theme-nam-2025/resources/favicon/favicon.svg +9 -0
  49. package/src/content/themes/theme-nam-2025/resources/favicon/google-touch-icon.png +0 -0
  50. package/src/content/themes/theme-nam-2025/resources/favicon/manifest.json +14 -0
  51. package/src/content/themes/theme-nam-2025/resources/favicon/site.webmanifest +21 -0
  52. package/src/content/themes/theme-nam-2025/resources/favicon/web-app-manifest-192x192.png +0 -0
  53. package/src/content/themes/theme-nam-2025/resources/favicon/web-app-manifest-512x512.png +0 -0
  54. package/src/content/themes/theme-nam-2025/theme-nam-2025.css +47 -0
  55. package/src/content/themes/theme-pennzoil/theme-pennzoil.css +36 -0
  56. package/src/content/themes/theme-quaker-state/theme-quaker-state.css +63 -0
  57. package/src/content/themes/theme-tafawoq/theme-tafawoq.css +26 -0
  58. package/src/content/themes/theme-vegetable/resources/favicon/apple-touch-icon.png +0 -0
  59. package/src/content/themes/theme-vegetable/resources/favicon/favico.ico +0 -0
  60. package/src/content/themes/theme-vegetable/resources/favicon/favicon-96x96.png +0 -0
  61. package/src/content/themes/theme-vegetable/resources/favicon/favicon.ico +0 -0
  62. package/src/content/themes/theme-vegetable/resources/favicon/favicon.png +0 -0
  63. package/src/content/themes/theme-vegetable/resources/favicon/favicon.svg +13 -0
  64. package/src/content/themes/theme-vegetable/resources/favicon/google-touch-icon.png +0 -0
  65. package/src/content/themes/theme-vegetable/resources/favicon/manifest.json +14 -0
  66. package/src/content/themes/theme-vegetable/resources/favicon/site.webmanifest +21 -0
  67. package/src/content/themes/theme-vegetable/resources/favicon/web-app-manifest-192x192.png +0 -0
  68. package/src/content/themes/theme-vegetable/resources/favicon/web-app-manifest-512x512.png +0 -0
  69. package/src/content/themes/theme-vegetable/theme-vegetable.css +49 -0
  70. package/src/content/themes/theme-zeolyst/resources/fonts/type-ar-medium.woff2 +0 -0
  71. package/src/content/themes/theme-zeolyst/theme-zeolyst.css +29 -0
  72. package/src/main/atoms/audio.js +16 -0
  73. package/src/main/atoms/box.js +5 -0
  74. package/src/main/atoms/button.js +40 -0
  75. package/src/main/atoms/card.js +22 -0
  76. package/src/main/atoms/form.js +30 -0
  77. package/src/main/atoms/heading.js +17 -0
  78. package/src/main/atoms/icon.js +24 -0
  79. package/src/main/atoms/img.js +131 -0
  80. package/src/main/atoms/input.js +55 -0
  81. package/src/main/atoms/link-text.js +21 -0
  82. package/src/main/atoms/link.js +60 -0
  83. package/src/main/atoms/list.js +12 -0
  84. package/src/main/atoms/logo.js +9 -0
  85. package/src/main/atoms/menu.js +10 -0
  86. package/src/main/atoms/message.js +5 -0
  87. package/src/main/atoms/nav-link.js +49 -0
  88. package/src/main/atoms/popup.js +47 -0
  89. package/src/main/atoms/rich-text.js +128 -0
  90. package/src/main/atoms/scroller.js +224 -0
  91. package/src/main/atoms/svg.js +65 -0
  92. package/src/main/atoms/table.js +32 -0
  93. package/src/main/atoms/textarea.js +10 -0
  94. package/src/main/atoms/time.js +12 -0
  95. package/src/main/atoms/video.js +100 -0
  96. package/src/main/export-main.js +12 -0
  97. package/src/main/export-matter.js +86 -0
  98. package/src/main/export-preact-hooks.js +1 -0
  99. package/src/main/export-preact.js +1 -0
  100. package/src/main/index.js +13 -0
  101. package/src/main/molecules/asset.js +23 -0
  102. package/src/main/molecules/glossary.js +44 -0
  103. package/src/main/molecules/links.js +23 -0
  104. package/src/main/molecules/promo-text.js +27 -0
  105. package/src/main/molecules/tags.js +15 -0
  106. package/src/main/molecules/tree.js +51 -0
  107. package/src/main/organisms/accordion-item.js +106 -0
  108. package/src/main/organisms/author.js +29 -0
  109. package/src/main/organisms/breadcrumb.js +69 -0
  110. package/src/main/organisms/call-to-action.js +24 -0
  111. package/src/main/organisms/carousel.js +178 -0
  112. package/src/main/organisms/cart-item.js +156 -0
  113. package/src/main/organisms/cart.js +162 -0
  114. package/src/main/organisms/contact-form.js +141 -0
  115. package/src/main/organisms/container/ab-test.js +47 -0
  116. package/src/main/organisms/container/default.js +6 -0
  117. package/src/main/organisms/container/filtered-section.js +293 -0
  118. package/src/main/organisms/container/footer.js +12 -0
  119. package/src/main/organisms/container/grid.js +44 -0
  120. package/src/main/organisms/container/header.js +13 -0
  121. package/src/main/organisms/container/list.js +7 -0
  122. package/src/main/organisms/container/main.js +6 -0
  123. package/src/main/organisms/container/raw.js +7 -0
  124. package/src/main/organisms/container/section.js +28 -0
  125. package/src/main/organisms/container.js +29 -0
  126. package/src/main/organisms/content-owner.js +15 -0
  127. package/src/main/organisms/date-entry.js +56 -0
  128. package/src/main/organisms/external-search.js +73 -0
  129. package/src/main/organisms/filtered-item.js +163 -0
  130. package/src/main/organisms/footer-item.js +17 -0
  131. package/src/main/organisms/image-gallery.js +164 -0
  132. package/src/main/organisms/last-modified.js +20 -0
  133. package/src/main/organisms/legal-footer.js +16 -0
  134. package/src/main/organisms/list-item.js +48 -0
  135. package/src/main/organisms/metadata.js +11 -0
  136. package/src/main/organisms/navigation.js +232 -0
  137. package/src/main/organisms/notification.js +87 -0
  138. package/src/main/organisms/order-tracker.js +203 -0
  139. package/src/main/organisms/page-header-banner.js +26 -0
  140. package/src/main/organisms/page-header.js +33 -0
  141. package/src/main/organisms/page-tags.js +14 -0
  142. package/src/main/organisms/page.js +260 -0
  143. package/src/main/organisms/press-release.js +24 -0
  144. package/src/main/organisms/product-admin.js +204 -0
  145. package/src/main/organisms/promo-banner.js +28 -0
  146. package/src/main/organisms/promo-bottom.js +23 -0
  147. package/src/main/organisms/promo-button.js +8 -0
  148. package/src/main/organisms/promo-card-cover.js +35 -0
  149. package/src/main/organisms/promo-card.js +33 -0
  150. package/src/main/organisms/promo-full.js +20 -0
  151. package/src/main/organisms/promo-image.js +22 -0
  152. package/src/main/organisms/promo-lure.js +22 -0
  153. package/src/main/organisms/promo-product-card.js +187 -0
  154. package/src/main/organisms/promo-product-full.js +293 -0
  155. package/src/main/organisms/promo-simple.js +23 -0
  156. package/src/main/organisms/quote.js +21 -0
  157. package/src/main/organisms/search-form.js +42 -0
  158. package/src/main/organisms/search-nav.js +66 -0
  159. package/src/main/organisms/search-result.js +53 -0
  160. package/src/main/organisms/slider.js +26 -0
  161. package/src/main/organisms/standalone-asset.js +22 -0
  162. package/src/main/organisms/tabs.js +277 -0
  163. package/src/main/organisms/topbar.js +83 -0
  164. package/src/main/organisms/web-component.js +53 -0
  165. package/src/main/routing/annotation.js +9 -0
  166. package/src/main/routing/component.js +138 -0
  167. package/src/main/routing/empty.js +5 -0
  168. package/src/main/routing/error-handler.js +64 -0
  169. package/src/main/routing/placeholder-image.svg +5 -0
  170. package/src/main/routing/router.js +219 -0
  171. package/src/main/shared/analytics.js +677 -0
  172. package/src/main/shared/bubble-event.js +11 -0
  173. package/src/main/shared/custom-element.js +21 -0
  174. package/src/main/shared/deep-selector.js +28 -0
  175. package/src/main/shared/disable-transparency.js +10 -0
  176. package/src/main/shared/format-time.js +8 -0
  177. package/src/main/shared/get-id.js +5 -0
  178. package/src/main/shared/get-meta.js +3 -0
  179. package/src/main/shared/get-size-class.js +3 -0
  180. package/src/main/shared/get-size.js +11 -0
  181. package/src/main/shared/h.js +88 -0
  182. package/src/main/shared/hash-jump.js +33 -0
  183. package/src/main/shared/icons/arrow-back.svg +1 -0
  184. package/src/main/shared/icons/arrow-down.svg +1 -0
  185. package/src/main/shared/icons/arrow-next.svg +1 -0
  186. package/src/main/shared/icons/arrow-tail-right.svg +1 -0
  187. package/src/main/shared/icons/arrow-tail-up.svg +1 -0
  188. package/src/main/shared/icons/arrow-up.svg +1 -0
  189. package/src/main/shared/icons/asset-download.svg +1 -0
  190. package/src/main/shared/icons/logo.svg +5 -0
  191. package/src/main/shared/icons/low-carbon-placeholder.svg +9 -0
  192. package/src/main/shared/icons/media-pause.svg +1 -0
  193. package/src/main/shared/icons/media-play.svg +1 -0
  194. package/src/main/shared/icons/navigation-burger.svg +1 -0
  195. package/src/main/shared/icons/navigation-close.svg +1 -0
  196. package/src/main/shared/icons/navigation-link.svg +1 -0
  197. package/src/main/shared/icons/navigation-refresh.svg +1 -0
  198. package/src/main/shared/icons/navigation-search.svg +1 -0
  199. package/src/main/shared/icons/navigation-share.svg +1 -0
  200. package/src/main/shared/icons/toggle-newwindow.svg +1 -0
  201. package/src/main/shared/icons.js +18 -0
  202. package/src/main/shared/id-from-string.js +5 -0
  203. package/src/main/shared/mark-selection.js +19 -0
  204. package/src/main/shared/register.js +26 -0
  205. package/src/main/shared/renderer.js +43 -0
  206. package/src/main/shared/simple-consent-api.js +70 -0
  207. package/src/main/shared/split-links.js +11 -0
  208. package/src/main/shared/t.js +60 -0
  209. package/src/main/shared/twind.js +837 -0
  210. package/src/main/shared/update-head.js +34 -0
  211. package/src/main/shared/update-scrollbar-width.js +30 -0
  212. package/src/main/shared/use-link.js +151 -0
  213. package/src/main/shared/use-persistent-state.js +42 -0
  214. package/src/main/shared/wait-for-dom-ready.js +6 -0
  215. package/src/main/shared/wcm-mode.js +4 -0
  216. package/src/wcs/components/acinguiux-preact-doc/acinguiux-preact-doc.js +207 -0
  217. package/src/wcs/components/admin-dashboard/admin-dashboard.js +487 -0
  218. package/src/wcs/components/admin-login/admin-login.js +91 -0
  219. package/src/wcs/components/bazaar-voice/bazaar-voice.js +56 -0
  220. package/src/wcs/components/chatbot-koreai/chatbot-koreai.js +176 -0
  221. package/src/wcs/components/chatbot-koreai/koreai-transport.js +217 -0
  222. package/src/wcs/components/chatbot-ms/chatbot-ms.js +210 -0
  223. package/src/wcs/components/chatbot-test/chatbot-test.js +44 -0
  224. package/src/wcs/components/comparison-chart/comparison-chart.js +111 -0
  225. package/src/wcs/components/consent-banner/consent-banner.js +248 -0
  226. package/src/wcs/components/consent-banner/icons/ccpa.svg +6 -0
  227. package/src/wcs/components/consent-banner/icons/info.svg +1 -0
  228. package/src/wcs/components/consent-banner/provider-onetrust.js +131 -0
  229. package/src/wcs/components/decision-tree/arrow-back.svg +3 -0
  230. package/src/wcs/components/decision-tree/badges.js +37 -0
  231. package/src/wcs/components/decision-tree/decision-tree.js +162 -0
  232. package/src/wcs/components/dynamic-contact-details/dynamic-contact-details.js +111 -0
  233. package/src/wcs/components/example-accordion/example-accordion.js +10 -0
  234. package/src/wcs/components/example-asset/example-asset.js +12 -0
  235. package/src/wcs/components/example-form/example-form.js +59 -0
  236. package/src/wcs/components/example-nested/example-nested.js +10 -0
  237. package/src/wcs/components/example-routing/example-routing.js +51 -0
  238. package/src/wcs/components/example-rtl/example-rtl.js +28 -0
  239. package/src/wcs/components/example-tabs/example-tabs.js +12 -0
  240. package/src/wcs/components/example-web-component/example-web-component.js +34 -0
  241. package/src/wcs/components/floating-button/floating-button.js +17 -0
  242. package/src/wcs/components/formstack-form/fields/address.js +38 -0
  243. package/src/wcs/components/formstack-form/fields/checkbox.js +42 -0
  244. package/src/wcs/components/formstack-form/fields/date.js +22 -0
  245. package/src/wcs/components/formstack-form/fields/description.js +8 -0
  246. package/src/wcs/components/formstack-form/fields/input.js +8 -0
  247. package/src/wcs/components/formstack-form/fields/name.js +39 -0
  248. package/src/wcs/components/formstack-form/fields/radio.js +24 -0
  249. package/src/wcs/components/formstack-form/fields/rating.js +53 -0
  250. package/src/wcs/components/formstack-form/fields/section.js +8 -0
  251. package/src/wcs/components/formstack-form/fields/select.js +10 -0
  252. package/src/wcs/components/formstack-form/fields/textarea.js +8 -0
  253. package/src/wcs/components/formstack-form/fields/wrapper.js +11 -0
  254. package/src/wcs/components/formstack-form/formstack-form.js +280 -0
  255. package/src/wcs/components/fuel-prices/fuel-prices.js +45 -0
  256. package/src/wcs/components/furniture-overview/furniture-overview.js +115 -0
  257. package/src/wcs/components/gauge-value/gauge-value.js +65 -0
  258. package/src/wcs/components/help-centre/api.js +150 -0
  259. package/src/wcs/components/help-centre/help-centre.js +400 -0
  260. package/src/wcs/components/help-centre/icon-search.svg +1 -0
  261. package/src/wcs/components/image-gen/admin-panel.js +248 -0
  262. package/src/wcs/components/image-gen/image-gen.js +385 -0
  263. package/src/wcs/components/image-gen/labels.js +37 -0
  264. package/src/wcs/components/image-gen/use-api.js +392 -0
  265. package/src/wcs/components/inspired-gallery/inspired-gallery.js +118 -0
  266. package/src/wcs/components/launch-container/launch-container.js +95 -0
  267. package/src/wcs/components/launch-container/ledger.js +140 -0
  268. package/src/wcs/components/lng-map/lng-map.js +44 -0
  269. package/src/wcs/components/mouseflow-analytics/mouseflow-analytics.js +39 -0
  270. package/src/wcs/components/msds-search/msds-search.js +127 -0
  271. package/src/wcs/components/msds-search/navigation-search.svg +3 -0
  272. package/src/wcs/components/product-catalogue/icon-back.svg +3 -0
  273. package/src/wcs/components/product-catalogue/icon-cart.svg +3 -0
  274. package/src/wcs/components/product-catalogue/icon-close.svg +3 -0
  275. package/src/wcs/components/product-catalogue/product-catalogue.js +215 -0
  276. package/src/wcs/components/product-links/icon-cart.svg +3 -0
  277. package/src/wcs/components/product-links/product-links.js +43 -0
  278. package/src/wcs/components/rio-iframe/rio-iframe.js +137 -0
  279. package/src/wcs/components/salsify-products/filter-tools.js +60 -0
  280. package/src/wcs/components/salsify-products/icon-cart.svg +3 -0
  281. package/src/wcs/components/salsify-products/process-products.js +53 -0
  282. package/src/wcs/components/salsify-products/route-tools.js +54 -0
  283. package/src/wcs/components/salsify-products/salsify-products.js +281 -0
  284. package/src/wcs/components/shout-out/shout-out.js +51 -0
  285. package/src/wcs/components/simple-chart/simple-chart.js +53 -0
  286. package/src/wcs/components/single-stat/single-stat.js +85 -0
  287. package/src/wcs/components/site-feedback/site-feedback.js +56 -0
  288. package/src/wcs/components/skds-search/navigation-search.svg +3 -0
  289. package/src/wcs/components/skds-search/skds-search.js +103 -0
  290. package/src/wcs/components/smart-banner/smart-banner.js +104 -0
  291. package/src/wcs/components/standalone-table/arrow-up-down.svg +3 -0
  292. package/src/wcs/components/standalone-table/arrow-up.svg +3 -0
  293. package/src/wcs/components/standalone-table/standalone-table.js +440 -0
  294. package/src/wcs/components/station-locator/station-locator.js +49 -0
  295. package/src/wcs/components/store-badges/badges.js +60 -0
  296. package/src/wcs/components/store-badges/store-badges.js +93 -0
  297. package/src/wcs/components/topbar-button/person.svg +1 -0
  298. package/src/wcs/components/topbar-button/topbar-button.js +22 -0
  299. package/src/wcs/components/universal-gallery/universal-gallery.js +308 -0
  300. package/src/wcs/components/zendesk-chat/zendesk-chat.js +133 -0
  301. package/src/wcs/shared/chat-bot/README.md +61 -0
  302. package/src/wcs/shared/chat-bot/chat-bot.js +216 -0
  303. package/src/wcs/shared/chat-bot/resources/arrow-next.svg +1 -0
  304. package/src/wcs/shared/chat-bot/resources/navigation-close.svg +1 -0
  305. package/src/wcs/shared/chat-bot/resources/person.svg +1 -0
  306. package/src/wcs/shared/chat-bot/resources/upload.svg +1 -0
  307. package/src/wcs/shared/filtered-data/README.md +52 -0
  308. package/src/wcs/shared/filtered-data/fetch-data.js +33 -0
  309. package/src/wcs/shared/filtered-data/filtered-data.js +337 -0
  310. package/src/wcs/shared/promo-with-popup/icon-close.svg +3 -0
  311. package/src/wcs/shared/promo-with-popup/icon-next.svg +3 -0
  312. package/src/wcs/shared/promo-with-popup/icon-prev.svg +3 -0
  313. package/src/wcs/shared/promo-with-popup/promo-with-popup.js +93 -0
@@ -0,0 +1,392 @@
1
+ import LABELS from './labels.js'
2
+ const { preactHooks } = globalThis.ami
3
+ const { useCallback, useEffect, useMemo, useRef, useState } = preactHooks
4
+
5
+ // Access token.
6
+ const AUTH_TOKEN_URL = '/bin/acinguiux/imagegen'
7
+
8
+ // Backend service paths and configuration.
9
+ const ENDPOINTS = {
10
+ AUTH: 'auth',
11
+ GENERATE: 'images/generate-async',
12
+ LIST: 'list',
13
+ QUOTA_USAGE: 'quota-usage',
14
+ USER_STATUS: 'user-status',
15
+ ADMIN: 'admin'
16
+ }
17
+ const REQUEST_RETRY_COUNT = 1
18
+ const REQUEST_RETRY_INTERVAL = 1000
19
+ const REQUEST_TIMEOUT = 60_000
20
+ const STATUS_POLL_INTERVAL = 2000
21
+ const STATUS_POLL_ATTEMPTS = 60
22
+
23
+ // Component status.
24
+ export const STATUS = {
25
+ LOADING: 1,
26
+ READY: 2,
27
+ PROCESSING: 3,
28
+ ERROR: 4
29
+ }
30
+
31
+ // Prompt and load more status.
32
+ export const PROMPT_STATUS = {
33
+ PENDING: 'pending',
34
+ RUNNING: 'running',
35
+ SUCCEEDED: 'succeeded',
36
+ CANCELLED: 'canceled',
37
+ FAILED: 'failed'
38
+ }
39
+
40
+ export const LOAD_MORE_STATUS = {
41
+ NONE: 1,
42
+ IN_PROGRESS: 2,
43
+ ACTIVE: 3
44
+ }
45
+
46
+ // Model version.
47
+ const MODEL_VERSION = { standard: 'image4_standard', custom: 'image4_custom' }
48
+
49
+ // HTTP status codes. Note unlike the HTTP error names, we use the following semantics in the codebase:
50
+ // 401 - unauthenticated (not logged in), 403 - unauthorized (logged in but no access).
51
+ const HTTP_BAD_REQUEST = 400
52
+ const HTTP_UNAUTHORIZED = 401
53
+ const HTTP_FORBIDDEN = 403
54
+
55
+ // Simple helper moved 2 levels up from statusPoll() to satisfy Unicorn.
56
+ const isPendingStatus = status => !status || [PROMPT_STATUS.PENDING, PROMPT_STATUS.RUNNING].includes(status)
57
+
58
+ // Request error with user messaging (alert box).
59
+ class RequestError extends Error {
60
+ constructor (message, { _response, _status, ...options }) {
61
+ super(message, options)
62
+
63
+ this._status = _status || _response?.status
64
+ this._response = _response
65
+ }
66
+
67
+ async getUserMessage (display = true) {
68
+ let userMessage
69
+
70
+ // For generic request error, use the error message from the response body if available.
71
+ if (this._response && (this._status === HTTP_BAD_REQUEST) && this._response.headers.get('Content-Type')?.includes('application/json')) {
72
+ try {
73
+ userMessage = (await this._response.json()).error
74
+ } catch {
75
+ // Ignore
76
+ }
77
+ }
78
+
79
+ // Fall back to the default mapping.
80
+ if (!userMessage) {
81
+ const errorMap = {
82
+ [HTTP_UNAUTHORIZED]: 'ERROR_LOGGED_OFF',
83
+ [HTTP_FORBIDDEN]: 'ERROR_UNAUTHORISED'
84
+ }
85
+ const labelKey = errorMap[this._status] || 'ERROR'
86
+ userMessage = LABELS[labelKey]
87
+ }
88
+
89
+ // Display the user message.
90
+ display && alert(userMessage)
91
+
92
+ return userMessage
93
+ }
94
+ }
95
+
96
+ // Main hook to handle image generation requests and status polling. Not an isolated implementation, affects the main component state.
97
+ export default function useApi ({ apiUrl, isAnonymous, setState }) {
98
+ const [prompts, setPrompts] = useState([])
99
+ const [quotaUsage, setQuotaUsage] = useState({ total: null, usage: null })
100
+
101
+ // Currently processed auth token promise, to avoid concurrent calls.
102
+ const authPromiseRef = useRef(null)
103
+ // Current auth token cached value.
104
+ const authTokenRef = useRef(null)
105
+ // Current prompt list for always fresh data in async functions - updated on each change. Null indicates not initialized yet.
106
+ const promptsRef = useRef(null)
107
+ // Counter to generate new prompt artificial IDs.
108
+ const newPromptCounterRef = useRef(0)
109
+ // Reference to the status polling handler to allow cleanup.
110
+ const statusPollHandlerRef = useRef(null)
111
+
112
+ useEffect(() => () => {
113
+ statusPollHandlerRef.current && clearTimeout(statusPollHandlerRef.current)
114
+ }, [])
115
+
116
+ useEffect(() => {
117
+ if (promptsRef.current !== null) {
118
+ promptsRef.current = prompts
119
+ }
120
+ }, [prompts])
121
+
122
+ // Decorate raw prompt with predefined custom fields.
123
+ const parsePrompt = useCallback(prompt => ({
124
+ ...prompt,
125
+ images: prompt.result?.outputs?.map?.(output => {
126
+ if (!output?.image?.url) {
127
+ return null
128
+ }
129
+ const url = `${apiUrl}${output?.image?.url}`
130
+ return {
131
+ url,
132
+ downloadUrl: url && `${url}${url.includes('?') ? '&' : '?'}download`,
133
+ seed: output?.seed
134
+ }
135
+ }).filter(Boolean) || [],
136
+ initDateTime: prompt.initTime && new Date(prompt.initTime)
137
+ }), [apiUrl])
138
+
139
+ // Auth token fetcher: returns true (for anonymous calls) or the access token on success, throws an exception on failure.
140
+ const authenticate = useCallback(async () => {
141
+ // Anonymous access in non-AEM environments.
142
+ if (isAnonymous) {
143
+ return true
144
+ }
145
+
146
+ if (authTokenRef.current) {
147
+ return authTokenRef.current
148
+ }
149
+
150
+ // Get a new auth token.
151
+ const res = await fetch(AUTH_TOKEN_URL, {
152
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT),
153
+ method: 'GET',
154
+ headers: {
155
+ Accept: 'application/json'
156
+ }
157
+ })
158
+
159
+ if (res.ok) {
160
+ authTokenRef.current = (await res.json())?.token
161
+ } else {
162
+ // Always assume an authentication error, even if 404 (means the user logged out).
163
+ throw new RequestError(`Get token: server responded with ${res.status}`, { _status: HTTP_UNAUTHORIZED })
164
+ }
165
+
166
+ return authTokenRef.current
167
+ }, [isAnonymous])
168
+
169
+ // Generic request sender with retry and auth handling.
170
+ const sendRequest = useCallback(async (endpoint, { body, headers, ...props } = {}) => {
171
+ let res
172
+ for (let i = 0; i < REQUEST_RETRY_COUNT + 1; i++) {
173
+ // Ensure there is always a single call to the auth services.
174
+ if (!authPromiseRef.current) {
175
+ authPromiseRef.current = authenticate()
176
+ }
177
+ // Authorization throws an error if failed.
178
+ let token
179
+ try {
180
+ token = await authPromiseRef.current
181
+ } finally {
182
+ authPromiseRef.current = null
183
+ }
184
+
185
+ // Perform the main request.
186
+ try {
187
+ res = await fetch(`${apiUrl}${endpoint}`, {
188
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT),
189
+ method: 'get',
190
+ ...!isAnonymous && { credentials: 'include' },
191
+ ...props,
192
+ headers: {
193
+ ...body && { 'Content-Type': 'application/json' },
194
+ ...!isAnonymous && { Authorization: `Bearer ${token}` },
195
+ ...headers
196
+ },
197
+ ...body && { body: JSON.stringify(body) }
198
+ })
199
+ } catch (error) {
200
+ throw new RequestError(`Request failed: ${error.message}`, { cause: error })
201
+ }
202
+
203
+ if (res.ok) {
204
+ break
205
+ } else if ((i === REQUEST_RETRY_COUNT) || (res.status === HTTP_FORBIDDEN)) { // No retries for authorization errors.
206
+ throw new RequestError(`Server responded with ${res.status}`, { _response: res })
207
+ }
208
+ if (res.status === HTTP_UNAUTHORIZED) {
209
+ authTokenRef.current = null // Clear token on auth error to force refresh.
210
+ }
211
+ await new Promise(resolve => setTimeout(resolve, REQUEST_RETRY_INTERVAL)) // Wait before retrying.
212
+ }
213
+
214
+ return res
215
+ }, [apiUrl, authenticate, isAnonymous])
216
+
217
+ // Load prompt list (initial or load more).
218
+ const loadPrompts = async () => {
219
+ const isInitial = promptsRef.current === null
220
+ promptsRef.current = prompts
221
+ if (!isInitial) {
222
+ setState(prev => ({
223
+ ...prev,
224
+ loadMore: LOAD_MORE_STATUS.IN_PROGRESS
225
+ }))
226
+ }
227
+
228
+ let listJson
229
+ let res
230
+ try {
231
+ // Pass lastId only for the subsequent calls (load more).
232
+ const lastPromptWithId = isInitial ? undefined : prompts.findLast?.(p => p.id)
233
+ const query = lastPromptWithId ? `?lastId=${encodeURIComponent(lastPromptWithId.id)}` : ''
234
+
235
+ res = await sendRequest(`${ENDPOINTS.LIST}${query}`)
236
+ listJson = await res.json()
237
+ listJson.prompts = listJson.prompts?.map(parsePrompt)
238
+ } catch (error) {
239
+ console.error('Error fetching prompts:', error)
240
+ if (isInitial) {
241
+ // For the initial call, set the component state for a global error.
242
+ const statusMessage = await error.getUserMessage?.(false)
243
+ setState(prev => ({ ...prev, status: STATUS.ERROR, statusMessage }))
244
+ } else {
245
+ // Otherwise show the regular error message and display load more button again to allow retry.
246
+ setState(prev => ({ ...prev, loadMore: LOAD_MORE_STATUS.ACTIVE }))
247
+ await error.getUserMessage?.()
248
+ }
249
+ return
250
+ }
251
+ setPrompts(prev => [...prev, ...(listJson.prompts || [])])
252
+ setState(prev => ({
253
+ ...prev,
254
+ ...prev.status === STATUS.LOADING && { status: STATUS.READY },
255
+ loadMore: listJson.hasMore ? LOAD_MORE_STATUS.ACTIVE : LOAD_MORE_STATUS.NONE,
256
+ ...!isInitial && { lastLoadPromptId: listJson.prompts[0]?.id }
257
+ }))
258
+ await updateQuotaUsage()
259
+ }
260
+
261
+ // Send new prompt request.
262
+ const submitPrompt = useCallback(async (formData, onError) => {
263
+ // Setting artificial newItemId to identify the prompt item while updating.
264
+ let prompt = { newItemId: newPromptCounterRef.current++, text: formData.prompt, status: PROMPT_STATUS.PENDING, numVariations: formData.numVariations }
265
+ setState(prev => ({ ...prev, status: STATUS.PROCESSING }))
266
+ setPrompts(currPrompts => [prompt, ...currPrompts])
267
+
268
+ let res
269
+ try {
270
+ res = await sendRequest(ENDPOINTS.GENERATE, {
271
+ method: 'post',
272
+ headers: {
273
+ 'x-model-version': MODEL_VERSION[formData.customModelId?.length ? 'custom' : 'standard']
274
+ },
275
+ body: formData
276
+ })
277
+ const { id, jobId, cancelUrl, statusUrl, initTime } = await res.json()
278
+ prompt = parsePrompt({ ...prompt, id, jobId, cancelUrl, statusUrl, initTime, status: PROMPT_STATUS.RUNNING })
279
+ } catch (error) {
280
+ console.error('Error submitting image generation prompt:', error)
281
+ await error.getUserMessage?.()
282
+ prompt = { ...prompt, status: PROMPT_STATUS.FAILED }
283
+ onError?.()
284
+ }
285
+
286
+ setPrompts(currPrompts => {
287
+ const promptIndex = currPrompts.findIndex(p => p.newItemId === prompt.newItemId)
288
+ return [
289
+ ...currPrompts.slice(0, promptIndex),
290
+ { ...currPrompts[promptIndex], ...prompt },
291
+ ...currPrompts.slice(promptIndex + 1)
292
+ ]
293
+ })
294
+ setState(prev => ({ ...prev, status: STATUS.READY }))
295
+ }, [parsePrompt, sendRequest])
296
+
297
+ // Polling function fired on prompt list change. If not all prompts are ready, the polling is continued after the interval.
298
+ const statusPoll = async (attempt = 1) => {
299
+ // Clear potential stale handler.
300
+ clearTimeout(statusPollHandlerRef.current)
301
+
302
+ let hasPendingPrompts = false
303
+ for (const prompt of promptsRef.current || []) {
304
+ if (!prompt?.statusUrl) {
305
+ // Not ready yet - just initiated.
306
+ hasPendingPrompts = true
307
+ continue
308
+ }
309
+ if (!isPendingStatus(prompt.status) || !prompt.jobId) {
310
+ continue
311
+ }
312
+
313
+ let resJson
314
+ try {
315
+ const res = await sendRequest(prompt.statusUrl)
316
+ if (!res.ok) {
317
+ throw new Error(`Server responded with ${res.status}`)
318
+ }
319
+ resJson = await res.json()
320
+ } catch (error) {
321
+ console.error('Error polling prompt status:', error)
322
+ // No retry, just ignore to avoid overloading the server.
323
+ continue
324
+ }
325
+ const statusMap = {
326
+ succeeded: PROMPT_STATUS.SUCCEEDED,
327
+ canceled: PROMPT_STATUS.CANCELLED,
328
+ failed: PROMPT_STATUS.FAILED
329
+ }
330
+ const status = statusMap[resJson.status]
331
+ if (isPendingStatus(status)) {
332
+ hasPendingPrompts = true
333
+ continue
334
+ }
335
+
336
+ setPrompts(currPrompts => {
337
+ const promptIndex = currPrompts.findIndex(p => p.jobId === prompt.jobId)
338
+ return [
339
+ ...currPrompts.slice(0, promptIndex),
340
+ parsePrompt({ ...prompt, status, result: resJson.result }),
341
+ ...currPrompts.slice(promptIndex + 1)
342
+ ]
343
+ })
344
+ await updateQuotaUsage()
345
+ }
346
+ if (hasPendingPrompts && (attempt < STATUS_POLL_ATTEMPTS)) {
347
+ statusPollHandlerRef.current = setTimeout(() => statusPoll(attempt + 1), STATUS_POLL_INTERVAL)
348
+ }
349
+ }
350
+
351
+ // Quota/usage updater.
352
+ const updateQuotaUsage = async () => {
353
+ try {
354
+ const res = await sendRequest(ENDPOINTS.QUOTA_USAGE)
355
+ if (!res.ok) {
356
+ throw new Error(`Server responded with ${res.status}`)
357
+ }
358
+ const { total, usage } = await res.json()
359
+ if (typeof total === 'number' && typeof usage === 'number') {
360
+ setQuotaUsage({ total, usage })
361
+ }
362
+ } catch (error) {
363
+ // Fail silently.
364
+ console.error('Error getting quota/usage:', error)
365
+ }
366
+ }
367
+
368
+ // Admin tools.
369
+ const adminTools = useMemo(() => ({
370
+ request: async (action, body) => {
371
+ const isUserStatus = action === ENDPOINTS.USER_STATUS
372
+ const endpoint = isUserStatus ? action : `${ENDPOINTS.ADMIN}/${action}`
373
+ let res
374
+ try {
375
+ res = await sendRequest(endpoint, {
376
+ method: action === 'user' ? 'post' : 'get',
377
+ body
378
+ })
379
+ } catch (error) {
380
+ console.error('Error requesting admin endpoint:', error)
381
+ !isUserStatus && await error.getUserMessage?.()
382
+ return null
383
+ }
384
+
385
+ return res.json()
386
+ },
387
+
388
+ getUrl: action => `${apiUrl}${ENDPOINTS.ADMIN}/${action}`
389
+ }), [apiUrl, sendRequest])
390
+
391
+ return [prompts, loadPrompts, statusPoll, submitPrompt, quotaUsage, adminTools]
392
+ }
@@ -0,0 +1,118 @@
1
+ import { fetchCsv } from '#acinguiux-preact/wcs/shared/filtered-data/fetch-data.js'
2
+ import FilteredData from '#acinguiux-preact/wcs/shared/filtered-data/filtered-data.js'
3
+ import PromoWithPopup from '#acinguiux-preact/wcs/shared/promo-with-popup/promo-with-popup.js'
4
+
5
+ const { h, register } = globalThis.ami
6
+
7
+ const LABELS = {
8
+ SEARCH_TEXT: 'Search',
9
+ ORDER_BY: 'Sort by',
10
+ LOAD_MORE: 'Load more',
11
+ RESET_FILTERS: 'Reset filters',
12
+ ASCENDING: 'ascending',
13
+ DESCENDING: 'descending',
14
+ BACK_TO_OVERVIEW: 'Back to overview',
15
+ PREVIOUS_ITEM: 'Go to the previous item',
16
+ NEXT_ITEM: 'Go to the next item'
17
+ }
18
+
19
+ const FIELDS = {
20
+ SPACE_TYPE: 'Space type',
21
+ MOMENTS_THAT_MATTER: 'Moments that Matter',
22
+ COLOUR: 'Colour',
23
+ SITE_NAME: 'Site Name',
24
+ COUNTRY: 'Country',
25
+ REGION: 'Region',
26
+ BUILDING_ZONE: 'Building Zone',
27
+ PEOPLE_PRESENCE: 'People Presence',
28
+ DESCRIPTION: 'Description',
29
+ IMAGE_PATH: 'Image Path',
30
+ TITLE: 'Title'
31
+ }
32
+
33
+ function valueToArray (item, key) {
34
+ return item[key]?.split(',').map(value => value.trim())
35
+ }
36
+
37
+ async function getData ({ src, imageRoot }) {
38
+ const items = await fetchCsv(src)
39
+ const sanitizedImageRoot = (imageRoot ?? '').replace(/\/$/, '')
40
+
41
+ return items
42
+ .filter(item => item[FIELDS.TITLE])
43
+ .map(item => ({
44
+ ...item,
45
+ [FIELDS.MOMENTS_THAT_MATTER]: valueToArray(item, FIELDS.MOMENTS_THAT_MATTER),
46
+ [FIELDS.COLOUR]: valueToArray(item, FIELDS.COLOUR),
47
+ [FIELDS.DESCRIPTION]: valueToArray(item, FIELDS.DESCRIPTION),
48
+ [FIELDS.IMAGE_PATH]: `${sanitizedImageRoot}/${item[FIELDS.IMAGE_PATH]}`
49
+ }))
50
+ }
51
+
52
+ function Items ({ items }) {
53
+ const models = items.map(item => ({
54
+ image: { src: item[FIELDS.IMAGE_PATH], alt: item[FIELDS.TITLE] },
55
+ title: item[FIELDS.TITLE],
56
+ text: item[FIELDS.SITE_NAME],
57
+ links: [
58
+ { name: FIELDS.SPACE_TYPE, value: item[FIELDS.SPACE_TYPE] },
59
+ { name: FIELDS.MOMENTS_THAT_MATTER, value: item[FIELDS.MOMENTS_THAT_MATTER] },
60
+ { name: FIELDS.COLOUR, value: item[FIELDS.COLOUR] },
61
+ { name: FIELDS.SITE_NAME, value: item[FIELDS.SITE_NAME] },
62
+ { name: FIELDS.COUNTRY, value: item[FIELDS.COUNTRY] },
63
+ { name: FIELDS.REGION, value: item[FIELDS.REGION] },
64
+ { name: FIELDS.BUILDING_ZONE, value: item[FIELDS.BUILDING_ZONE] },
65
+ { name: FIELDS.PEOPLE_PRESENCE, value: item[FIELDS.PEOPLE_PRESENCE] },
66
+ { name: FIELDS.DESCRIPTION, value: item[FIELDS.DESCRIPTION] }
67
+ ]
68
+ }))
69
+
70
+ return h('div', { className: 'grid gap-5 grid-cols-4 md:grid-cols-2 sm:grid-cols-1', role: 'list' }, models.map((_m, index) =>
71
+ h(PromoWithPopup, { models, currentModelIndex: index })
72
+ ))
73
+ }
74
+
75
+ function InspiredGallery ({ src, 'image-root': imageRoot }) {
76
+ if (!src) {
77
+ return null
78
+ }
79
+ return h(FilteredData, {
80
+ getData,
81
+ filters: [{
82
+ type: 'text',
83
+ label: LABELS.SEARCH_TEXT,
84
+ keys: [FIELDS.TITLE, FIELDS.SPACE_TYPE, FIELDS.SITE_NAME, FIELDS.MOMENTS_THAT_MATTER, FIELDS.COLOUR, FIELDS.COUNTRY, FIELDS.REGION, FIELDS.BUILDING_ZONE, FIELDS.DESCRIPTION]
85
+ }, {
86
+ type: 'select',
87
+ keys: ['', FIELDS.SPACE_TYPE]
88
+ }, {
89
+ type: 'select',
90
+ keys: ['', FIELDS.MOMENTS_THAT_MATTER]
91
+ }, {
92
+ type: 'select',
93
+ keys: ['', FIELDS.COLOUR]
94
+ }, {
95
+ type: 'select',
96
+ keys: ['', FIELDS.SITE_NAME]
97
+ }, {
98
+ type: 'select',
99
+ label: 'Region',
100
+ keys: ['', FIELDS.REGION]
101
+ }, {
102
+ type: 'select',
103
+ keys: ['', FIELDS.BUILDING_ZONE]
104
+ }, {
105
+ type: 'select',
106
+ keys: ['', FIELDS.PEOPLE_PRESENCE]
107
+ }, {
108
+ type: 'order',
109
+ label: LABELS.ORDER_BY,
110
+ keys: [FIELDS.SITE_NAME, `-${FIELDS.SITE_NAME}`, FIELDS.MOMENTS_THAT_MATTER, FIELDS.COLOUR, FIELDS.COUNTRY, `-${FIELDS.COUNTRY}`, FIELDS.REGION, `-${FIELDS.REGION}`, FIELDS.BUILDING_ZONE]
111
+ }],
112
+ Items,
113
+ src,
114
+ imageRoot
115
+ })
116
+ }
117
+
118
+ register(InspiredGallery, 'inspired-gallery')
@@ -0,0 +1,95 @@
1
+ import { initLedger } from './ledger.js'
2
+
3
+ const { register, waitForConsent } = globalThis.ami
4
+
5
+ const LAUNCH_DELAY = 1000 // Give it enough time for the page to render properly first.
6
+ const LAUNCH_ID = 'adobe-launch-container-script'
7
+ const POLL_STEP = 500
8
+ const ADOBE_SERVICES = {
9
+ statistics: ['ecid', 'aa', 'aam'],
10
+ preferences: ['target']
11
+ }
12
+
13
+ function loadLaunch (src, consent) {
14
+ // If no Launch link or of Launch is already loaded.
15
+ if (!src || document.getElementById(LAUNCH_ID)) {
16
+ return
17
+ }
18
+
19
+ const script = document.createElement('script')
20
+ script.id = LAUNCH_ID
21
+ script.setAttribute('async', '')
22
+ script.setAttribute('defer', '')
23
+ script.setAttribute('src', src)
24
+ document.head.appendChild(script)
25
+
26
+ // Launch is loaded only when consent is granted.
27
+ adobeConsent(consent)
28
+ googleConsent(consent)
29
+ pageBottom()
30
+ }
31
+
32
+ function adobeConsent (consent) {
33
+ // Wait for Adobe Opt-In to be loaded via Launch.
34
+ if (!globalThis?.adobe?.optIn) {
35
+ setTimeout(() => adobeConsent(consent), POLL_STEP)
36
+ return
37
+ }
38
+
39
+ // Approve selected services only.
40
+ for (const [name, services] of Object.entries(ADOBE_SERVICES)) {
41
+ if (consent[name]) {
42
+ globalThis.adobe.optIn.approve(services)
43
+ } else {
44
+ globalThis.adobe.optIn.deny(services)
45
+ }
46
+ }
47
+ }
48
+
49
+ function googleConsent (consent) {
50
+ globalThis.dataLayer ||= []
51
+
52
+ const marketingConsent = consent.marketing ? 'granted' : 'denied'
53
+ const statisticsConsent = consent.statistics ? 'granted' : 'denied'
54
+
55
+ ;(function () {
56
+ // eslint-disable-next-line prefer-rest-params -- Google API requires Arguments object, not rest parameters or spread
57
+ globalThis.dataLayer.push(arguments) // NOSONAR
58
+ })('consent', 'default', {
59
+ ad_storage: marketingConsent,
60
+ ad_user_data: marketingConsent,
61
+ ad_personalization: marketingConsent,
62
+ analytics_storage: statisticsConsent
63
+ })
64
+ }
65
+
66
+ function pageBottom () {
67
+ if (!globalThis._satellite?.pageBottom) {
68
+ setTimeout(pageBottom, POLL_STEP)
69
+ return
70
+ }
71
+
72
+ globalThis._satellite.pageBottom()
73
+ }
74
+
75
+ function init (src, consent) {
76
+ initLedger()
77
+
78
+ // Delay Launch injection.
79
+ setTimeout(() => {
80
+ loadLaunch(src, consent)
81
+ }, LAUNCH_DELAY)
82
+ }
83
+
84
+ function LaunchContainer ({ src }) {
85
+ const force = new URLSearchParams(location.search).has('force-launch')
86
+ const consentToCheck = { statistics: true, preferences: true, marketing: true }
87
+
88
+ if (force) {
89
+ init(src, consentToCheck)
90
+ } else {
91
+ waitForConsent(consentToCheck, false).then(consent => init(src, consent))
92
+ }
93
+ }
94
+
95
+ register(LaunchContainer, 'launch-container')