@dcl-regenesislabs/web-editor 0.0.0-stage → 0.1.0

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 (277) hide show
  1. package/editor.css +1 -0
  2. package/editor.js +33891 -0
  3. package/package.json +8 -4
  4. package/prefabs/admin-tools/composite.json +77 -0
  5. package/prefabs/admin-tools/data.json +28 -0
  6. package/prefabs/admin-tools/icons/admin-panel-control-button.png +0 -0
  7. package/prefabs/admin-tools/icons/admin-panel-moderation-control-button.png +0 -0
  8. package/prefabs/admin-tools/icons/admin-panel-rewards-control-button.png +0 -0
  9. package/prefabs/admin-tools/icons/admin-panel-smart-item-control-button.png +0 -0
  10. package/prefabs/admin-tools/icons/admin-panel-text-announcement-control-button.png +0 -0
  11. package/prefabs/admin-tools/icons/admin-panel-video-control-button.png +0 -0
  12. package/prefabs/admin-tools/icons/admin-tool-background.png +0 -0
  13. package/prefabs/admin-tools/icons/announcements/text-announcement-chat-message.png +0 -0
  14. package/prefabs/admin-tools/icons/announcements/text-announcement-check.png +0 -0
  15. package/prefabs/admin-tools/icons/announcements/text-announcement-close-button.png +0 -0
  16. package/prefabs/admin-tools/icons/moderation/admin-panel-verified-user.png +0 -0
  17. package/prefabs/admin-tools/icons/moderation/ban.png +0 -0
  18. package/prefabs/admin-tools/icons/moderation/chevron-back.png +0 -0
  19. package/prefabs/admin-tools/icons/moderation/chevron-forward.png +0 -0
  20. package/prefabs/admin-tools/icons/moderation/close.png +0 -0
  21. package/prefabs/admin-tools/icons/moderation/error.png +0 -0
  22. package/prefabs/admin-tools/icons/moderation/person-outline.png +0 -0
  23. package/prefabs/admin-tools/icons/moderation-control-icon.png +0 -0
  24. package/prefabs/admin-tools/icons/rewards/close.png +0 -0
  25. package/prefabs/admin-tools/icons/rewards/send.png +0 -0
  26. package/prefabs/admin-tools/icons/rewards-control.png +0 -0
  27. package/prefabs/admin-tools/icons/smart-item-control.png +0 -0
  28. package/prefabs/admin-tools/icons/text-announcement-control.png +0 -0
  29. package/prefabs/admin-tools/icons/video/chevron-down.png +0 -0
  30. package/prefabs/admin-tools/icons/video/chevron-up.png +0 -0
  31. package/prefabs/admin-tools/icons/video/copy-to-clipboard.png +0 -0
  32. package/prefabs/admin-tools/icons/video/error.png +0 -0
  33. package/prefabs/admin-tools/icons/video/eye-off.png +0 -0
  34. package/prefabs/admin-tools/icons/video/eye.png +0 -0
  35. package/prefabs/admin-tools/icons/video/help.png +0 -0
  36. package/prefabs/admin-tools/icons/video/info.png +0 -0
  37. package/prefabs/admin-tools/icons/video/person-outline.png +0 -0
  38. package/prefabs/admin-tools/icons/video/star.png +0 -0
  39. package/prefabs/admin-tools/icons/video/video-control-dcl-cast.png +0 -0
  40. package/prefabs/admin-tools/icons/video/video-control-live.png +0 -0
  41. package/prefabs/admin-tools/icons/video/video-control-loop.png +0 -0
  42. package/prefabs/admin-tools/icons/video/video-control-mute.png +0 -0
  43. package/prefabs/admin-tools/icons/video/video-control-pause-button.png +0 -0
  44. package/prefabs/admin-tools/icons/video/video-control-play-button.png +0 -0
  45. package/prefabs/admin-tools/icons/video/video-control-video-icon.png +0 -0
  46. package/prefabs/admin-tools/icons/video/video-control-volume-minus-button.png +0 -0
  47. package/prefabs/admin-tools/icons/video/video-control-volume-plus-button.png +0 -0
  48. package/prefabs/admin-tools/icons/video-control.png +0 -0
  49. package/prefabs/admin-tools/scripts/actions.ts +206 -0
  50. package/prefabs/admin-tools/scripts/admin.tsx +253 -0
  51. package/prefabs/admin-tools/scripts/api.ts +125 -0
  52. package/prefabs/admin-tools/scripts/components.ts +190 -0
  53. package/prefabs/admin-tools/scripts/icons.ts +53 -0
  54. package/prefabs/admin-tools/scripts/message-bus.ts +297 -0
  55. package/prefabs/admin-tools/scripts/state.ts +69 -0
  56. package/prefabs/admin-tools/scripts/tabs/Moderation.tsx +155 -0
  57. package/prefabs/admin-tools/scripts/tabs/Rewards.tsx +136 -0
  58. package/prefabs/admin-tools/scripts/tabs/SmartItemActions.tsx +167 -0
  59. package/prefabs/admin-tools/scripts/tabs/TextAnnouncements.tsx +195 -0
  60. package/prefabs/admin-tools/scripts/tabs/VideoControl.tsx +156 -0
  61. package/prefabs/admin-tools/scripts/tabs/announcements/Overlay.tsx +94 -0
  62. package/prefabs/admin-tools/scripts/tabs/announcements/icons.ts +15 -0
  63. package/prefabs/admin-tools/scripts/tabs/announcements/state.ts +71 -0
  64. package/prefabs/admin-tools/scripts/tabs/announcements/ui.tsx +37 -0
  65. package/prefabs/admin-tools/scripts/tabs/moderation/AddUserInput.tsx +184 -0
  66. package/prefabs/admin-tools/scripts/tabs/moderation/RemoveAdminConfirmation.tsx +93 -0
  67. package/prefabs/admin-tools/scripts/tabs/moderation/UsersList.tsx +228 -0
  68. package/prefabs/admin-tools/scripts/tabs/moderation/api.ts +35 -0
  69. package/prefabs/admin-tools/scripts/tabs/moderation/icons.ts +23 -0
  70. package/prefabs/admin-tools/scripts/tabs/moderation/kick.ts +95 -0
  71. package/prefabs/admin-tools/scripts/tabs/moderation/state.ts +69 -0
  72. package/prefabs/admin-tools/scripts/tabs/moderation/ui.tsx +172 -0
  73. package/prefabs/admin-tools/scripts/tabs/moderation/utils.ts +88 -0
  74. package/prefabs/admin-tools/scripts/tabs/rewards/Button.tsx +52 -0
  75. package/prefabs/admin-tools/scripts/tabs/rewards/Captcha.tsx +88 -0
  76. package/prefabs/admin-tools/scripts/tabs/rewards/api.ts +138 -0
  77. package/prefabs/admin-tools/scripts/tabs/rewards/claim.ts +99 -0
  78. package/prefabs/admin-tools/scripts/tabs/rewards/controller.ts +103 -0
  79. package/prefabs/admin-tools/scripts/tabs/rewards/icons.ts +13 -0
  80. package/prefabs/admin-tools/scripts/tabs/rewards/state.ts +32 -0
  81. package/prefabs/admin-tools/scripts/tabs/smart-items/selection.ts +90 -0
  82. package/prefabs/admin-tools/scripts/tabs/video/DclCast.tsx +226 -0
  83. package/prefabs/admin-tools/scripts/tabs/video/LiveStream.tsx +284 -0
  84. package/prefabs/admin-tools/scripts/tabs/video/SpeakerShowcase.tsx +112 -0
  85. package/prefabs/admin-tools/scripts/tabs/video/VideoUrl.tsx +107 -0
  86. package/prefabs/admin-tools/scripts/tabs/video/Volume.tsx +69 -0
  87. package/prefabs/admin-tools/scripts/tabs/video/api.ts +172 -0
  88. package/prefabs/admin-tools/scripts/tabs/video/controls.ts +108 -0
  89. package/prefabs/admin-tools/scripts/tabs/video/icons.ts +59 -0
  90. package/prefabs/admin-tools/scripts/tabs/video/speakers.ts +43 -0
  91. package/prefabs/admin-tools/scripts/tabs/video/state.ts +82 -0
  92. package/prefabs/admin-tools/scripts/tabs/video/ui.tsx +243 -0
  93. package/prefabs/admin-tools/scripts/types.ts +46 -0
  94. package/prefabs/admin-tools/scripts/ui.tsx +122 -0
  95. package/prefabs/admin-tools/thumbnail.png +0 -0
  96. package/prefabs/announcer/ai.md +76 -0
  97. package/prefabs/announcer/composite.json +31 -0
  98. package/prefabs/announcer/data.json +35 -0
  99. package/prefabs/announcer/scripts/announcer.tsx +79 -0
  100. package/prefabs/announcer/scripts/pure/toast.ts +30 -0
  101. package/prefabs/announcer/thumbnail.png +0 -0
  102. package/prefabs/black-chair/HighBackChair.glb +0 -0
  103. package/prefabs/black-chair/composite.json +74 -0
  104. package/prefabs/black-chair/data.json +30 -0
  105. package/prefabs/black-chair/scripts/seat.ts +191 -0
  106. package/prefabs/black-chair/thumbnail.png +0 -0
  107. package/prefabs/catalog.json +1 -0
  108. package/prefabs/classic-bench/Bench_01.glb +0 -0
  109. package/prefabs/classic-bench/composite.json +100 -0
  110. package/prefabs/classic-bench/data.json +34 -0
  111. package/prefabs/classic-bench/file1.png +0 -0
  112. package/prefabs/classic-bench/scripts/seat.ts +191 -0
  113. package/prefabs/classic-bench/thumbnail.png +0 -0
  114. package/prefabs/classic-bench-armrests/Bench 1.glb +0 -0
  115. package/prefabs/classic-bench-armrests/composite.json +100 -0
  116. package/prefabs/classic-bench-armrests/data.json +30 -0
  117. package/prefabs/classic-bench-armrests/scripts/seat.ts +191 -0
  118. package/prefabs/classic-bench-armrests/thumbnail.png +0 -0
  119. package/prefabs/classroom-chair/Chair_02.glb +0 -0
  120. package/prefabs/classroom-chair/composite.json +74 -0
  121. package/prefabs/classroom-chair/data.json +34 -0
  122. package/prefabs/classroom-chair/file1.png +0 -0
  123. package/prefabs/classroom-chair/scripts/seat.ts +191 -0
  124. package/prefabs/classroom-chair/thumbnail.png +0 -0
  125. package/prefabs/court-chair/Chair_Wood_01.glb +0 -0
  126. package/prefabs/court-chair/FanstasyPack_TX.png +0 -0
  127. package/prefabs/court-chair/composite.json +74 -0
  128. package/prefabs/court-chair/data.json +34 -0
  129. package/prefabs/court-chair/scripts/seat.ts +191 -0
  130. package/prefabs/court-chair/thumbnail.png +0 -0
  131. package/prefabs/curved-couch/CurvedCouch.glb +0 -0
  132. package/prefabs/curved-couch/composite.json +100 -0
  133. package/prefabs/curved-couch/data.json +30 -0
  134. package/prefabs/curved-couch/scripts/seat.ts +191 -0
  135. package/prefabs/curved-couch/thumbnail.png +0 -0
  136. package/prefabs/game-flow/ai.md +94 -0
  137. package/prefabs/game-flow/composite.json +45 -0
  138. package/prefabs/game-flow/data.json +27 -0
  139. package/prefabs/game-flow/scripts/game-flow.ts +168 -0
  140. package/prefabs/game-flow/scripts/pure/flow.ts +185 -0
  141. package/prefabs/game-flow/thumbnail.png +0 -0
  142. package/prefabs/health-respawn/ai.md +76 -0
  143. package/prefabs/health-respawn/composite.json +31 -0
  144. package/prefabs/health-respawn/data.json +38 -0
  145. package/prefabs/health-respawn/scripts/health-respawn.ts +98 -0
  146. package/prefabs/health-respawn/scripts/health.ts +22 -0
  147. package/prefabs/health-respawn/scripts/pure/health.ts +60 -0
  148. package/prefabs/health-respawn/thumbnail.png +0 -0
  149. package/prefabs/high-stool/HighStool.glb +0 -0
  150. package/prefabs/high-stool/composite.json +74 -0
  151. package/prefabs/high-stool/data.json +30 -0
  152. package/prefabs/high-stool/scripts/seat.ts +191 -0
  153. package/prefabs/high-stool/thumbnail.png +0 -0
  154. package/prefabs/large-couch/ThreeSeater_Couch.glb +0 -0
  155. package/prefabs/large-couch/composite.json +100 -0
  156. package/prefabs/large-couch/data.json +30 -0
  157. package/prefabs/large-couch/scripts/seat.ts +191 -0
  158. package/prefabs/large-couch/thumbnail.png +0 -0
  159. package/prefabs/leaderboard/ai.md +75 -0
  160. package/prefabs/leaderboard/board-panel.glb +0 -0
  161. package/prefabs/leaderboard/composite.json +82 -0
  162. package/prefabs/leaderboard/data.json +35 -0
  163. package/prefabs/leaderboard/scripts/leaderboard.ts +66 -0
  164. package/prefabs/leaderboard/scripts/pure/board.ts +71 -0
  165. package/prefabs/leaderboard/thumbnail.png +0 -0
  166. package/prefabs/loveseat/Couch_01.glb +0 -0
  167. package/prefabs/loveseat/composite.json +100 -0
  168. package/prefabs/loveseat/data.json +34 -0
  169. package/prefabs/loveseat/file1.png +0 -0
  170. package/prefabs/loveseat/scripts/seat.ts +191 -0
  171. package/prefabs/loveseat/thumbnail.png +0 -0
  172. package/prefabs/modern-sofa/ModernSofa.glb +0 -0
  173. package/prefabs/modern-sofa/composite.json +100 -0
  174. package/prefabs/modern-sofa/data.json +30 -0
  175. package/prefabs/modern-sofa/scripts/seat.ts +191 -0
  176. package/prefabs/modern-sofa/thumbnail.png +0 -0
  177. package/prefabs/outdoor-chair/Chair_07.glb +0 -0
  178. package/prefabs/outdoor-chair/composite.json +74 -0
  179. package/prefabs/outdoor-chair/data.json +34 -0
  180. package/prefabs/outdoor-chair/file1.png +0 -0
  181. package/prefabs/outdoor-chair/scripts/seat.ts +191 -0
  182. package/prefabs/outdoor-chair/thumbnail.png +0 -0
  183. package/prefabs/rustic-bench/Bench_01.glb +0 -0
  184. package/prefabs/rustic-bench/PiratesPack_TX.png.png +0 -0
  185. package/prefabs/rustic-bench/composite.json +100 -0
  186. package/prefabs/rustic-bench/data.json +34 -0
  187. package/prefabs/rustic-bench/scripts/seat.ts +191 -0
  188. package/prefabs/rustic-bench/thumbnail.png +0 -0
  189. package/prefabs/rustic-round-stool/Chairwood_01.glb +0 -0
  190. package/prefabs/rustic-round-stool/PiratesPack_TX.png.png +0 -0
  191. package/prefabs/rustic-round-stool/composite.json +74 -0
  192. package/prefabs/rustic-round-stool/data.json +34 -0
  193. package/prefabs/rustic-round-stool/scripts/seat.ts +191 -0
  194. package/prefabs/rustic-round-stool/thumbnail.png +0 -0
  195. package/prefabs/rustic-square-stool/Chairwood_02.glb +0 -0
  196. package/prefabs/rustic-square-stool/PiratesPack_TX.png.png +0 -0
  197. package/prefabs/rustic-square-stool/composite.json +74 -0
  198. package/prefabs/rustic-square-stool/data.json +34 -0
  199. package/prefabs/rustic-square-stool/scripts/seat.ts +191 -0
  200. package/prefabs/rustic-square-stool/thumbnail.png +0 -0
  201. package/prefabs/server-clock/ai.md +67 -0
  202. package/prefabs/server-clock/composite.json +45 -0
  203. package/prefabs/server-clock/data.json +24 -0
  204. package/prefabs/server-clock/scripts/clock-hud.tsx +55 -0
  205. package/prefabs/server-clock/scripts/server-clock.ts +71 -0
  206. package/prefabs/server-clock/thumbnail.png +0 -0
  207. package/prefabs/simple-chair/Chair_03.glb +0 -0
  208. package/prefabs/simple-chair/composite.json +74 -0
  209. package/prefabs/simple-chair/data.json +34 -0
  210. package/prefabs/simple-chair/file1.png +0 -0
  211. package/prefabs/simple-chair/scripts/seat.ts +191 -0
  212. package/prefabs/simple-chair/thumbnail.png +0 -0
  213. package/prefabs/sit-spot/composite.json +45 -0
  214. package/prefabs/sit-spot/data.json +30 -0
  215. package/prefabs/sit-spot/scripts/seat.ts +191 -0
  216. package/prefabs/sit-spot/thumbnail.png +0 -0
  217. package/prefabs/sit-spot-edge/composite.json +42 -0
  218. package/prefabs/sit-spot-edge/data.json +32 -0
  219. package/prefabs/sit-spot-edge/emotes/Sit_Edge1_emote.glb +0 -0
  220. package/prefabs/sit-spot-edge/emotes/Sit_Edge2_emote.glb +0 -0
  221. package/prefabs/sit-spot-edge/scripts/seat.ts +191 -0
  222. package/prefabs/sit-spot-edge/thumbnail.png +0 -0
  223. package/prefabs/small-couch/TwoSeater_Couch.glb +0 -0
  224. package/prefabs/small-couch/composite.json +100 -0
  225. package/prefabs/small-couch/data.json +30 -0
  226. package/prefabs/small-couch/scripts/seat.ts +191 -0
  227. package/prefabs/small-couch/thumbnail.png +0 -0
  228. package/prefabs/spawner/ai.md +109 -0
  229. package/prefabs/spawner/composite.json +73 -0
  230. package/prefabs/spawner/data.json +22 -0
  231. package/prefabs/spawner/scripts/spawner.ts +380 -0
  232. package/prefabs/spawner/thumbnail.png +0 -0
  233. package/prefabs/steampunk-bench/Steampunk Bench.glb +0 -0
  234. package/prefabs/steampunk-bench/composite.json +100 -0
  235. package/prefabs/steampunk-bench/data.json +30 -0
  236. package/prefabs/steampunk-bench/scripts/seat.ts +191 -0
  237. package/prefabs/steampunk-bench/thumbnail.png +0 -0
  238. package/prefabs/steampunk-chair/Steampunk Chair.glb +0 -0
  239. package/prefabs/steampunk-chair/composite.json +74 -0
  240. package/prefabs/steampunk-chair/data.json +30 -0
  241. package/prefabs/steampunk-chair/scripts/seat.ts +191 -0
  242. package/prefabs/steampunk-chair/thumbnail.png +0 -0
  243. package/prefabs/tall-stool/Stool_D.glb +0 -0
  244. package/prefabs/tall-stool/composite.json +74 -0
  245. package/prefabs/tall-stool/data.json +30 -0
  246. package/prefabs/tall-stool/scripts/seat.ts +191 -0
  247. package/prefabs/tall-stool/thumbnail.png +0 -0
  248. package/prefabs/trigger-zone/ai.md +115 -0
  249. package/prefabs/trigger-zone/composite.json +52 -0
  250. package/prefabs/trigger-zone/data.json +31 -0
  251. package/prefabs/trigger-zone/scripts/trigger-zone.ts +89 -0
  252. package/prefabs/trigger-zone/thumbnail.png +0 -0
  253. package/prefabs/video-screen/composite.json +98 -0
  254. package/prefabs/video-screen/data.json +21 -0
  255. package/prefabs/video-screen/thumbnail.png +0 -0
  256. package/prefabs/video-screen/video_player.glb +0 -0
  257. package/prefabs/wooden-chair/Chair 1.glb +0 -0
  258. package/prefabs/wooden-chair/composite.json +74 -0
  259. package/prefabs/wooden-chair/data.json +30 -0
  260. package/prefabs/wooden-chair/scripts/seat.ts +191 -0
  261. package/prefabs/wooden-chair/thumbnail.png +0 -0
  262. package/prefabs/wooden-chair-armrests/Chair 3.glb +0 -0
  263. package/prefabs/wooden-chair-armrests/composite.json +74 -0
  264. package/prefabs/wooden-chair-armrests/data.json +30 -0
  265. package/prefabs/wooden-chair-armrests/scripts/seat.ts +191 -0
  266. package/prefabs/wooden-chair-armrests/thumbnail.png +0 -0
  267. package/scene/about +17 -0
  268. package/scene/bafkreias2w4xuqaic6sdcvblrisayi6xg7drfowzpt5ubjkmrk3mx6czc4 +1 -0
  269. package/scene/bafkreiby2gmthtauf7cckttphmhpecsueyypi2hzlke4lcodg7fm4cm2sq +23 -0
  270. package/scene/bafkreidhwqwajqk4nsuffef5fkdrgmp5ql6vnn2knpqsuqalyosv5o6lk4 +1 -0
  271. package/scene/bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku +0 -0
  272. package/scene/bafybeihw3xlbff4sf5sc3kvmlmlwf2visnty5lfymawlunzt5vrkp5fd7u +90 -0
  273. package/web-build/esbuild.wasm +0 -0
  274. package/web-build/index.js +3 -0
  275. package/web-build/sdk-snapshot.json +1 -0
  276. package/web-build/worker.js +701 -0
  277. package/README.md +0 -3
@@ -0,0 +1,99 @@
1
+ // The CLAIM_AIRDROP flow from @dcl/asset-packs src/actions.ts (~line 1544), as a
2
+ // standalone module: read the dispenser's asset-packs::Rewards config, look the
3
+ // campaign up by dispenser key, and either claim straight away or hand the caller
4
+ // a captcha to solve first. Test mode short-circuits, like the Hub's interpreter.
5
+ import type { Entity } from '@dcl/sdk/ecs'
6
+ import { getPlayer } from '@dcl/sdk/players'
7
+ import { loadRealm } from '../../api'
8
+ import { Rewards } from '../../components'
9
+ import {
10
+ claimReward,
11
+ fetchCampaigns,
12
+ fetchCaptcha,
13
+ isCaptchaRequired,
14
+ type RewardCaptcha
15
+ } from './api'
16
+
17
+ export const CLAIM_AIRDROP_ACTION = 'claim_airdrop'
18
+
19
+ export interface PendingCaptcha {
20
+ campaignId: string
21
+ dispenserKey: string
22
+ captcha: RewardCaptcha
23
+ }
24
+
25
+ export type ClaimOutcome =
26
+ | { kind: 'claimed'; message: string }
27
+ | { kind: 'captcha'; pending: PendingCaptcha }
28
+ | { kind: 'test-mode' }
29
+ | { kind: 'error'; message: string }
30
+
31
+ function failed(message: string): ClaimOutcome {
32
+ return { kind: 'error', message }
33
+ }
34
+
35
+ function beneficiary(): string {
36
+ const player = getPlayer()
37
+ if (player === null || player.isGuest) return ''
38
+ return player.userId.toLowerCase()
39
+ }
40
+
41
+ async function catalyst(): Promise<string> {
42
+ const realm = await loadRealm()
43
+ return realm?.baseUrl ?? ''
44
+ }
45
+
46
+ async function promptCaptcha(campaignId: string, dispenserKey: string): Promise<ClaimOutcome> {
47
+ const [error, captcha] = await fetchCaptcha()
48
+ if (error !== null) return failed(error)
49
+ return { kind: 'captcha', pending: { campaignId, dispenserKey, captcha } }
50
+ }
51
+
52
+ async function claim(
53
+ campaignId: string,
54
+ dispenserKey: string,
55
+ captcha?: { id: string; value: string }
56
+ ): Promise<ClaimOutcome> {
57
+ const [error, summary] = await claimReward({
58
+ dispenserKey,
59
+ beneficiary: beneficiary(),
60
+ catalyst: await catalyst(),
61
+ captchaId: captcha?.id,
62
+ captchaValue: captcha?.value
63
+ })
64
+
65
+ if (error !== null) {
66
+ // The dispenser may demand a captcha even when the campaign flag says
67
+ // otherwise — the newer Hub dispenser script reads the same signal.
68
+ if (captcha === undefined && isCaptchaRequired(error)) return promptCaptcha(campaignId, dispenserKey)
69
+ return failed(error)
70
+ }
71
+ const reward = summary.count === 1 ? 'reward' : 'rewards'
72
+ return { kind: 'claimed', message: `Airdrop released — ${summary.count} ${reward} ${summary.status}.` }
73
+ }
74
+
75
+ /** Runs the airdrop claim for a dispenser entity carrying asset-packs::Rewards. */
76
+ export async function releaseAirdrop(entity: Entity): Promise<ClaimOutcome> {
77
+ const rewards = Rewards.getOrNull(entity)
78
+ if (rewards === null) return failed('This item has no Rewards configuration.')
79
+
80
+ const { campaignId, dispenserKey, testMode } = rewards
81
+ if (testMode) return { kind: 'test-mode' }
82
+ if (dispenserKey === '') return failed('This item has no dispenser key set.')
83
+
84
+ const [error, campaigns] = await fetchCampaigns(dispenserKey)
85
+ if (error !== null) return failed(error)
86
+
87
+ const campaign = campaigns.find((entry) => entry.campaignId === campaignId)
88
+ if (campaign === undefined) return failed('No campaign matches this dispenser key.')
89
+ if (!campaign.enabled) return failed('This campaign is disabled on rewards.decentraland.org.')
90
+ if (campaign.requiresCaptcha) return promptCaptcha(campaignId, dispenserKey)
91
+
92
+ return claim(campaignId, dispenserKey)
93
+ }
94
+
95
+ export async function submitCaptcha(pending: PendingCaptcha, value: string): Promise<ClaimOutcome> {
96
+ const answer = value.trim()
97
+ if (answer === '') return failed('Enter the captcha to continue.')
98
+ return claim(pending.campaignId, pending.dispenserKey, { id: pending.captcha.id, value: answer })
99
+ }
@@ -0,0 +1,103 @@
1
+ // What the Release / Clear / Submit buttons do. Same contract as the Creator Hub's
2
+ // RewardsControl: Release runs the dispenser's "Airdrop" action and Clear runs its
3
+ // "Invisible" one. The names are the Hub's convention, so an action of the matching
4
+ // *type* is accepted as a fallback for items authored with different labels.
5
+ import type { Entity } from '@dcl/sdk/ecs'
6
+ import { ActionType, getActions, runAction } from '../../actions'
7
+ import type { ActionEntry, RewardItemRef } from '../../components'
8
+ import { Rewards } from '../../components'
9
+ import { CLAIM_AIRDROP_ACTION, releaseAirdrop, submitCaptcha, type ClaimOutcome } from './claim'
10
+ import { clearCaptcha, rewardsTabState, setStatus } from './state'
11
+
12
+ export const RELEASE_ACTION = 'Airdrop'
13
+ export const CLEAR_ACTION = 'Invisible'
14
+
15
+ export function selectedEntity(
16
+ items: readonly RewardItemRef[],
17
+ index: number | undefined
18
+ ): Entity | null {
19
+ if (index === undefined || index < 0 || index >= items.length) return null
20
+ return items[index].entity as Entity
21
+ }
22
+
23
+ function findAction(entity: Entity, name: string, type: string): ActionEntry | undefined {
24
+ const actions = getActions(entity)
25
+ return actions.find((action) => action.name === name) ?? actions.find((action) => action.type === type)
26
+ }
27
+
28
+ function describe(error: unknown): string {
29
+ return error instanceof Error ? error.message : String(error)
30
+ }
31
+
32
+ function applyOutcome(outcome: ClaimOutcome): void {
33
+ switch (outcome.kind) {
34
+ case 'claimed':
35
+ clearCaptcha()
36
+ setStatus(outcome.message, 'success')
37
+ break
38
+ case 'captcha':
39
+ rewardsTabState.pending = outcome.pending
40
+ rewardsTabState.answer = ''
41
+ setStatus('Solve the captcha to release this airdrop.')
42
+ break
43
+ case 'test-mode':
44
+ clearCaptcha()
45
+ setStatus('Test mode is on for this item — nothing was assigned.')
46
+ break
47
+ case 'error':
48
+ setStatus(outcome.message, 'error')
49
+ break
50
+ }
51
+ }
52
+
53
+ async function run(task: Promise<ClaimOutcome>): Promise<void> {
54
+ rewardsTabState.busy = true
55
+ try {
56
+ applyOutcome(await task)
57
+ } catch (error) {
58
+ setStatus(describe(error), 'error')
59
+ } finally {
60
+ rewardsTabState.busy = false
61
+ }
62
+ }
63
+
64
+ export function release(entity: Entity): void {
65
+ if (rewardsTabState.busy) return
66
+ const action = findAction(entity, RELEASE_ACTION, CLAIM_AIRDROP_ACTION)
67
+
68
+ if (action !== undefined && action.type !== CLAIM_AIRDROP_ACTION) {
69
+ runAction(entity, action)
70
+ clearCaptcha()
71
+ setStatus(`Ran "${action.name}" on this item.`, 'success')
72
+ return
73
+ }
74
+
75
+ clearCaptcha()
76
+ setStatus('Releasing airdrop…')
77
+ void run(releaseAirdrop(entity))
78
+ }
79
+
80
+ export function clear(entity: Entity): void {
81
+ if (rewardsTabState.busy) return
82
+ if (Rewards.getOrNull(entity) === null) {
83
+ setStatus('This item has no Rewards configuration.', 'error')
84
+ return
85
+ }
86
+
87
+ const action = findAction(entity, CLEAR_ACTION, ActionType.SET_VISIBILITY)
88
+ if (action === undefined) {
89
+ setStatus(`This item has no "${CLEAR_ACTION}" action to clear it with.`, 'error')
90
+ return
91
+ }
92
+
93
+ runAction(entity, action)
94
+ clearCaptcha()
95
+ setStatus('Airdrop cleared.', 'success')
96
+ }
97
+
98
+ export function submit(): void {
99
+ const pending = rewardsTabState.pending
100
+ if (pending === null || rewardsTabState.busy) return
101
+ setStatus('Checking the captcha…')
102
+ void run(submitCaptcha(pending, rewardsTabState.answer))
103
+ }
@@ -0,0 +1,13 @@
1
+ import { iconPath } from '../../icons'
2
+
3
+ export interface RewardsIcons {
4
+ send: string
5
+ close: string
6
+ }
7
+
8
+ export function rewardsIcons(base: string): RewardsIcons {
9
+ return {
10
+ send: iconPath(base, 'rewards/send.png'),
11
+ close: iconPath(base, 'rewards/close.png')
12
+ }
13
+ }
@@ -0,0 +1,32 @@
1
+ // Per-tab UI state. The shared AdminState only carries the selected airdrop, so
2
+ // the in-flight claim (busy flag, feedback line, captcha challenge) lives here —
3
+ // one panel per scene, so a module singleton is enough.
4
+ import type { PendingCaptcha } from './claim'
5
+
6
+ export type StatusTone = 'info' | 'error' | 'success'
7
+
8
+ export interface RewardsTabState {
9
+ busy: boolean
10
+ status: string
11
+ tone: StatusTone
12
+ pending: PendingCaptcha | null
13
+ answer: string
14
+ }
15
+
16
+ export const rewardsTabState: RewardsTabState = {
17
+ busy: false,
18
+ status: '',
19
+ tone: 'info',
20
+ pending: null,
21
+ answer: ''
22
+ }
23
+
24
+ export function setStatus(status: string, tone: StatusTone = 'info'): void {
25
+ rewardsTabState.status = status
26
+ rewardsTabState.tone = tone
27
+ }
28
+
29
+ export function clearCaptcha(): void {
30
+ rewardsTabState.pending = null
31
+ rewardsTabState.answer = ''
32
+ }
@@ -0,0 +1,90 @@
1
+ // Smart-item list building and per-item panel state.
2
+ //
3
+ // `linkAllSmartItems` is a plain flag in the inspector — nothing populates the
4
+ // list from it — so it is honoured here: with it on, every entity carrying
5
+ // asset-packs::Actions joins the dropdown, named after core-schema::Name.
6
+ import { engine, Name, VisibilityComponent, type Entity } from '@dcl/sdk/ecs'
7
+ import { getActions } from '../../actions'
8
+ import { Actions, type AdminToolsValue } from '../../components'
9
+ import type { AdminState, SmartItemUiState } from '../../state'
10
+
11
+ export interface SmartItemEntry {
12
+ entity: Entity
13
+ customName: string
14
+ defaultAction: string
15
+ }
16
+
17
+ export interface SmartItemsUiState {
18
+ lastPlayed: string
19
+ lastPlayedFailed: boolean
20
+ }
21
+
22
+ export const smartItemsUi: SmartItemsUiState = { lastPlayed: '', lastPlayedFailed: false }
23
+
24
+ function labelFor(entity: Entity): string {
25
+ const name = Name.getOrNull(entity)
26
+ return name !== null && name.value !== '' ? name.value : `Entity ${entity as number}`
27
+ }
28
+
29
+ export function smartItemList(config: AdminToolsValue, self: Entity): SmartItemEntry[] {
30
+ const entries: SmartItemEntry[] = []
31
+ const seen = new Set<number>()
32
+
33
+ for (const item of config.smartItemsControl.smartItems ?? []) {
34
+ if (item.entity === 0 || seen.has(item.entity)) continue
35
+ seen.add(item.entity)
36
+ const entity = item.entity as Entity
37
+ entries.push({
38
+ entity,
39
+ customName: item.customName === '' ? labelFor(entity) : item.customName,
40
+ defaultAction: item.defaultAction
41
+ })
42
+ }
43
+
44
+ if (!config.smartItemsControl.linkAllSmartItems) return entries
45
+
46
+ const discovered: SmartItemEntry[] = []
47
+ for (const [entity] of engine.getEntitiesWith(Actions)) {
48
+ if (entity === self || seen.has(entity as number)) continue
49
+ seen.add(entity as number)
50
+ discovered.push({
51
+ entity,
52
+ customName: labelFor(entity),
53
+ defaultAction: getActions(entity)[0]?.name ?? ''
54
+ })
55
+ }
56
+ discovered.sort((a, b) => (a.entity as number) - (b.entity as number))
57
+ return [...entries, ...discovered]
58
+ }
59
+
60
+ export function uiStateFor(state: AdminState, item: SmartItemEntry): SmartItemUiState {
61
+ const existing = state.smartItems.byEntity.get(item.entity)
62
+ if (existing !== undefined) return existing
63
+ const created: SmartItemUiState = {
64
+ visible: VisibilityComponent.getOrNull(item.entity)?.visible ?? true,
65
+ selectedAction: item.defaultAction
66
+ }
67
+ state.smartItems.byEntity.set(item.entity, created)
68
+ return created
69
+ }
70
+
71
+ export function selectedItem(state: AdminState, items: SmartItemEntry[]): SmartItemEntry | undefined {
72
+ const index = state.smartItems.selectedIndex
73
+ if (index === undefined || index < 0 || index >= items.length) return undefined
74
+ return items[index]
75
+ }
76
+
77
+ export function selectSmartItem(state: AdminState, items: SmartItemEntry[], index: number): void {
78
+ if (index < 0 || index >= items.length) return
79
+ state.smartItems.selectedIndex = index
80
+ uiStateFor(state, items[index])
81
+ smartItemsUi.lastPlayed = ''
82
+ smartItemsUi.lastPlayedFailed = false
83
+ }
84
+
85
+ export function setVisible(state: AdminState, item: SmartItemEntry, visible: boolean): void {
86
+ uiStateFor(state, item).visible = visible
87
+ const existing = VisibilityComponent.getMutableOrNull(item.entity)
88
+ if (existing === null) VisibilityComponent.create(item.entity, { visible })
89
+ else existing.visible = visible
90
+ }
@@ -0,0 +1,226 @@
1
+ import ReactEcs, { Label, UiEntity } from '@dcl/sdk/react-ecs'
2
+ import { VideoPlayer, type Entity } from '@dcl/sdk/ecs'
3
+ import { copyToClipboard } from '~system/RestrictedActions'
4
+ import { VideoControlState } from '../../components'
5
+ import { ensurePresenterRole, getCastRoom, resetStreamKey } from './api'
6
+ import { LIVEKIT_STREAM_SRC, type VideoControls } from './controls'
7
+ import type { VideoIcons } from './icons'
8
+ import { ensureSpeakerPolling, refreshSpeakers } from './speakers'
9
+ import { markCopied, videoTab, wasCopied } from './state'
10
+ import { SpeakerShowcase } from './SpeakerShowcase'
11
+ import { Volume } from './Volume'
12
+ import { Button, COLORS, Column, Divider, ErrorNote, Hint, Row, SubHeader } from './ui'
13
+
14
+ export function DclCast(props: {
15
+ self: Entity
16
+ entity: Entity
17
+ icons: VideoIcons
18
+ controls: VideoControls
19
+ soundDisabled: boolean
20
+ playerAddress: string | undefined
21
+ active: boolean
22
+ }): ReactEcs.JSX.Element {
23
+ const [loading, setLoading] = ReactEcs.useState(videoTab.castRoom === undefined)
24
+
25
+ const fetchRoom = (): void => {
26
+ setLoading(true)
27
+ videoTab.castError = ''
28
+ void (async () => {
29
+ const [error, room] = await getCastRoom()
30
+ if (error !== null) videoTab.castError = error
31
+ else videoTab.castRoom = room
32
+ setLoading(false)
33
+ })()
34
+ }
35
+
36
+ ReactEcs.useEffect(() => {
37
+ ensureSpeakerPolling()
38
+ if (videoTab.castRoom === undefined) fetchRoom()
39
+ else setLoading(false)
40
+ if (props.playerAddress !== undefined) void ensurePresenterRole(props.playerAddress)
41
+ }, [])
42
+
43
+ const room = videoTab.castRoom
44
+ const showSpeakers = videoTab.showSpeakers
45
+
46
+ return (
47
+ <Column>
48
+ <Row uiTransform={{ justifyContent: 'space-between' }}>
49
+ <SubHeader icon={props.icons.sourceCast} title="DCL Cast" />
50
+ <UiEntity
51
+ uiTransform={{ width: 20, height: 20, display: props.active ? 'flex' : 'none' }}
52
+ uiBackground={{
53
+ textureMode: 'stretch',
54
+ texture: { src: props.icons.star },
55
+ color: COLORS.success
56
+ }}
57
+ />
58
+ </Row>
59
+ <Hint text="Use a browser-based DCL Cast room to stream camera and screen feeds onto this screen." />
60
+
61
+ {loading ? <Hint text="Loading room…" uiTransform={{ margin: { top: 16 } }} /> : null}
62
+
63
+ {!loading && videoTab.castError !== '' ? (
64
+ <Column uiTransform={{ margin: { top: 12 } }}>
65
+ <ErrorNote icon={props.icons.error} text={videoTab.castError} />
66
+ <Row uiTransform={{ margin: { top: 8 } }}>
67
+ <Button label="Retry" uiTransform={{ width: 'auto' }} onClick={fetchRoom} />
68
+ </Row>
69
+ </Column>
70
+ ) : null}
71
+
72
+ {!loading && room !== undefined ? (
73
+ <Column uiTransform={{ margin: { top: 12 } }}>
74
+ <Row uiTransform={{ justifyContent: 'space-between' }}>
75
+ <Column uiTransform={{ width: 'auto' }}>
76
+ <Label
77
+ value="<b>Room ID</b>"
78
+ fontSize={20}
79
+ color={COLORS.white}
80
+ textAlign="middle-left"
81
+ uiTransform={{ height: 26 }}
82
+ />
83
+ <Hint text={`Expires in ${room.expiresInDays} days`} />
84
+ </Column>
85
+ {props.active ? (
86
+ <Button
87
+ label="Deactivate"
88
+ variant="text"
89
+ uiTransform={{ width: 'auto' }}
90
+ onClick={() => {
91
+ props.controls.setSource('')
92
+ videoTab.activeTrackSid = undefined
93
+ }}
94
+ />
95
+ ) : (
96
+ <Button
97
+ label="Activate"
98
+ variant="success"
99
+ uiTransform={{ width: 'auto' }}
100
+ onClick={() => {
101
+ props.controls.setSource(LIVEKIT_STREAM_SRC)
102
+ videoTab.activeTrackSid = undefined
103
+ videoTab.source = 'dcl-cast'
104
+ }}
105
+ />
106
+ )}
107
+ </Row>
108
+
109
+ <Divider />
110
+
111
+ <Row uiTransform={{ justifyContent: 'space-between' }}>
112
+ <Column uiTransform={{ width: '62%' }}>
113
+ <Label
114
+ value="<b>Cast speakers</b>"
115
+ fontSize={16}
116
+ color={COLORS.white}
117
+ textAlign="middle-left"
118
+ uiTransform={{ height: 22 }}
119
+ />
120
+ <Hint text="This link grants streaming access." />
121
+ </Column>
122
+ <Button
123
+ label={wasCopied('stream-link') ? 'Copied!' : 'Copy Link'}
124
+ variant="text"
125
+ iconRight={props.icons.copy}
126
+ uiTransform={{ width: 'auto' }}
127
+ onClick={() => {
128
+ if (room.streamLink === '') return
129
+ void copyToClipboard({ text: room.streamLink })
130
+ markCopied('stream-link')
131
+ }}
132
+ />
133
+ </Row>
134
+
135
+ <Divider />
136
+
137
+ <Row uiTransform={{ justifyContent: 'space-between' }}>
138
+ <Column uiTransform={{ width: '62%' }}>
139
+ <Label
140
+ value="<b>Viewers</b>"
141
+ fontSize={16}
142
+ color={COLORS.white}
143
+ textAlign="middle-left"
144
+ uiTransform={{ height: 22 }}
145
+ />
146
+ <Hint text="This link grants viewing access." />
147
+ </Column>
148
+ <Button
149
+ label={wasCopied('watcher-link') ? 'Copied!' : 'Copy Link'}
150
+ variant="text"
151
+ iconRight={props.icons.copy}
152
+ uiTransform={{ width: 'auto' }}
153
+ onClick={() => {
154
+ if (room.watcherLink === '') return
155
+ void copyToClipboard({ text: room.watcherLink })
156
+ markCopied('watcher-link')
157
+ }}
158
+ />
159
+ </Row>
160
+
161
+ <Volume
162
+ label="Cast controls"
163
+ entity={props.entity}
164
+ icons={props.icons}
165
+ controls={props.controls}
166
+ soundDisabled={props.soundDisabled}
167
+ />
168
+
169
+ <Row uiTransform={{ margin: { top: 16 }, justifyContent: 'space-between' }}>
170
+ <Button
171
+ label={showSpeakers ? 'Hide Speakers' : 'Speakers'}
172
+ icon={props.icons.star}
173
+ iconRight={showSpeakers ? props.icons.chevronUp : props.icons.chevronDown}
174
+ disabled={!props.active}
175
+ uiTransform={{ width: 'auto' }}
176
+ onClick={() => {
177
+ videoTab.showSpeakers = !showSpeakers
178
+ if (!showSpeakers) refreshSpeakers()
179
+ }}
180
+ />
181
+ <Button
182
+ label="Reset Room"
183
+ variant="danger"
184
+ uiTransform={{ width: 'auto' }}
185
+ onClick={() => {
186
+ setLoading(true)
187
+ void (async () => {
188
+ const [error, data] = await resetStreamKey()
189
+ if (error !== null) {
190
+ videoTab.castError = error
191
+ setLoading(false)
192
+ return
193
+ }
194
+ const state = VideoControlState.getMutableOrNull(props.self)
195
+ if (state !== null) state.endsAt = data.endsAt
196
+ videoTab.castRoom = undefined
197
+ videoTab.activeTrackSid = undefined
198
+ fetchRoom()
199
+ })()
200
+ }}
201
+ />
202
+ </Row>
203
+
204
+ {showSpeakers && props.active ? (
205
+ <SpeakerShowcase
206
+ icons={props.icons}
207
+ onSelectTrack={(track) => {
208
+ props.controls.setSource(track.sid)
209
+ videoTab.activeTrackSid = track.sid
210
+ videoTab.source = 'dcl-cast'
211
+ }}
212
+ onAutomatic={() => {
213
+ props.controls.setSource(LIVEKIT_STREAM_SRC)
214
+ videoTab.activeTrackSid = undefined
215
+ }}
216
+ />
217
+ ) : null}
218
+ </Column>
219
+ ) : null}
220
+
221
+ {VideoPlayer.getOrNull(props.entity) === null ? (
222
+ <ErrorNote icon={props.icons.error} text="This screen has no VideoPlayer component." />
223
+ ) : null}
224
+ </Column>
225
+ )
226
+ }