@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,253 @@
1
+ import ReactEcs, { Label, ReactEcsRenderer, UiEntity } from '@dcl/sdk/react-ecs'
2
+ import { engine, VideoPlayer, type Entity } from '@dcl/sdk/ecs'
3
+ import { isServer, syncEntity } from '@dcl/sdk/network'
4
+ import { getPlayer } from '@dcl/sdk/players'
5
+ import { listenForKicks } from './tabs/moderation/kick'
6
+ import { Color4 } from '@dcl/sdk/math'
7
+ import {
8
+ AdminTools,
9
+ TextAnnouncements,
10
+ VideoControlState,
11
+ VIDEO_CONTROL_SYNC_ID,
12
+ type AdminToolsValue
13
+ } from './components'
14
+ import {
15
+ getSceneAdmins,
16
+ getSceneBans,
17
+ isPreview,
18
+ isSceneAdmin,
19
+ loadRealm,
20
+ toSceneAdmins,
21
+ type SceneAdmin
22
+ } from './api'
23
+ import { adminIcons, assetBase, type AdminIcons } from './icons'
24
+ import { initAdminMessageBus, type AdminMessageBus } from './message-bus'
25
+ import { createAdminState, drainNextTick, nextTick, TabId, type AdminState } from './state'
26
+ import type { AdminPlayer, TabProps, TabSpec } from './types'
27
+ import { IconButton, PANEL_BACKGROUND, PANEL_WIDTH, TEXT_DIM } from './ui'
28
+ import { moderationTab } from './tabs/Moderation'
29
+ import { rewardsTab } from './tabs/Rewards'
30
+ import { smartItemActionsTab } from './tabs/SmartItemActions'
31
+ import { AnnouncementOverlay, textAnnouncementsTab } from './tabs/TextAnnouncements'
32
+ import { videoControlTab } from './tabs/VideoControl'
33
+
34
+ const VIRTUAL_CANVAS = { virtualWidth: 1920, virtualHeight: 1080 }
35
+
36
+ // The panel is scene-wide, and so is everything start() registers: one comms
37
+ // bus, one synced video state under a fixed sync id, one drain system that the
38
+ // engine refuses to add twice. A second item stops here.
39
+ let started = false
40
+
41
+ const TABS: TabSpec[] = [
42
+ moderationTab,
43
+ videoControlTab,
44
+ smartItemActionsTab,
45
+ textAnnouncementsTab,
46
+ rewardsTab
47
+ ]
48
+
49
+ function videoPlayerEntities(): Entity[] {
50
+ return Array.from(engine.getEntitiesWith(VideoPlayer)).map(([entity]) => entity)
51
+ }
52
+
53
+ function toAdminPlayer(): AdminPlayer | null {
54
+ const player = getPlayer()
55
+ if (player === null) return null
56
+ return { userId: player.userId, name: player.name }
57
+ }
58
+
59
+ export class AdminToolsScript {
60
+ private readonly state: AdminState = createAdminState()
61
+ private readonly base: string
62
+ private readonly icons: AdminIcons
63
+ private bus: AdminMessageBus | null = null
64
+ private ready = false
65
+
66
+ constructor(
67
+ public src: string,
68
+ public entity: Entity
69
+ ) {
70
+ this.base = assetBase(src)
71
+ this.icons = adminIcons(this.base)
72
+ }
73
+
74
+ start(): void {
75
+ // Not free to skip: a realm lookup, two admin fetches, a comms subscription
76
+ // and a UI diff every frame.
77
+ if (isServer()) { return }
78
+ listenForKicks()
79
+ if (started) {
80
+ console.log('Admin Tools: this scene already has one — delete this item, the panel it draws is scene-wide.')
81
+ return
82
+ }
83
+ started = true
84
+
85
+ if (TextAnnouncements.getOrNull(this.entity) === null) {
86
+ TextAnnouncements.create(this.entity, { text: '', author: '', id: '' })
87
+ }
88
+ if (VideoControlState.getOrNull(this.entity) === null) {
89
+ VideoControlState.create(this.entity)
90
+ }
91
+ syncEntity(this.entity, [VideoControlState.componentId], VIDEO_CONTROL_SYNC_ID)
92
+
93
+ engine.addSystem(drainNextTick, Number.POSITIVE_INFINITY)
94
+
95
+ void this.initialize()
96
+ }
97
+
98
+ private async initialize(): Promise<void> {
99
+ await loadRealm()
100
+ await this.refreshAdmins()
101
+ await this.refreshBans()
102
+
103
+ this.bus = initAdminMessageBus({
104
+ self: this.entity,
105
+ admins: this.state.admins,
106
+ videoEntities: videoPlayerEntities(),
107
+ onRefetchAdmins: () => {
108
+ void this.refreshAdmins()
109
+ }
110
+ })
111
+
112
+ this.ready = true
113
+ ReactEcsRenderer.addUiRenderer(this.entity, () => this.render(), VIRTUAL_CANVAS)
114
+ }
115
+
116
+ private async refreshAdmins(): Promise<void> {
117
+ const [error, response] = await getSceneAdmins()
118
+ const admins: SceneAdmin[] = error === null ? toSceneAdmins(response) : []
119
+ this.state.admins = admins
120
+ this.bus?.updateAdminList(admins)
121
+ }
122
+
123
+ private async refreshBans(): Promise<void> {
124
+ const [error, response] = await getSceneBans()
125
+ this.state.bans = error === null && Array.isArray(response.results) ? response.results : []
126
+ }
127
+
128
+ private visibleTabs(config: AdminToolsValue): TabSpec[] {
129
+ return TABS.filter(
130
+ (tab) => tab.isEnabled(config) && !(tab.hiddenInPreview === true && isPreview())
131
+ )
132
+ }
133
+
134
+ private selectTab(id: TabId): void {
135
+ if (this.state.activeTab === id) {
136
+ this.state.activeTab = TabId.NONE
137
+ return
138
+ }
139
+ this.state.activeTab = TabId.NONE
140
+ nextTick(() => {
141
+ this.state.activeTab = id
142
+ })
143
+ }
144
+
145
+ private render(): ReactEcs.JSX.Element | null {
146
+ const config: AdminToolsValue | null = AdminTools.getOrNull(this.entity)
147
+ if (config === null || !this.ready) return null
148
+
149
+ const player = toAdminPlayer()
150
+
151
+ return (
152
+ <UiEntity uiTransform={{ positionType: 'absolute', width: '100%', height: '100%' }}>
153
+ <AnnouncementOverlay
154
+ self={this.entity}
155
+ config={config}
156
+ assetBase={this.base}
157
+ state={this.state}
158
+ />
159
+ {isSceneAdmin(this.state.admins, player?.userId) ? this.panel(config, player) : null}
160
+ </UiEntity>
161
+ )
162
+ }
163
+
164
+ private panel(config: AdminToolsValue, player: AdminPlayer | null): ReactEcs.JSX.Element {
165
+ const tabs = this.visibleTabs(config)
166
+ const active = tabs.find((tab) => tab.id === this.state.activeTab)
167
+ const ActiveTab = active?.Component
168
+ const props: TabProps = {
169
+ self: this.entity,
170
+ config,
171
+ state: this.state,
172
+ icons: this.icons,
173
+ assetBase: this.base,
174
+ bus: this.bus,
175
+ player
176
+ }
177
+
178
+ return (
179
+ <UiEntity
180
+ uiTransform={{
181
+ positionType: 'absolute',
182
+ flexDirection: 'row',
183
+ position: { top: 120, right: 14 }
184
+ }}
185
+ >
186
+ <UiEntity
187
+ uiTransform={{
188
+ display: this.state.panelOpen ? 'flex' : 'none',
189
+ width: PANEL_WIDTH,
190
+ flexDirection: 'column',
191
+ pointerFilter: 'block',
192
+ margin: { right: 8 }
193
+ }}
194
+ >
195
+ <UiEntity
196
+ uiTransform={{
197
+ width: '100%',
198
+ height: 50,
199
+ flexDirection: 'row',
200
+ alignItems: 'center',
201
+ borderRadius: 12,
202
+ padding: { left: 12, right: 12 }
203
+ }}
204
+ uiBackground={{ color: PANEL_BACKGROUND }}
205
+ >
206
+ <Label
207
+ value="ADMIN TOOLS"
208
+ fontSize={20}
209
+ color={TEXT_DIM}
210
+ textAlign="middle-left"
211
+ uiTransform={{ flexGrow: 1 }}
212
+ />
213
+ {tabs.map((tab) => (
214
+ <IconButton
215
+ key={tab.id}
216
+ icon={this.icons[tab.icon]}
217
+ active={this.state.activeTab === tab.id}
218
+ onClick={() => this.selectTab(tab.id)}
219
+ />
220
+ ))}
221
+ </UiEntity>
222
+ {ActiveTab !== undefined ? <ActiveTab {...props} /> : null}
223
+ </UiEntity>
224
+ <UiEntity
225
+ uiTransform={{
226
+ width: 42,
227
+ height: 42,
228
+ alignItems: 'center',
229
+ justifyContent: 'center',
230
+ pointerFilter: 'block'
231
+ }}
232
+ uiBackground={{
233
+ textureMode: 'stretch',
234
+ texture: { src: this.icons.panelBackground },
235
+ color: Color4.White()
236
+ }}
237
+ onMouseDown={() => {
238
+ this.state.panelOpen = !this.state.panelOpen
239
+ }}
240
+ >
241
+ <UiEntity
242
+ uiTransform={{ width: 32, height: 32 }}
243
+ uiBackground={{
244
+ textureMode: 'stretch',
245
+ texture: { src: this.icons.panelToggle },
246
+ color: Color4.White()
247
+ }}
248
+ />
249
+ </UiEntity>
250
+ </UiEntity>
251
+ )
252
+ }
253
+ }
@@ -0,0 +1,125 @@
1
+ // Decentraland service endpoints the admin panel talks to, plus the signedFetch
2
+ // wrapper every call goes through. The hosts are hardcoded on purpose — they are
3
+ // hardcoded in the Creator Hub runtime too, and the scene has no way to discover
4
+ // them. `getDomain()` switches org/zone off the realm's network id.
5
+ import { signedFetch } from '~system/SignedFetch'
6
+ import type { SignedFetchRequest } from '~system/SignedFetch'
7
+ import { getRealm } from '~system/Runtime'
8
+ import type { PBRealmInfo } from '~system/Runtime'
9
+
10
+ export type Result<T> = [null, T] | [string, null]
11
+
12
+ let realmCache: PBRealmInfo | undefined
13
+
14
+ export async function loadRealm(): Promise<PBRealmInfo | undefined> {
15
+ if (realmCache !== undefined) return realmCache
16
+ const info = await getRealm({})
17
+ realmCache = info.realmInfo
18
+ return realmCache
19
+ }
20
+
21
+ export function isPreview(): boolean {
22
+ return realmCache?.isPreview === true
23
+ }
24
+
25
+ export function getDomain(): string {
26
+ if (realmCache === undefined || realmCache.networkId === 1) return 'org'
27
+ return 'zone'
28
+ }
29
+
30
+ export function gatekeeperUrl(path: string): string {
31
+ return `https://comms-gatekeeper.decentraland.${getDomain()}${path}`
32
+ }
33
+
34
+ export function rewardsUrl(path: string): string {
35
+ return `https://rewards.decentraland.${getDomain()}${path}`
36
+ }
37
+
38
+ export const ENDPOINTS = {
39
+ sceneAdmin: (): string => gatekeeperUrl('/scene-admin'),
40
+ sceneBans: (): string => gatekeeperUrl('/scene-bans'),
41
+ streamAccess: (): string => gatekeeperUrl('/scene-stream-access'),
42
+ castStreamLink: (): string => gatekeeperUrl('/cast/generate-stream-link'),
43
+ castPresenters: (): string => gatekeeperUrl('/cast/presenters'),
44
+ rewardsCampaignKeys: (): string => rewardsUrl('/api/campaigns/keys'),
45
+ rewardsCaptcha: (): string => rewardsUrl('/api/captcha'),
46
+ rewardsAssign: (): string => rewardsUrl('/api/rewards')
47
+ }
48
+
49
+ // signedFetch is unavailable in local preview (no wallet-signed identity), so the
50
+ // caller gets an explanatory failure instead of a stack trace.
51
+ export async function request<T>(req: SignedFetchRequest): Promise<Result<T>> {
52
+ await loadRealm()
53
+ if (isPreview()) return ['Not available in local preview — try it in a World or Genesis City.', null]
54
+
55
+ try {
56
+ const response = await signedFetch(req)
57
+ if (!response.ok) return [response.body === '' ? `request to ${req.url} failed` : response.body, null]
58
+ return [null, JSON.parse(response.body === '' ? '{}' : response.body) as T]
59
+ } catch (error) {
60
+ console.log(`admin-tools: ${req.url} failed`, error)
61
+ return [error instanceof Error ? error.message : String(error), null]
62
+ }
63
+ }
64
+
65
+ export interface SceneAdminResponse {
66
+ id: string
67
+ name: string
68
+ admin: string
69
+ canBeRemoved: boolean
70
+ }
71
+
72
+ export interface SceneAdmin {
73
+ address: string
74
+ name: string
75
+ verified: boolean
76
+ canBeRemoved: boolean
77
+ }
78
+
79
+ export async function getSceneAdmins(): Promise<Result<SceneAdminResponse[]>> {
80
+ return request<SceneAdminResponse[]>({ url: ENDPOINTS.sceneAdmin() })
81
+ }
82
+
83
+ export async function addSceneAdmin(admin: string): Promise<Result<unknown>> {
84
+ return request({
85
+ url: ENDPOINTS.sceneAdmin(),
86
+ init: { method: 'POST', headers: {}, body: JSON.stringify({ admin }) }
87
+ })
88
+ }
89
+
90
+ export async function removeSceneAdmin(admin: string): Promise<Result<unknown>> {
91
+ return request({
92
+ url: ENDPOINTS.sceneAdmin(),
93
+ init: { method: 'DELETE', headers: {}, body: JSON.stringify({ admin }) }
94
+ })
95
+ }
96
+
97
+ export interface SceneBanUser {
98
+ bannedAddress: string
99
+ name: string
100
+ }
101
+
102
+ export async function getSceneBans(): Promise<Result<{ results: SceneBanUser[] }>> {
103
+ return request<{ results: SceneBanUser[] }>({ url: ENDPOINTS.sceneBans() })
104
+ }
105
+
106
+ // Who gets to see the panel. Local preview has no wallet-signed identity to check
107
+ // against /scene-admin, so everyone is an admin there — the Creator Hub runtime
108
+ // makes the same call, and without it the panel would be untestable offline.
109
+ export function isSceneAdmin(admins: SceneAdmin[], address: string | undefined): boolean {
110
+ if (isPreview()) return true
111
+ if (address === undefined) return false
112
+ const lower = address.toLowerCase()
113
+ return admins.some((admin) => admin.address === lower)
114
+ }
115
+
116
+ export function toSceneAdmins(response: SceneAdminResponse[]): SceneAdmin[] {
117
+ return response
118
+ .map((entry) => ({
119
+ address: entry.admin.toLowerCase(),
120
+ name: entry.name,
121
+ verified: !entry.name.includes('#'),
122
+ canBeRemoved: entry.canBeRemoved === true
123
+ }))
124
+ .sort((a) => (a.canBeRemoved ? 1 : -1))
125
+ }
@@ -0,0 +1,190 @@
1
+ // The asset-packs:: components this prefab reads and writes, defined on the scene
2
+ // engine. A component's numeric id is derived from its name, so these decode the
3
+ // exact bytes the editor authored — but FIELD ORDER MATTERS (Schemas.Map serializes
4
+ // in insertion order), so every spec here is a verbatim copy of the editor registry
5
+ // in packages/scene/src/custom-registry.ts, which itself mirrors the Creator Hub.
6
+ import { engine, Schemas } from '@dcl/sdk/ecs'
7
+
8
+ export enum AdminPermissions {
9
+ PUBLIC = 'PUBLIC',
10
+ PRIVATE = 'PRIVATE'
11
+ }
12
+
13
+ export enum MediaSource {
14
+ VideoURL,
15
+ LiveStream
16
+ }
17
+
18
+ export const Actions = engine.defineComponent('asset-packs::Actions', {
19
+ id: Schemas.Int,
20
+ value: Schemas.Array(
21
+ Schemas.Map({
22
+ name: Schemas.String,
23
+ type: Schemas.String,
24
+ jsonPayload: Schemas.String,
25
+ allowedInBasicView: Schemas.Optional(Schemas.Boolean),
26
+ basicViewId: Schemas.Optional(Schemas.String),
27
+ default: Schemas.Optional(Schemas.Boolean)
28
+ })
29
+ )
30
+ })
31
+
32
+ export const States = engine.defineComponent('asset-packs::States', {
33
+ id: Schemas.Number,
34
+ value: Schemas.Array(Schemas.String),
35
+ defaultValue: Schemas.Optional(Schemas.String),
36
+ currentValue: Schemas.Optional(Schemas.String),
37
+ previousValue: Schemas.Optional(Schemas.String)
38
+ })
39
+
40
+ export const AdminTools = engine.defineComponent('asset-packs::AdminTools', {
41
+ adminPermissions: Schemas.EnumString<AdminPermissions>(AdminPermissions, AdminPermissions.PUBLIC),
42
+ authorizedAdminUsers: Schemas.Map({
43
+ me: Schemas.Boolean,
44
+ sceneOwners: Schemas.Boolean,
45
+ allowList: Schemas.Boolean,
46
+ adminAllowList: Schemas.Array(Schemas.String)
47
+ }),
48
+ moderationControl: Schemas.Map({
49
+ isEnabled: Schemas.Boolean,
50
+ kickCoordinates: Schemas.Map({
51
+ x: Schemas.Number,
52
+ y: Schemas.Number,
53
+ z: Schemas.Number
54
+ }),
55
+ allowNonOwnersManageAdminAllowList: Schemas.Boolean
56
+ }),
57
+ textAnnouncementControl: Schemas.Map({
58
+ isEnabled: Schemas.Boolean,
59
+ playSoundOnEachAnnouncement: Schemas.Boolean,
60
+ showAuthorOnEachAnnouncement: Schemas.Boolean
61
+ }),
62
+ videoControl: Schemas.Map({
63
+ isEnabled: Schemas.Boolean,
64
+ disableVideoPlayersSound: Schemas.Boolean,
65
+ showAuthorOnVideoPlayers: Schemas.Boolean,
66
+ linkAllVideoPlayers: Schemas.Boolean,
67
+ videoPlayers: Schemas.Optional(
68
+ Schemas.Array(
69
+ Schemas.Map({
70
+ entity: Schemas.Int,
71
+ customName: Schemas.String
72
+ })
73
+ )
74
+ )
75
+ }),
76
+ smartItemsControl: Schemas.Map({
77
+ isEnabled: Schemas.Boolean,
78
+ linkAllSmartItems: Schemas.Boolean,
79
+ smartItems: Schemas.Optional(
80
+ Schemas.Array(
81
+ Schemas.Map({
82
+ entity: Schemas.Int,
83
+ customName: Schemas.String,
84
+ defaultAction: Schemas.String
85
+ })
86
+ )
87
+ )
88
+ }),
89
+ rewardsControl: Schemas.Map({
90
+ isEnabled: Schemas.Boolean,
91
+ rewardItems: Schemas.Optional(
92
+ Schemas.Array(
93
+ Schemas.Map({
94
+ entity: Schemas.Int,
95
+ customName: Schemas.String
96
+ })
97
+ )
98
+ )
99
+ })
100
+ })
101
+
102
+ export const VideoScreen = engine.defineComponent('asset-packs::VideoScreen', {
103
+ thumbnail: Schemas.String,
104
+ defaultMediaSource: Schemas.EnumNumber<MediaSource>(MediaSource, MediaSource.VideoURL),
105
+ defaultURL: Schemas.String
106
+ })
107
+
108
+ export const Rewards = engine.defineComponent('asset-packs::Rewards', {
109
+ campaignId: Schemas.String,
110
+ dispenserKey: Schemas.String,
111
+ testMode: Schemas.Boolean
112
+ })
113
+
114
+ export const TextAnnouncements = engine.defineComponent('asset-packs::TextAnnouncements', {
115
+ text: Schemas.String,
116
+ author: Schemas.Optional(Schemas.String),
117
+ id: Schemas.String
118
+ })
119
+
120
+ export const VideoControlState = engine.defineComponent('asset-packs::VideoControlState', {
121
+ endsAt: Schemas.Optional(Schemas.Int64),
122
+ streamKey: Schemas.Optional(Schemas.String)
123
+ })
124
+
125
+ // syncEntity needs a network id every client agrees on before any of them has
126
+ // seen the scene. The editor allocates scene entities from 8001 up, so 8000 is
127
+ // reserved for the admin entity's shared video state (same value the Hub uses).
128
+ export const VIDEO_CONTROL_SYNC_ID = 8000
129
+
130
+ // Plain mirrors of the schemas above. The component getters return DeepReadonly
131
+ // views; these are what the tabs and the message bus pass around.
132
+ export interface ActionEntry {
133
+ name: string
134
+ type: string
135
+ jsonPayload: string
136
+ }
137
+
138
+ // Everything is readonly because this is what AdminTools.get() hands back
139
+ // (DeepReadonly of the schema) — the tabs receive that value directly, no cast.
140
+ export interface VideoPlayerRef {
141
+ readonly entity: number
142
+ readonly customName: string
143
+ }
144
+
145
+ export interface SmartItemRef {
146
+ readonly entity: number
147
+ readonly customName: string
148
+ readonly defaultAction: string
149
+ }
150
+
151
+ export interface RewardItemRef {
152
+ readonly entity: number
153
+ readonly customName: string
154
+ }
155
+
156
+ export interface AdminToolsValue {
157
+ readonly adminPermissions: AdminPermissions
158
+ readonly authorizedAdminUsers: {
159
+ readonly me: boolean
160
+ readonly sceneOwners: boolean
161
+ readonly allowList: boolean
162
+ readonly adminAllowList: readonly string[]
163
+ }
164
+ readonly moderationControl: {
165
+ readonly isEnabled: boolean
166
+ readonly kickCoordinates: { readonly x: number; readonly y: number; readonly z: number }
167
+ readonly allowNonOwnersManageAdminAllowList: boolean
168
+ }
169
+ readonly textAnnouncementControl: {
170
+ readonly isEnabled: boolean
171
+ readonly playSoundOnEachAnnouncement: boolean
172
+ readonly showAuthorOnEachAnnouncement: boolean
173
+ }
174
+ readonly videoControl: {
175
+ readonly isEnabled: boolean
176
+ readonly disableVideoPlayersSound: boolean
177
+ readonly showAuthorOnVideoPlayers: boolean
178
+ readonly linkAllVideoPlayers: boolean
179
+ readonly videoPlayers?: readonly VideoPlayerRef[]
180
+ }
181
+ readonly smartItemsControl: {
182
+ readonly isEnabled: boolean
183
+ readonly linkAllSmartItems: boolean
184
+ readonly smartItems?: readonly SmartItemRef[]
185
+ }
186
+ readonly rewardsControl: {
187
+ readonly isEnabled: boolean
188
+ readonly rewardItems?: readonly RewardItemRef[]
189
+ }
190
+ }
@@ -0,0 +1,53 @@
1
+ // UI textures ship inside the prefab folder instead of the builder-items CDN, so
2
+ // a placed scene stays self-contained. The prefab folder is discovered at runtime
3
+ // from the script's own location (sdk-commands passes the script's *directory* as
4
+ // the first constructor argument) — it has to be, because placing the prefab twice
5
+ // gives the second copy a different folder (custom/admin-tools_2/…).
6
+ const SCRIPTS_SEGMENT = '/scripts'
7
+
8
+ export function assetBase(scriptSrc: string): string {
9
+ const slash = scriptSrc.lastIndexOf('/')
10
+ const dir = /\.tsx?$/.test(scriptSrc) && slash > -1 ? scriptSrc.slice(0, slash) : scriptSrc
11
+ const trimmed = dir.replace(/\/+$/, '')
12
+ if (trimmed === 'scripts') return ''
13
+ return trimmed.endsWith(SCRIPTS_SEGMENT) ? trimmed.slice(0, -SCRIPTS_SEGMENT.length) : trimmed
14
+ }
15
+
16
+ // Every texture in the prefab lives under <assetBase>/icons/. Tabs that ship
17
+ // their own glyphs build their paths with this instead of re-deriving the root.
18
+ export function iconPath(base: string, name: string): string {
19
+ return base === '' ? `icons/${name}` : `${base}/icons/${name}`
20
+ }
21
+
22
+ export interface AdminIcons {
23
+ panelBackground: string
24
+ panelToggle: string
25
+ tabModeration: string
26
+ tabVideo: string
27
+ tabSmartItems: string
28
+ tabTextAnnouncements: string
29
+ tabRewards: string
30
+ headerModeration: string
31
+ headerVideo: string
32
+ headerSmartItems: string
33
+ headerTextAnnouncements: string
34
+ headerRewards: string
35
+ }
36
+
37
+ export function adminIcons(base: string): AdminIcons {
38
+ const icon = (name: string): string => iconPath(base, name)
39
+ return {
40
+ panelBackground: icon('admin-tool-background.png'),
41
+ panelToggle: icon('admin-panel-control-button.png'),
42
+ tabModeration: icon('admin-panel-moderation-control-button.png'),
43
+ tabVideo: icon('admin-panel-video-control-button.png'),
44
+ tabSmartItems: icon('admin-panel-smart-item-control-button.png'),
45
+ tabTextAnnouncements: icon('admin-panel-text-announcement-control-button.png'),
46
+ tabRewards: icon('admin-panel-rewards-control-button.png'),
47
+ headerModeration: icon('moderation-control-icon.png'),
48
+ headerVideo: icon('video-control.png'),
49
+ headerSmartItems: icon('smart-item-control.png'),
50
+ headerTextAnnouncements: icon('text-announcement-control.png'),
51
+ headerRewards: icon('rewards-control.png')
52
+ }
53
+ }