@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,243 @@
1
+ import ReactEcs, { Label, UiEntity } from '@dcl/sdk/react-ecs'
2
+ import type { Key, UiBackgroundProps, UiTransformProps } from '@dcl/sdk/react-ecs'
3
+ import { Color4 } from '@dcl/sdk/math'
4
+
5
+ export const COLORS = {
6
+ white: Color4.White(),
7
+ black: Color4.Black(),
8
+ offWhite: Color4.fromHexString('#FCFCFC'),
9
+ gray: Color4.create(160 / 255, 155 / 255, 168 / 255, 1),
10
+ dim: Color4.fromHexString('#FFFFFFB2'),
11
+ surface: Color4.fromHexString('#43404A'),
12
+ line: Color4.create(1, 1, 1, 0.12),
13
+ accent: Color4.fromHexString('#00D3FF'),
14
+ success: Color4.fromHexString('#34CE77'),
15
+ successMuted: Color4.fromHexString('#274431'),
16
+ danger: Color4.fromHexString('#FB3B3B'),
17
+ transparent: Color4.create(0, 0, 0, 0)
18
+ }
19
+
20
+ export type ButtonVariant = 'primary' | 'secondary' | 'text' | 'success' | 'danger'
21
+
22
+ function background(variant: ButtonVariant, disabled: boolean): Color4.Mutable {
23
+ if (disabled && variant === 'success') return COLORS.successMuted
24
+ if (variant === 'primary') return COLORS.white
25
+ if (variant === 'success') return COLORS.success
26
+ if (variant === 'secondary') return COLORS.surface
27
+ return COLORS.transparent
28
+ }
29
+
30
+ function foreground(variant: ButtonVariant, disabled: boolean): Color4.Mutable {
31
+ if (disabled) return COLORS.gray
32
+ if (variant === 'primary' || variant === 'success') return COLORS.black
33
+ if (variant === 'danger') return COLORS.danger
34
+ return COLORS.white
35
+ }
36
+
37
+ export function Button(props: {
38
+ key?: Key
39
+ label?: string
40
+ icon?: string
41
+ iconRight?: string
42
+ variant?: ButtonVariant
43
+ disabled?: boolean
44
+ fontSize?: number
45
+ onClick: () => void
46
+ uiTransform?: UiTransformProps
47
+ }): ReactEcs.JSX.Element {
48
+ const variant = props.variant ?? 'secondary'
49
+ const disabled = props.disabled === true
50
+ const tint = foreground(variant, disabled)
51
+ const iconStyle: UiBackgroundProps = { textureMode: 'stretch', color: tint }
52
+ return (
53
+ <UiEntity
54
+ uiTransform={{
55
+ height: 40,
56
+ flexDirection: 'row',
57
+ alignItems: 'center',
58
+ justifyContent: 'center',
59
+ borderRadius: 8,
60
+ padding: { left: 12, right: 12 },
61
+ ...props.uiTransform
62
+ }}
63
+ uiBackground={{ color: background(variant, disabled) }}
64
+ onMouseDown={() => {
65
+ if (!disabled) props.onClick()
66
+ }}
67
+ >
68
+ {props.icon !== undefined ? (
69
+ <UiEntity
70
+ uiTransform={{ width: 22, height: 22, margin: { right: props.label === undefined ? 0 : 8 } }}
71
+ uiBackground={{ ...iconStyle, texture: { src: props.icon } }}
72
+ />
73
+ ) : null}
74
+ {props.label !== undefined ? (
75
+ <Label value={`<b>${props.label}</b>`} fontSize={props.fontSize ?? 16} color={tint} />
76
+ ) : null}
77
+ {props.iconRight !== undefined ? (
78
+ <UiEntity
79
+ uiTransform={{ width: 20, height: 20, margin: { left: 8 } }}
80
+ uiBackground={{ ...iconStyle, texture: { src: props.iconRight } }}
81
+ />
82
+ ) : null}
83
+ </UiEntity>
84
+ )
85
+ }
86
+
87
+ export function SectionLabel(props: {
88
+ text: string
89
+ uiTransform?: UiTransformProps
90
+ }): ReactEcs.JSX.Element {
91
+ return (
92
+ <Label
93
+ value={`<b>${props.text}</b>`}
94
+ fontSize={16}
95
+ color={COLORS.white}
96
+ textAlign="middle-left"
97
+ uiTransform={{ width: '100%', height: 24, margin: { top: 12, bottom: 4 }, ...props.uiTransform }}
98
+ />
99
+ )
100
+ }
101
+
102
+ export function Hint(props: {
103
+ text: string
104
+ color?: Color4.Mutable
105
+ uiTransform?: UiTransformProps
106
+ }): ReactEcs.JSX.Element {
107
+ return (
108
+ <Label
109
+ value={props.text}
110
+ fontSize={14}
111
+ color={props.color ?? COLORS.gray}
112
+ textAlign="top-left"
113
+ textWrap="wrap"
114
+ uiTransform={{ width: '100%', height: 'auto', ...props.uiTransform }}
115
+ />
116
+ )
117
+ }
118
+
119
+ export function Row(props: {
120
+ children?: ReactEcs.JSX.ReactNode
121
+ uiTransform?: UiTransformProps
122
+ uiBackground?: UiBackgroundProps
123
+ }): ReactEcs.JSX.Element {
124
+ return (
125
+ <UiEntity
126
+ uiTransform={{
127
+ width: '100%',
128
+ height: 'auto',
129
+ flexDirection: 'row',
130
+ alignItems: 'center',
131
+ ...props.uiTransform
132
+ }}
133
+ uiBackground={props.uiBackground}
134
+ >
135
+ {props.children}
136
+ </UiEntity>
137
+ )
138
+ }
139
+
140
+ export function Column(props: {
141
+ children?: ReactEcs.JSX.ReactNode
142
+ uiTransform?: UiTransformProps
143
+ }): ReactEcs.JSX.Element {
144
+ return (
145
+ <UiEntity uiTransform={{ width: '100%', flexDirection: 'column', ...props.uiTransform }}>
146
+ {props.children}
147
+ </UiEntity>
148
+ )
149
+ }
150
+
151
+ export function Divider(): ReactEcs.JSX.Element {
152
+ return (
153
+ <UiEntity
154
+ uiTransform={{ width: '100%', height: 1, margin: { top: 10, bottom: 10 } }}
155
+ uiBackground={{ color: COLORS.line }}
156
+ />
157
+ )
158
+ }
159
+
160
+ export function ErrorNote(props: { icon: string; text: string }): ReactEcs.JSX.Element {
161
+ return (
162
+ <Row uiTransform={{ margin: { top: 8 } }}>
163
+ <UiEntity
164
+ uiTransform={{ width: 16, height: 16, margin: { right: 6 } }}
165
+ uiBackground={{ textureMode: 'stretch', texture: { src: props.icon }, color: COLORS.danger }}
166
+ />
167
+ <Hint text={props.text} color={COLORS.danger} uiTransform={{ width: '90%' }} />
168
+ </Row>
169
+ )
170
+ }
171
+
172
+ export function IconLink(props: { icon: string; onClick: () => void }): ReactEcs.JSX.Element {
173
+ return (
174
+ <UiEntity
175
+ uiTransform={{ width: 22, height: 22 }}
176
+ uiBackground={{ textureMode: 'stretch', texture: { src: props.icon }, color: COLORS.white }}
177
+ onMouseDown={props.onClick}
178
+ />
179
+ )
180
+ }
181
+
182
+ export function SubHeader(props: {
183
+ icon: string
184
+ title: string
185
+ helpIcon?: string
186
+ onHelp?: () => void
187
+ }): ReactEcs.JSX.Element {
188
+ return (
189
+ <Row uiTransform={{ justifyContent: 'space-between', margin: { bottom: 8 } }}>
190
+ <Row uiTransform={{ width: 'auto' }}>
191
+ <UiEntity
192
+ uiTransform={{ width: 24, height: 24, margin: { right: 8 } }}
193
+ uiBackground={{
194
+ textureMode: 'stretch',
195
+ texture: { src: props.icon },
196
+ color: COLORS.white
197
+ }}
198
+ />
199
+ <Label value={`<b>${props.title}</b>`} fontSize={18} color={COLORS.white} />
200
+ </Row>
201
+ {props.helpIcon !== undefined && props.onHelp !== undefined ? (
202
+ <IconLink icon={props.helpIcon} onClick={props.onHelp} />
203
+ ) : null}
204
+ </Row>
205
+ )
206
+ }
207
+
208
+ export function ReadOnlyField(props: {
209
+ value: string
210
+ actionLabel: string
211
+ onAction: () => void
212
+ children?: ReactEcs.JSX.ReactNode
213
+ }): ReactEcs.JSX.Element {
214
+ return (
215
+ <Row
216
+ uiTransform={{
217
+ height: 42,
218
+ borderRadius: 8,
219
+ justifyContent: 'space-between',
220
+ margin: { top: 6, bottom: 6 }
221
+ }}
222
+ uiBackground={{ color: COLORS.offWhite }}
223
+ >
224
+ <Label
225
+ value={`<b>${props.value}</b>`}
226
+ fontSize={15}
227
+ color={COLORS.gray}
228
+ textAlign="middle-left"
229
+ uiTransform={{ margin: { left: 12 }, flexShrink: 1 }}
230
+ />
231
+ <Row uiTransform={{ width: 'auto', flexShrink: 0 }}>
232
+ {props.children}
233
+ <Button
234
+ label={props.actionLabel}
235
+ variant="text"
236
+ fontSize={15}
237
+ uiTransform={{ height: 32, margin: { right: 6 } }}
238
+ onClick={props.onAction}
239
+ />
240
+ </Row>
241
+ </Row>
242
+ )
243
+ }
@@ -0,0 +1,46 @@
1
+ // The contract between the panel shell (admin.tsx) and the tab components in
2
+ // tabs/. Every tab is a plain function of TabProps returning react-ecs JSX; the
3
+ // shell owns the tab bar, mounts exactly one tab at a time and passes the same
4
+ // props to all of them. Adding a tab means adding a file that exports a TabSpec
5
+ // and listing it in admin.tsx's TABS array — nothing else.
6
+ import type { Entity } from '@dcl/sdk/ecs'
7
+ import type ReactEcs from '@dcl/sdk/react-ecs'
8
+ import type { AdminToolsValue } from './components'
9
+ import type { AdminIcons } from './icons'
10
+ import type { AdminMessageBus } from './message-bus'
11
+ import type { AdminState, TabId } from './state'
12
+
13
+ export interface AdminPlayer {
14
+ userId: string
15
+ name: string
16
+ }
17
+
18
+ export interface TabProps {
19
+ /** entity the admin prefab is attached to; owns TextAnnouncements + VideoControlState */
20
+ self: Entity
21
+ /** live asset-packs::AdminTools value authored in the inspector */
22
+ config: AdminToolsValue
23
+ /** mutable panel state — mutate in place, the next frame re-renders */
24
+ state: AdminState
25
+ /** prefab-relative texture paths */
26
+ icons: AdminIcons
27
+ /** the placed prefab's folder ('' when the scene root is the prefab folder) */
28
+ assetBase: string
29
+ /** wallet-validated command bus; null before initialisation completes */
30
+ bus: AdminMessageBus | null
31
+ /** the local player, or null while the profile is still loading */
32
+ player: AdminPlayer | null
33
+ }
34
+
35
+ export type TabComponent = (props: TabProps) => ReactEcs.JSX.Element | null
36
+
37
+ export interface TabSpec {
38
+ id: TabId
39
+ /** tab-bar button texture; the panel header is the tab's own to render */
40
+ icon: keyof AdminIcons
41
+ /** whether the creator switched this control on in the inspector */
42
+ isEnabled: (config: AdminToolsValue) => boolean
43
+ /** hide the tab in local preview (moderation needs a signed identity) */
44
+ hiddenInPreview?: boolean
45
+ Component: TabComponent
46
+ }
@@ -0,0 +1,122 @@
1
+ import ReactEcs, { Label, UiEntity } from '@dcl/sdk/react-ecs'
2
+ import type { Key, UiBackgroundProps, UiTransformProps } from '@dcl/sdk/react-ecs'
3
+ import { Color4 } from '@dcl/sdk/math'
4
+
5
+ export const PANEL_BACKGROUND = Color4.create(0, 0, 0, 0.75)
6
+ export const PANEL_WIDTH = 500
7
+ export const TEXT_DIM = Color4.create(160 / 255, 155 / 255, 168 / 255, 1)
8
+
9
+ export function Card(props: {
10
+ children?: ReactEcs.JSX.ReactNode
11
+ uiTransform?: UiTransformProps
12
+ }): ReactEcs.JSX.Element {
13
+ return (
14
+ <UiEntity
15
+ uiTransform={{
16
+ width: '100%',
17
+ borderRadius: 12,
18
+ flexDirection: 'column',
19
+ margin: { top: 10 },
20
+ padding: { top: 24, right: 24, bottom: 24, left: 24 },
21
+ ...props.uiTransform
22
+ }}
23
+ uiBackground={{ color: PANEL_BACKGROUND }}
24
+ >
25
+ {props.children}
26
+ </UiEntity>
27
+ )
28
+ }
29
+
30
+ export function CardHeader(props: { icon: string; title: string }): ReactEcs.JSX.Element {
31
+ return (
32
+ <UiEntity
33
+ uiTransform={{
34
+ flexDirection: 'row',
35
+ alignItems: 'center',
36
+ height: 'auto',
37
+ margin: { bottom: 16 }
38
+ }}
39
+ >
40
+ <UiEntity
41
+ uiTransform={{ width: 30, height: 30 }}
42
+ uiBackground={{ textureMode: 'stretch', texture: { src: props.icon } }}
43
+ />
44
+ <Label
45
+ value={`<b>${props.title}</b>`}
46
+ fontSize={22}
47
+ color={Color4.White()}
48
+ textAlign="middle-left"
49
+ uiTransform={{ margin: { left: 10 } }}
50
+ />
51
+ </UiEntity>
52
+ )
53
+ }
54
+
55
+ export function IconButton(props: {
56
+ key?: Key
57
+ icon: string
58
+ active?: boolean
59
+ onClick: () => void
60
+ uiTransform?: UiTransformProps
61
+ iconBackground?: UiBackgroundProps
62
+ }): ReactEcs.JSX.Element {
63
+ const active = props.active === true
64
+ return (
65
+ <UiEntity
66
+ uiTransform={{
67
+ width: 49,
68
+ height: 42,
69
+ alignItems: 'center',
70
+ justifyContent: 'center',
71
+ borderRadius: 12,
72
+ margin: { right: 8 },
73
+ ...props.uiTransform
74
+ }}
75
+ uiBackground={{ color: active ? Color4.White() : Color4.create(0, 0, 0, 0) }}
76
+ onMouseDown={props.onClick}
77
+ >
78
+ <UiEntity
79
+ uiTransform={{ width: '70%', height: '70%' }}
80
+ uiBackground={{
81
+ textureMode: 'stretch',
82
+ texture: { src: props.icon },
83
+ color: active ? Color4.Black() : Color4.White(),
84
+ ...props.iconBackground
85
+ }}
86
+ />
87
+ </UiEntity>
88
+ )
89
+ }
90
+
91
+ export function TextButton(props: {
92
+ label: string
93
+ disabled?: boolean
94
+ onClick: () => void
95
+ uiTransform?: UiTransformProps
96
+ }): ReactEcs.JSX.Element {
97
+ const disabled = props.disabled === true
98
+ return (
99
+ <UiEntity
100
+ uiTransform={{
101
+ height: 40,
102
+ alignItems: 'center',
103
+ justifyContent: 'center',
104
+ borderRadius: 12,
105
+ borderWidth: 2,
106
+ borderColor: disabled ? Color4.create(50 / 255, 50 / 255, 50 / 255, 1) : Color4.White(),
107
+ padding: { left: 14, right: 14 },
108
+ margin: { right: 8 },
109
+ ...props.uiTransform
110
+ }}
111
+ onMouseDown={() => {
112
+ if (!disabled) props.onClick()
113
+ }}
114
+ >
115
+ <Label
116
+ value={`<b>${props.label}</b>`}
117
+ fontSize={16}
118
+ color={disabled ? Color4.Gray() : Color4.White()}
119
+ />
120
+ </UiEntity>
121
+ )
122
+ }
@@ -0,0 +1,76 @@
1
+ ---
2
+ prefab: announcer
3
+ claims-messages: announce
4
+ ---
5
+
6
+ # Announcer — AI guide
7
+
8
+ One line on every player's client, for a few seconds. The server says it; every
9
+ client shows it.
10
+
11
+ The project copy is normally custom/announcer/ — check what is on disk, a second
12
+ copy is custom/announcer_2/.
13
+
14
+ ## When to use
15
+
16
+ For moments: "The flag is taken", "Round over", "A climber made it". Place ONE
17
+ anywhere; it is invisible. A second copy is a no-op (the first one owns the
18
+ scene's UI).
19
+
20
+ Do NOT use it for anything a late joiner must see — a score, a carrier, a
21
+ countdown. Those are facts and belong in `game.state` with a board or a sign
22
+ reading them.
23
+
24
+ ## API
25
+
26
+ There is nothing to import. The channel is one broadcast name every script
27
+ already has:
28
+
29
+ game.broadcast('announce', { text: 'Round over — the tower is rebuilt' })
30
+
31
+ - WHICH SIDE: the call goes on the SERVER, inside the `if (isServer())` branch —
32
+ in a `game.onRequest`, `game.onRoundStart`, `game.every` or `game.onEnterArea`
33
+ handler. A broadcast from a client reaches nobody.
34
+ - `game.broadcast('announce', { text }, player)` narrows it to one player — only
35
+ that player's client receives it.
36
+ - The payload is `{ text }`; a plain string works too. Longer than 140 characters
37
+ is trimmed, and empty text shows nothing.
38
+ - Do NOT register your own `game.onBroadcast('announce', …)`. Broadcast listeners
39
+ are many-per-name, so yours would not replace this prefab's client-side one —
40
+ it would run beside it and the line would be shown twice.
41
+
42
+ Params of the prefab's script — set them in the placePrefab request:
43
+
44
+ - `holdSeconds`: how long a message stays up (default 4, max 60).
45
+ - `fontSize`: text size at 1920×1080 (default 32).
46
+
47
+ ## The scene's UI is single-owner
48
+
49
+ Only one script per scene may draw UI. This prefab claims it at start and says so
50
+ in the console if something else (Admin Tools, your own panel) already has it —
51
+ in that case the announcements do not render. If you write a UI panel of your
52
+ own, draw the announcement inside it instead of placing this prefab.
53
+
54
+ ## Do / Don't
55
+
56
+ - DON'T announce from the client half of a script. `game.broadcast` is a server
57
+ verb; a client that calls it reaches nobody, itself included.
58
+ - DON'T announce the same thing every frame. Broadcast it once, where the
59
+ decision is made — and `update()` runs on the server too, so a broadcast there
60
+ fires ~41 times a second.
61
+ - DO pair it with a board: the announcement is the moment, the board is the fact.
62
+
63
+ ## Example
64
+
65
+ "Tell everyone when someone takes the flag": in the server branch that decides it,
66
+
67
+ start(): void {
68
+ if (isServer()) {
69
+ game.onRequest('takeFlag', (_data, player) => {
70
+ game.setState({ flag: { carrier: player } })
71
+ game.broadcast('announce', { text: 'The flag is taken!' })
72
+ return { ok: true }
73
+ })
74
+ return
75
+ }
76
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "version": 1,
3
+ "components": [
4
+ {
5
+ "name": "core-schema::Name",
6
+ "data": {
7
+ "0": {
8
+ "json": {
9
+ "value": "Announcer"
10
+ }
11
+ }
12
+ }
13
+ },
14
+ {
15
+ "name": "asset-packs::Script",
16
+ "data": {
17
+ "0": {
18
+ "json": {
19
+ "value": [
20
+ {
21
+ "path": "{assetPath}/scripts/announcer.tsx",
22
+ "priority": 0,
23
+ "layout": "{\"params\":{},\"actions\":[]}"
24
+ }
25
+ ]
26
+ }
27
+ }
28
+ }
29
+ }
30
+ ]
31
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "id": "fb41d8d1-b842-437d-a830-f38379e74743",
3
+ "name": "Announcer",
4
+ "description": "Shows a message on every player's client — it fades, so lasting facts belong on a board. Works with: Game Flow.",
5
+ "category": "custom",
6
+ "group": "Multiplayer Server",
7
+ "tags": [
8
+ "multiplayer",
9
+ "server",
10
+ "announce",
11
+ "message",
12
+ "toast",
13
+ "ui"
14
+ ],
15
+ "requiresSdk": "auth-server",
16
+ "drivenBy": {
17
+ "rule": "This item shows nothing by itself — the server sends a line, and every player's client shows it for a few seconds.",
18
+ "code": "game.broadcast('announce', { text: 'Round over' })",
19
+ "next": "Press New script below and write that line inside the isServer() block it comes with — a third argument, the player, shows it on that one player's client only."
20
+ },
21
+ "version": "1.1.0",
22
+ "changelog": [
23
+ {
24
+ "version": "1.0.0",
25
+ "notes": "Initial release: server code anywhere in the scene calls game.broadcast('announce', { text }) and every player's client shows the line for a few seconds."
26
+ },
27
+ {
28
+ "version": "1.1.0",
29
+ "notes": "The placed item's Script card now shows the broadcast line that makes it speak, under its hold seconds and font size."
30
+ }
31
+ ],
32
+ "origin": {
33
+ "source": "builtin"
34
+ }
35
+ }
@@ -0,0 +1,79 @@
1
+ // The server says something; every player sees it. One line, centred near the
2
+ // top, gone a few seconds later.
3
+ //
4
+ // This is the listening half of the ordinary broadcast pair: server code
5
+ // anywhere in the scene calls game.broadcast('announce', { text }) and this
6
+ // piece draws it for every player. It is a moment, not a fact — a player who
7
+ // arrives afterwards never sees it, which is why standings belong in game.state.
8
+ import ReactEcs, { Label, ReactEcsRenderer, UiEntity } from '@dcl/sdk/react-ecs'
9
+ import { Color4 } from '@dcl/sdk/math'
10
+ import type { Entity } from '@dcl/sdk/ecs'
11
+ import { isServer } from '@dcl/sdk/network'
12
+ import { game } from '~runtime/game'
13
+ import { clampHold, toastText } from './pure/toast'
14
+
15
+ const ANNOUNCE = 'announce'
16
+ const VIRTUAL_CANVAS = { virtualWidth: 1920, virtualHeight: 1080 }
17
+ const BACKDROP = Color4.create(0, 0, 0, 0.65)
18
+
19
+ export class Announcer {
20
+ private text = ''
21
+ private leftS = 0
22
+
23
+ constructor(
24
+ public src: string,
25
+ public entity: Entity,
26
+ /** How long a message stays up. */
27
+ public holdSeconds: number = 4,
28
+ /** Text size at 1920×1080. */
29
+ public fontSize: number = 32
30
+ ) {}
31
+
32
+ start(): void {
33
+ if (isServer()) { return }
34
+ ReactEcsRenderer.addUiRenderer(this.entity, () => this.render(), VIRTUAL_CANVAS)
35
+ game.onBroadcast(ANNOUNCE, (data: unknown) => this.show(data))
36
+ }
37
+
38
+ update(dt: number): void {
39
+ if (isServer()) { return }
40
+ if (this.leftS <= 0) return
41
+ this.leftS -= dt
42
+ if (this.leftS <= 0) this.text = ''
43
+ }
44
+
45
+ private show(data: unknown): void {
46
+ const text = toastText(data)
47
+ if (text === null) return
48
+ this.text = text
49
+ this.leftS = clampHold(this.holdSeconds)
50
+ }
51
+
52
+ private render(): ReactEcs.JSX.Element | null {
53
+ if (this.text === '') return null
54
+ return (
55
+ <UiEntity
56
+ uiTransform={{
57
+ width: '100%',
58
+ height: '100%',
59
+ positionType: 'absolute',
60
+ alignItems: 'flex-start',
61
+ justifyContent: 'center'
62
+ }}
63
+ >
64
+ <UiEntity
65
+ uiTransform={{
66
+ margin: { top: 96 },
67
+ padding: { top: 12, bottom: 12, left: 28, right: 28 },
68
+ borderRadius: 12,
69
+ alignItems: 'center',
70
+ justifyContent: 'center'
71
+ }}
72
+ uiBackground={{ color: BACKDROP }}
73
+ >
74
+ <Label value={this.text} fontSize={this.fontSize} color={Color4.White()} />
75
+ </UiEntity>
76
+ </UiEntity>
77
+ )
78
+ }
79
+ }
@@ -0,0 +1,30 @@
1
+ // The announcer's decisions, with no SDK and no I/O: what a message turns into,
2
+ // and how long it stays. A message is a moment — the server says it once and it
3
+ // fades, which is exactly why a player who joins afterwards never sees it.
4
+
5
+ export const MAX_TEXT_CHARS = 140
6
+ export const MIN_HOLD_S = 1
7
+ export const MAX_HOLD_S = 60
8
+
9
+ export function clampHold(seconds: number): number {
10
+ if (!Number.isFinite(seconds)) return 4
11
+ return Math.min(MAX_HOLD_S, Math.max(MIN_HOLD_S, Math.round(seconds)))
12
+ }
13
+
14
+ /**
15
+ * The line to show, or null when there is nothing sayable. Payloads come off the
16
+ * wire, so a missing field, a number, or a paragraph must all end somewhere
17
+ * predictable rather than in front of a player.
18
+ */
19
+ export function toastText(data: unknown): string | null {
20
+ const value =
21
+ typeof data === 'string'
22
+ ? data
23
+ : typeof data === 'object' && data !== null
24
+ ? (data as Record<string, unknown>).text
25
+ : undefined
26
+ if (typeof value !== 'string') return null
27
+ const text = value.replace(/\s+/g, ' ').trim()
28
+ if (text === '') return null
29
+ return text.length > MAX_TEXT_CHARS ? `${text.slice(0, MAX_TEXT_CHARS - 1)}…` : text
30
+ }
Binary file