@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,69 @@
1
+ // View state for the moderation tab. It lives at module scope rather than in
2
+ // ReactEcs.useState because the shell unmounts the tab body on every tab switch
3
+ // (state.ts nextTick trick) — a list the admin paged into would otherwise reset.
4
+ // Same reason the Creator Hub keeps `moderationControlState` outside the tree.
5
+ import type { SceneAdmin } from '../../api'
6
+
7
+ export enum ModerationView {
8
+ MAIN = 'main',
9
+ ADMIN_LIST = 'admin_list',
10
+ BAN_LIST = 'ban_list',
11
+ CONFIRM_REMOVE = 'confirm_remove'
12
+ }
13
+
14
+ export const USERS_PER_PAGE = 4
15
+ const MESSAGE_MS = 3000
16
+
17
+ export interface ModerationViewState {
18
+ view: ModerationView
19
+ page: number
20
+ adminToRemove: SceneAdmin | null
21
+ removeError: string
22
+ removing: boolean
23
+ message: string
24
+ messageAt: number
25
+ }
26
+
27
+ export const moderationView: ModerationViewState = {
28
+ view: ModerationView.MAIN,
29
+ page: 1,
30
+ adminToRemove: null,
31
+ removeError: '',
32
+ removing: false,
33
+ message: '',
34
+ messageAt: 0
35
+ }
36
+
37
+ export function openView(view: ModerationView): void {
38
+ moderationView.view = view
39
+ moderationView.page = 1
40
+ moderationView.adminToRemove = null
41
+ moderationView.removeError = ''
42
+ moderationView.removing = false
43
+ }
44
+
45
+ export function showMessage(text: string): void {
46
+ moderationView.message = text
47
+ moderationView.messageAt = Date.now()
48
+ }
49
+
50
+ // Read during render, so the toast disappears on its own without a timer system.
51
+ export function currentMessage(): string {
52
+ if (moderationView.message === '') return ''
53
+ if (Date.now() - moderationView.messageAt > MESSAGE_MS) {
54
+ moderationView.message = ''
55
+ return ''
56
+ }
57
+ return moderationView.message
58
+ }
59
+
60
+ export function pageCount(total: number): number {
61
+ return Math.max(1, Math.ceil(total / USERS_PER_PAGE))
62
+ }
63
+
64
+ export function clampPage(total: number): number {
65
+ const pages = pageCount(total)
66
+ if (moderationView.page > pages) moderationView.page = pages
67
+ if (moderationView.page < 1) moderationView.page = 1
68
+ return moderationView.page
69
+ }
@@ -0,0 +1,172 @@
1
+ import ReactEcs, { Label, UiEntity } from '@dcl/sdk/react-ecs'
2
+ import type { Key, UiTransformProps } from '@dcl/sdk/react-ecs'
3
+ import { Color4 } from '@dcl/sdk/math'
4
+
5
+ export const WHITE = Color4.White()
6
+ export const BLACK = Color4.Black()
7
+ export const GRAY = Color4.Gray()
8
+ export const ADDRESS_GRAY = Color4.fromHexString('#716B7C')
9
+ export const DIVIDER_GRAY = Color4.fromHexString('#43404A')
10
+ export const DISABLED_GRAY = Color4.fromHexString('#323232')
11
+ export const BADGE_GRAY = Color4.fromHexString('#A09BA8')
12
+ export const DANGER = Color4.fromHexString('#FF2D55')
13
+ export const BAN_PINK = Color4.fromHexString('#FB3B3B')
14
+ export const RED = Color4.Red()
15
+
16
+ export type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'text'
17
+
18
+ export function Divider(props: { uiTransform?: UiTransformProps }): ReactEcs.JSX.Element {
19
+ return (
20
+ <UiEntity
21
+ uiTransform={{
22
+ width: '100%',
23
+ height: 1,
24
+ flexShrink: 0,
25
+ margin: { top: 14, bottom: 14 },
26
+ ...props.uiTransform
27
+ }}
28
+ uiBackground={{ color: DIVIDER_GRAY }}
29
+ />
30
+ )
31
+ }
32
+
33
+ export function SectionTitle(props: { text: string }): ReactEcs.JSX.Element {
34
+ return (
35
+ <Label
36
+ value={`<b>${props.text}</b>`}
37
+ fontSize={17}
38
+ color={WHITE}
39
+ textAlign="middle-left"
40
+ uiTransform={{ height: 'auto', margin: { bottom: 8 } }}
41
+ />
42
+ )
43
+ }
44
+
45
+ export function ErrorLine(props: {
46
+ icon: string
47
+ text: string
48
+ uiTransform?: UiTransformProps
49
+ }): ReactEcs.JSX.Element {
50
+ return (
51
+ <UiEntity
52
+ uiTransform={{
53
+ width: '100%',
54
+ height: 'auto',
55
+ flexDirection: 'row',
56
+ alignItems: 'center',
57
+ margin: { top: 6 },
58
+ ...props.uiTransform
59
+ }}
60
+ >
61
+ <UiEntity
62
+ uiTransform={{ width: 16, height: 16, flexShrink: 0, margin: { right: 8 } }}
63
+ uiBackground={{ textureMode: 'stretch', texture: { src: props.icon } }}
64
+ />
65
+ <UiEntity
66
+ uiTransform={{ width: '90%', height: 'auto' }}
67
+ uiText={{ value: props.text, fontSize: 13, color: RED, textAlign: 'top-left' }}
68
+ />
69
+ </UiEntity>
70
+ )
71
+ }
72
+
73
+ function backgroundColor(variant: ButtonVariant, disabled: boolean): Color4 {
74
+ if (disabled) return variant === 'primary' ? DISABLED_GRAY : Color4.create(0, 0, 0, 0)
75
+ if (variant === 'primary') return WHITE
76
+ if (variant === 'danger') return BAN_PINK
77
+ return Color4.create(0, 0, 0, 0)
78
+ }
79
+
80
+ function labelColor(variant: ButtonVariant, disabled: boolean): Color4 {
81
+ if (disabled) return DISABLED_GRAY
82
+ if (variant === 'primary') return BLACK
83
+ return WHITE
84
+ }
85
+
86
+ export function Button(props: {
87
+ key?: Key
88
+ label: string
89
+ variant?: ButtonVariant
90
+ disabled?: boolean
91
+ icon?: string
92
+ iconRight?: string
93
+ color?: Color4
94
+ fontSize?: number
95
+ onClick: () => void
96
+ uiTransform?: UiTransformProps
97
+ }): ReactEcs.JSX.Element {
98
+ const variant = props.variant ?? 'primary'
99
+ const disabled = props.disabled === true
100
+ const color = disabled ? DISABLED_GRAY : (props.color ?? labelColor(variant, disabled))
101
+ return (
102
+ <UiEntity
103
+ uiTransform={{
104
+ height: 38,
105
+ flexDirection: 'row',
106
+ alignItems: 'center',
107
+ justifyContent: 'center',
108
+ borderRadius: 10,
109
+ borderWidth: variant === 'secondary' ? 2 : 0,
110
+ borderColor: disabled ? DISABLED_GRAY : WHITE,
111
+ padding: { left: 12, right: 12 },
112
+ ...props.uiTransform
113
+ }}
114
+ uiBackground={{ color: backgroundColor(variant, disabled) }}
115
+ onMouseDown={() => {
116
+ if (!disabled) props.onClick()
117
+ }}
118
+ >
119
+ {props.icon === undefined ? null : (
120
+ <UiEntity
121
+ uiTransform={{ width: 20, height: 20, flexShrink: 0, margin: { right: 8 } }}
122
+ uiBackground={{ textureMode: 'stretch', texture: { src: props.icon }, color }}
123
+ />
124
+ )}
125
+ <Label value={`<b>${props.label}</b>`} fontSize={props.fontSize ?? 15} color={color} />
126
+ {props.iconRight === undefined ? null : (
127
+ <UiEntity
128
+ uiTransform={{ width: 20, height: 20, flexShrink: 0, margin: { left: 8 } }}
129
+ uiBackground={{ textureMode: 'stretch', texture: { src: props.iconRight }, color }}
130
+ />
131
+ )}
132
+ </UiEntity>
133
+ )
134
+ }
135
+
136
+ export function ListHeader(props: {
137
+ icon: string
138
+ title: string
139
+ counter: string
140
+ closeIcon: string
141
+ onClose: () => void
142
+ }): ReactEcs.JSX.Element {
143
+ return (
144
+ <UiEntity
145
+ uiTransform={{
146
+ width: '100%',
147
+ height: 32,
148
+ flexDirection: 'row',
149
+ alignItems: 'center',
150
+ margin: { bottom: 10 }
151
+ }}
152
+ >
153
+ <UiEntity
154
+ uiTransform={{ width: 26, height: 26, flexShrink: 0, margin: { right: 8 } }}
155
+ uiBackground={{ textureMode: 'stretch', texture: { src: props.icon }, color: WHITE }}
156
+ />
157
+ <Label value={`<b>${props.title}</b>`} fontSize={19} color={WHITE} />
158
+ <Label
159
+ value={props.counter}
160
+ fontSize={14}
161
+ color={GRAY}
162
+ uiTransform={{ flexGrow: 1, margin: { left: 8 } }}
163
+ textAlign="middle-left"
164
+ />
165
+ <UiEntity
166
+ uiTransform={{ width: 26, height: 26, flexShrink: 0 }}
167
+ uiBackground={{ textureMode: 'stretch', texture: { src: props.closeIcon }, color: WHITE }}
168
+ onMouseDown={props.onClose}
169
+ />
170
+ </UiEntity>
171
+ )
172
+ }
@@ -0,0 +1,88 @@
1
+ // Pure-ish helpers behind the moderation tab: input validation, the two list
2
+ // refreshes and the permission rule for `allowNonOwnersManageAdminAllowList`.
3
+ import {
4
+ getSceneAdmins,
5
+ getSceneBans,
6
+ isPreview,
7
+ toSceneAdmins,
8
+ type SceneAdmin,
9
+ type SceneBanUser
10
+ } from '../../api'
11
+ import type { AdminToolsValue } from '../../components'
12
+ import type { AdminMessageBus } from '../../message-bus'
13
+ import type { AdminState } from '../../state'
14
+ import type { AdminPlayer } from '../../types'
15
+
16
+ const ADDRESS = /^0x[a-fA-F0-9]{40}$/
17
+
18
+ export function isValidAddress(value: string): boolean {
19
+ return ADDRESS.test(value)
20
+ }
21
+
22
+ // A Decentraland NAME is at most 15 characters, so anything longer was meant to be
23
+ // a wallet address — that is how the Hub decides which request shape to send.
24
+ export function looksLikeAddress(value: string): boolean {
25
+ return value.length > 15
26
+ }
27
+
28
+ export function shortAddress(address: string): string {
29
+ if (address.length <= 12) return address
30
+ return `${address.slice(0, 6)}...${address.slice(-4)}`
31
+ }
32
+
33
+ export function addressOf(user: SceneAdmin | SceneBanUser): string {
34
+ return 'address' in user ? user.address : user.bannedAddress
35
+ }
36
+
37
+ // The gatekeeper omits `name` for wallets with no NAME, so this never trusts it.
38
+ export function userName(user: SceneAdmin | SceneBanUser): string {
39
+ return typeof user.name === 'string' ? user.name : ''
40
+ }
41
+
42
+ export function displayName(user: SceneAdmin | SceneBanUser): string {
43
+ const name = userName(user)
44
+ return name === '' ? shortAddress(addressOf(user)) : name
45
+ }
46
+
47
+ export function isAdminUser(admins: SceneAdmin[], value: string): boolean {
48
+ const lower = value.toLowerCase()
49
+ return admins.some(
50
+ (admin) => admin.address === lower || (lower !== '' && userName(admin).toLowerCase() === lower)
51
+ )
52
+ }
53
+
54
+ // The gatekeeper marks scene owners and operators as non-removable, which is the
55
+ // only signal the /scene-admin response carries about who is more than an admin.
56
+ export function isSceneOwner(admins: SceneAdmin[], address: string | undefined): boolean {
57
+ if (address === undefined) return false
58
+ const lower = address.toLowerCase()
59
+ return admins.some((admin) => admin.address === lower && !admin.canBeRemoved)
60
+ }
61
+
62
+ export function canManageAdmins(
63
+ config: AdminToolsValue,
64
+ admins: SceneAdmin[],
65
+ player: AdminPlayer | null
66
+ ): boolean {
67
+ if (config.moderationControl.allowNonOwnersManageAdminAllowList) return true
68
+ if (isPreview()) return true
69
+ return isSceneOwner(admins, player?.userId)
70
+ }
71
+
72
+ // `broadcast` tells the other clients to refetch too — only worth the comms message
73
+ // after the list actually changed, which is how the Hub splits its two fetchers.
74
+ export async function refreshAdmins(
75
+ state: AdminState,
76
+ bus: AdminMessageBus | null,
77
+ broadcast = false
78
+ ): Promise<void> {
79
+ const [error, response] = await getSceneAdmins()
80
+ state.admins = error === null ? toSceneAdmins(response) : []
81
+ bus?.updateAdminList(state.admins)
82
+ if (broadcast) bus?.syncAdmins()
83
+ }
84
+
85
+ export async function refreshBans(state: AdminState): Promise<void> {
86
+ const [error, response] = await getSceneBans()
87
+ state.bans = error === null && Array.isArray(response.results) ? response.results : []
88
+ }
@@ -0,0 +1,52 @@
1
+ import ReactEcs, { Label, UiEntity } from '@dcl/sdk/react-ecs'
2
+ import type { UiTransformProps } from '@dcl/sdk/react-ecs'
3
+ import { Color4 } from '@dcl/sdk/math'
4
+
5
+ const ACTIVE = Color4.create(252 / 255, 252 / 255, 252 / 255, 1)
6
+ const DISABLED = Color4.create(74 / 255, 74 / 255, 74 / 255, 1)
7
+ const DISABLED_TEXT = Color4.create(160 / 255, 155 / 255, 168 / 255, 1)
8
+
9
+ export function RewardsButton(props: {
10
+ label: string
11
+ icon?: string
12
+ disabled?: boolean
13
+ onClick: () => void
14
+ uiTransform?: UiTransformProps
15
+ }): ReactEcs.JSX.Element {
16
+ const disabled = props.disabled === true
17
+ return (
18
+ <UiEntity
19
+ uiTransform={{
20
+ height: 40,
21
+ flexDirection: 'row',
22
+ alignItems: 'center',
23
+ justifyContent: 'center',
24
+ borderRadius: 12,
25
+ borderWidth: 0,
26
+ padding: { left: 16, right: 16 },
27
+ margin: { right: 16 },
28
+ ...props.uiTransform
29
+ }}
30
+ uiBackground={{ color: disabled ? DISABLED : ACTIVE }}
31
+ onMouseDown={() => {
32
+ if (!disabled) props.onClick()
33
+ }}
34
+ >
35
+ {props.icon !== undefined ? (
36
+ <UiEntity
37
+ uiTransform={{ width: 25, height: 25, margin: { right: 8 } }}
38
+ uiBackground={{
39
+ textureMode: 'stretch',
40
+ texture: { src: props.icon },
41
+ color: disabled ? DISABLED_TEXT : Color4.Black()
42
+ }}
43
+ />
44
+ ) : null}
45
+ <Label
46
+ value={`<b>${props.label}</b>`}
47
+ fontSize={16}
48
+ color={disabled ? DISABLED_TEXT : Color4.Black()}
49
+ />
50
+ </UiEntity>
51
+ )
52
+ }
@@ -0,0 +1,88 @@
1
+ import ReactEcs, { Input, Label, UiEntity } from '@dcl/sdk/react-ecs'
2
+ import { Color4 } from '@dcl/sdk/math'
3
+ import { TEXT_DIM } from '../../ui'
4
+ import { RewardsButton } from './Button'
5
+ import type { RewardCaptcha } from './api'
6
+
7
+ export function CaptchaPrompt(props: {
8
+ captcha: RewardCaptcha
9
+ answer: string
10
+ busy: boolean
11
+ closeIcon: string
12
+ onChange: (value: string) => void
13
+ onSubmit: () => void
14
+ onClose: () => void
15
+ }): ReactEcs.JSX.Element {
16
+ return (
17
+ <UiEntity uiTransform={{ width: '100%', flexDirection: 'column' }}>
18
+ <UiEntity
19
+ uiTransform={{
20
+ width: '100%',
21
+ flexDirection: 'row',
22
+ alignItems: 'center',
23
+ margin: { bottom: 16 }
24
+ }}
25
+ >
26
+ <Label
27
+ value="<b>Solve the captcha</b>"
28
+ fontSize={16}
29
+ color={Color4.White()}
30
+ textAlign="middle-left"
31
+ uiTransform={{ flexGrow: 1 }}
32
+ />
33
+ <UiEntity
34
+ uiTransform={{ width: 24, height: 24 }}
35
+ uiBackground={{
36
+ textureMode: 'stretch',
37
+ texture: { src: props.closeIcon },
38
+ color: Color4.White()
39
+ }}
40
+ onMouseDown={props.onClose}
41
+ />
42
+ </UiEntity>
43
+
44
+ <UiEntity
45
+ uiTransform={{
46
+ width: '100%',
47
+ height: 120,
48
+ margin: { bottom: 16 },
49
+ borderRadius: 8
50
+ }}
51
+ uiBackground={{
52
+ color: Color4.White(),
53
+ textureMode: 'stretch',
54
+ texture: { src: props.captcha.image }
55
+ }}
56
+ />
57
+
58
+ <Input
59
+ key={`captcha-${props.captcha.id}`}
60
+ placeholder="Enter the captcha"
61
+ value={props.answer}
62
+ fontSize={16}
63
+ textAlign="middle-left"
64
+ color={Color4.Black()}
65
+ placeholderColor={Color4.create(0.4, 0.4, 0.4, 1)}
66
+ uiTransform={{ width: '100%', height: 40, margin: { bottom: 16 } }}
67
+ uiBackground={{ color: Color4.White() }}
68
+ onChange={props.onChange}
69
+ onSubmit={props.onSubmit}
70
+ />
71
+
72
+ <UiEntity uiTransform={{ flexDirection: 'row' }}>
73
+ <RewardsButton
74
+ label={props.busy ? 'Sending…' : 'Submit'}
75
+ disabled={props.busy}
76
+ onClick={props.onSubmit}
77
+ />
78
+ <Label
79
+ value="The reward is assigned once the captcha is accepted."
80
+ fontSize={12}
81
+ color={TEXT_DIM}
82
+ textAlign="middle-left"
83
+ uiTransform={{ width: 220, height: 'auto' }}
84
+ />
85
+ </UiEntity>
86
+ </UiEntity>
87
+ )
88
+ }
@@ -0,0 +1,138 @@
1
+ // rewards.decentraland.<domain> calls behind the CLAIM_AIRDROP flow, ported from
2
+ // @dcl/asset-packs src/actions.ts (fetchCampaignsByDispenserKey / fetchCaptcha /
3
+ // requestToken). Every response comes wrapped in an `{ ok, data }` envelope that
4
+ // the shared signedFetch helper does not unwrap, so it is narrowed here.
5
+ import { ENDPOINTS, request, type Result } from '../../api'
6
+
7
+ export interface RewardCampaign {
8
+ campaignId: string
9
+ enabled: boolean
10
+ requiresCaptcha: boolean
11
+ }
12
+
13
+ export interface RewardCaptcha {
14
+ id: string
15
+ image: string
16
+ width: number
17
+ height: number
18
+ }
19
+
20
+ export interface ClaimSummary {
21
+ status: string
22
+ count: number
23
+ }
24
+
25
+ export interface ClaimRequest {
26
+ dispenserKey: string
27
+ beneficiary: string
28
+ catalyst: string
29
+ captchaId?: string
30
+ captchaValue?: string
31
+ }
32
+
33
+ const JSON_HEADERS = { 'Content-Type': 'application/json' }
34
+
35
+ function asRecord(value: unknown): Record<string, unknown> {
36
+ return typeof value === 'object' && value !== null ? (value as Record<string, unknown>) : {}
37
+ }
38
+
39
+ function asString(value: unknown, fallback = ''): string {
40
+ return typeof value === 'string' ? value : fallback
41
+ }
42
+
43
+ function asNumber(value: unknown, fallback: number): number {
44
+ return typeof value === 'number' && !Number.isNaN(value) ? value : fallback
45
+ }
46
+
47
+ function asBoolean(value: unknown, fallback: boolean): boolean {
48
+ return typeof value === 'boolean' ? value : fallback
49
+ }
50
+
51
+ function envelopeError(json: Record<string, unknown>): string {
52
+ const message = asString(json.error) !== '' ? asString(json.error) : asString(json.message)
53
+ if (message !== '') return message
54
+ const code = asString(json.code)
55
+ return code !== '' ? code : 'the rewards server rejected the request'
56
+ }
57
+
58
+ function unwrap(body: unknown): Result<unknown> {
59
+ const json = asRecord(body)
60
+ if (json.ok === false) return [envelopeError(json), null]
61
+ return [null, json.data]
62
+ }
63
+
64
+ function toCampaign(value: unknown): RewardCampaign | null {
65
+ const json = asRecord(value)
66
+ const campaignId = asString(json.campaign_id)
67
+ if (campaignId === '') return null
68
+ return {
69
+ campaignId,
70
+ enabled: asBoolean(json.enabled, true),
71
+ requiresCaptcha: asBoolean(json.requires_captcha, false)
72
+ }
73
+ }
74
+
75
+ export async function fetchCampaigns(dispenserKey: string): Promise<Result<RewardCampaign[]>> {
76
+ const url = `${ENDPOINTS.rewardsCampaignKeys()}?campaign_key=${encodeURIComponent(dispenserKey)}`
77
+ const [failure, body] = await request<unknown>({ url })
78
+ if (failure !== null) return [failure, null]
79
+ const [invalid, data] = unwrap(body)
80
+ if (invalid !== null) return [invalid, null]
81
+ if (!Array.isArray(data)) return ['the rewards server returned no campaigns for this key', null]
82
+ const campaigns: RewardCampaign[] = []
83
+ for (const entry of data) {
84
+ const campaign = toCampaign(entry)
85
+ if (campaign !== null) campaigns.push(campaign)
86
+ }
87
+ return [null, campaigns]
88
+ }
89
+
90
+ export async function fetchCaptcha(): Promise<Result<RewardCaptcha>> {
91
+ const [failure, body] = await request<unknown>({
92
+ url: ENDPOINTS.rewardsCaptcha(),
93
+ init: { method: 'POST', headers: JSON_HEADERS }
94
+ })
95
+ if (failure !== null) return [failure, null]
96
+ const [invalid, data] = unwrap(body)
97
+ if (invalid !== null) return [invalid, null]
98
+ const json = asRecord(data)
99
+ const id = asString(json.id)
100
+ const image = asString(json.image)
101
+ if (id === '' || image === '') return ['the captcha could not be loaded, try again', null]
102
+ return [null, { id, image, width: asNumber(json.width, 250), height: asNumber(json.height, 100) }]
103
+ }
104
+
105
+ function toSummary(data: unknown): ClaimSummary {
106
+ if (Array.isArray(data)) {
107
+ const first = asRecord(data[0])
108
+ return { status: asString(first.status, 'assigned'), count: data.length }
109
+ }
110
+ const json = asRecord(data)
111
+ return { status: asString(json.status, 'assigned'), count: Object.keys(json).length > 0 ? 1 : 0 }
112
+ }
113
+
114
+ /** True when the server is asking for a captcha rather than reporting a real failure. */
115
+ export function isCaptchaRequired(message: string): boolean {
116
+ return message.toLowerCase().includes('captcha')
117
+ }
118
+
119
+ export async function claimReward(claim: ClaimRequest): Promise<Result<ClaimSummary>> {
120
+ const body: Record<string, string> = {
121
+ campaign_key: claim.dispenserKey,
122
+ beneficiary: claim.beneficiary,
123
+ catalyst: claim.catalyst
124
+ }
125
+ if (claim.captchaId !== undefined && claim.captchaValue !== undefined) {
126
+ body.captcha_id = claim.captchaId
127
+ body.captcha_value = claim.captchaValue
128
+ }
129
+
130
+ const [failure, response] = await request<unknown>({
131
+ url: ENDPOINTS.rewardsAssign(),
132
+ init: { method: 'POST', headers: JSON_HEADERS, body: JSON.stringify(body) }
133
+ })
134
+ if (failure !== null) return [failure, null]
135
+ const [invalid, data] = unwrap(response)
136
+ if (invalid !== null) return [invalid, null]
137
+ return [null, toSummary(data)]
138
+ }