@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,37 @@
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 DISABLED_BACKGROUND = Color4.create(74 / 255, 74 / 255, 74 / 255, 1)
6
+ const DISABLED_TEXT = Color4.create(160 / 255, 155 / 255, 168 / 255, 1)
7
+
8
+ export function PrimaryButton(props: {
9
+ label: string
10
+ disabled?: boolean
11
+ onClick: () => void
12
+ uiTransform?: UiTransformProps
13
+ }): ReactEcs.JSX.Element {
14
+ const disabled = props.disabled === true
15
+ return (
16
+ <UiEntity
17
+ uiTransform={{
18
+ height: 40,
19
+ alignItems: 'center',
20
+ justifyContent: 'center',
21
+ borderRadius: 12,
22
+ padding: { left: 20, right: 20 },
23
+ ...props.uiTransform
24
+ }}
25
+ uiBackground={{ color: disabled ? DISABLED_BACKGROUND : Color4.White() }}
26
+ onMouseDown={() => {
27
+ if (!disabled) props.onClick()
28
+ }}
29
+ >
30
+ <Label
31
+ value={`<b>${props.label}</b>`}
32
+ fontSize={16}
33
+ color={disabled ? DISABLED_TEXT : Color4.Black()}
34
+ />
35
+ </UiEntity>
36
+ )
37
+ }
@@ -0,0 +1,184 @@
1
+ import ReactEcs, { Input, Label, UiEntity } from '@dcl/sdk/react-ecs'
2
+ import type { SceneAdmin } from '../../api'
3
+ import { postSceneAdmin, postSceneBan } from './api'
4
+ import type { ModerationIcons } from './icons'
5
+ import { kickUser } from './kick'
6
+ import { showMessage } from './state'
7
+ import { BADGE_GRAY, Button, ErrorLine, RED, SectionTitle, WHITE } from './ui'
8
+ import { isAdminUser, isValidAddress, looksLikeAddress } from './utils'
9
+
10
+ export enum PermissionType {
11
+ ADMIN = 'admin',
12
+ BAN = 'ban'
13
+ }
14
+
15
+ interface Props {
16
+ type: PermissionType
17
+ icons: ModerationIcons
18
+ admins: SceneAdmin[]
19
+ disabled: boolean
20
+ disabledHint: string
21
+ allowKick: boolean
22
+ onChanged: () => void
23
+ }
24
+
25
+ function BanUserDescription(): ReactEcs.JSX.Element {
26
+ return (
27
+ <UiEntity
28
+ uiTransform={{
29
+ width: '100%',
30
+ height: 'auto',
31
+ flexDirection: 'column',
32
+ margin: { bottom: 8 }
33
+ }}
34
+ >
35
+ <UiEntity
36
+ uiTransform={{ width: '100%', height: 'auto', margin: { bottom: 4 } }}
37
+ uiText={{
38
+ value:
39
+ "<b>Banned users CAN'T:</b> See your scene, send messages in the Nearby chat, or be seen by other users.",
40
+ fontSize: 13,
41
+ color: WHITE,
42
+ textAlign: 'top-left'
43
+ }}
44
+ />
45
+ <UiEntity
46
+ uiTransform={{ width: '100%', height: 'auto' }}
47
+ uiText={{
48
+ value:
49
+ '<b>Banned users CAN still:</b> See other users and see the messages in the Nearby chat.',
50
+ fontSize: 13,
51
+ color: WHITE,
52
+ textAlign: 'top-left'
53
+ }}
54
+ />
55
+ </UiEntity>
56
+ )
57
+ }
58
+
59
+ export function AddUserInput(props: Props): ReactEcs.JSX.Element {
60
+ const [value, setValue] = ReactEcs.useState('')
61
+ const [error, setError] = ReactEcs.useState('')
62
+ const [loading, setLoading] = ReactEcs.useState(false)
63
+
64
+ const isAdminForm = props.type === PermissionType.ADMIN
65
+
66
+ const validate = (raw: string): string | null => {
67
+ const submitValue = raw.trim()
68
+ if (submitValue.length <= 2) {
69
+ setError('Provide a valid address or NAME')
70
+ return null
71
+ }
72
+ if (looksLikeAddress(submitValue) && !isValidAddress(submitValue)) {
73
+ setError('Provide a valid address format')
74
+ return null
75
+ }
76
+ return submitValue
77
+ }
78
+
79
+ const submit = async (raw: string): Promise<void> => {
80
+ if (loading || props.disabled) return
81
+ const submitValue = validate(raw)
82
+ if (submitValue === null) return
83
+
84
+ const byAddress = looksLikeAddress(submitValue)
85
+
86
+ if (!isAdminForm && isAdminUser(props.admins, submitValue)) {
87
+ setError(
88
+ 'Admin users cannot be banned. Please remove this user from the Admin List and try again.'
89
+ )
90
+ return
91
+ }
92
+
93
+ setLoading(true)
94
+ const [failure] = isAdminForm
95
+ ? await postSceneAdmin(byAddress ? { admin: submitValue } : { name: submitValue })
96
+ : await postSceneBan(
97
+ byAddress ? { banned_address: submitValue } : { banned_name: submitValue }
98
+ )
99
+ setLoading(false)
100
+
101
+ if (failure !== null) {
102
+ setError('Please try again with a valid NAME or wallet address.')
103
+ return
104
+ }
105
+
106
+ setError('')
107
+ setValue('')
108
+ if (!isAdminForm) kickUser(submitValue, byAddress)
109
+ showMessage(isAdminForm ? `${submitValue} is now an admin` : `${submitValue} has been banned`)
110
+ props.onChanged()
111
+ }
112
+
113
+ const kick = (): void => {
114
+ if (loading || props.disabled) return
115
+ const submitValue = validate(value)
116
+ if (submitValue === null) return
117
+ setError('')
118
+ setValue('')
119
+ kickUser(submitValue, looksLikeAddress(submitValue))
120
+ showMessage(`Kick sent to ${submitValue}`)
121
+ }
122
+
123
+ return (
124
+ <UiEntity uiTransform={{ width: '100%', height: 'auto', flexDirection: 'column' }}>
125
+ <SectionTitle text={isAdminForm ? 'Add an Admin' : 'Ban User from Scene'} />
126
+ {isAdminForm ? null : <BanUserDescription />}
127
+ <UiEntity
128
+ uiTransform={{ width: '100%', height: 44, flexDirection: 'row', alignItems: 'center' }}
129
+ >
130
+ <Input
131
+ value={value}
132
+ placeholder="Enter a NAME or wallet address"
133
+ fontSize={14}
134
+ disabled={props.disabled}
135
+ onChange={(next) => {
136
+ setError('')
137
+ setValue(next)
138
+ }}
139
+ onSubmit={(next) => {
140
+ void submit(next)
141
+ }}
142
+ uiBackground={{ color: WHITE }}
143
+ uiTransform={{
144
+ flexGrow: 1,
145
+ height: 44,
146
+ borderWidth: 3,
147
+ borderRadius: 8,
148
+ borderColor: error === '' ? WHITE : RED
149
+ }}
150
+ />
151
+ {props.allowKick ? (
152
+ <Button
153
+ label="Kick"
154
+ variant="secondary"
155
+ disabled={props.disabled || loading}
156
+ fontSize={15}
157
+ onClick={kick}
158
+ uiTransform={{ width: 74, height: 44, margin: { left: 8 } }}
159
+ />
160
+ ) : null}
161
+ <Button
162
+ label={loading ? '...' : isAdminForm ? 'Add' : 'Ban'}
163
+ variant={isAdminForm ? 'primary' : 'danger'}
164
+ disabled={props.disabled || loading}
165
+ fontSize={16}
166
+ onClick={() => {
167
+ void submit(value)
168
+ }}
169
+ uiTransform={{ width: 84, height: 44, margin: { left: 8 } }}
170
+ />
171
+ </UiEntity>
172
+ {error === '' ? null : <ErrorLine icon={props.icons.error} text={error} />}
173
+ {props.disabled && props.disabledHint !== '' ? (
174
+ <Label
175
+ value={props.disabledHint}
176
+ fontSize={13}
177
+ color={BADGE_GRAY}
178
+ textAlign="middle-left"
179
+ uiTransform={{ width: '100%', height: 'auto', margin: { top: 6 } }}
180
+ />
181
+ ) : null}
182
+ </UiEntity>
183
+ )
184
+ }
@@ -0,0 +1,93 @@
1
+ import ReactEcs, { Label, UiEntity } from '@dcl/sdk/react-ecs'
2
+ import type { SceneAdmin } from '../../api'
3
+ import { deleteSceneAdmin } from './api'
4
+ import type { ModerationIcons } from './icons'
5
+ import { moderationView, ModerationView, openView, showMessage } from './state'
6
+ import { BADGE_GRAY, Button, ErrorLine, WHITE } from './ui'
7
+ import { displayName } from './utils'
8
+
9
+ interface Props {
10
+ admin: SceneAdmin
11
+ icons: ModerationIcons
12
+ onChanged: () => void
13
+ }
14
+
15
+ export function RemoveAdminConfirmation(props: Props): ReactEcs.JSX.Element {
16
+ const label = displayName(props.admin)
17
+
18
+ const confirm = async (): Promise<void> => {
19
+ if (moderationView.removing) return
20
+ moderationView.removing = true
21
+ const [failure] = await deleteSceneAdmin(props.admin.address)
22
+ moderationView.removing = false
23
+ if (failure !== null) {
24
+ moderationView.removeError = failure
25
+ return
26
+ }
27
+ showMessage(`${label} is no longer an admin`)
28
+ openView(ModerationView.ADMIN_LIST)
29
+ props.onChanged()
30
+ }
31
+
32
+ return (
33
+ <UiEntity
34
+ uiTransform={{
35
+ width: '100%',
36
+ height: 'auto',
37
+ flexDirection: 'column',
38
+ alignItems: 'center'
39
+ }}
40
+ >
41
+ <UiEntity
42
+ uiTransform={{ width: '100%', height: 'auto' }}
43
+ uiText={{
44
+ value: `Are you sure you want to remove <b><color=#FF2D55>${label}</color></b> from the Admin list?`,
45
+ fontSize: 16,
46
+ color: WHITE,
47
+ textAlign: 'top-left'
48
+ }}
49
+ />
50
+ <Label
51
+ value="If you proceed, they will lose access to the Admin Tools for this scene."
52
+ fontSize={13}
53
+ color={BADGE_GRAY}
54
+ textAlign="middle-left"
55
+ uiTransform={{ width: '100%', height: 'auto', margin: { top: 10, bottom: 16 } }}
56
+ />
57
+ <UiEntity
58
+ uiTransform={{
59
+ width: '100%',
60
+ height: 42,
61
+ flexDirection: 'row',
62
+ justifyContent: 'center',
63
+ alignItems: 'center'
64
+ }}
65
+ >
66
+ <Button
67
+ label="Cancel"
68
+ variant="secondary"
69
+ disabled={moderationView.removing}
70
+ onClick={() => openView(ModerationView.ADMIN_LIST)}
71
+ uiTransform={{ width: 120, height: 40, margin: { right: 12 } }}
72
+ />
73
+ <Button
74
+ label={moderationView.removing ? 'Removing...' : 'Remove'}
75
+ variant="danger"
76
+ color={WHITE}
77
+ disabled={moderationView.removing}
78
+ onClick={() => {
79
+ void confirm()
80
+ }}
81
+ uiTransform={{ width: 160, height: 40 }}
82
+ />
83
+ </UiEntity>
84
+ {moderationView.removeError === '' ? null : (
85
+ <ErrorLine
86
+ icon={props.icons.error}
87
+ text={moderationView.removeError}
88
+ uiTransform={{ margin: { top: 14 } }}
89
+ />
90
+ )}
91
+ </UiEntity>
92
+ )
93
+ }
@@ -0,0 +1,228 @@
1
+ import ReactEcs, { Label, UiEntity } from '@dcl/sdk/react-ecs'
2
+ import type { Key } from '@dcl/sdk/react-ecs'
3
+ import type { SceneAdmin, SceneBanUser } from '../../api'
4
+ import { deleteSceneBan } from './api'
5
+ import type { ModerationIcons } from './icons'
6
+ import {
7
+ clampPage,
8
+ moderationView,
9
+ ModerationView,
10
+ openView,
11
+ pageCount,
12
+ showMessage,
13
+ USERS_PER_PAGE
14
+ } from './state'
15
+ import {
16
+ ADDRESS_GRAY,
17
+ BADGE_GRAY,
18
+ BLACK,
19
+ Button,
20
+ DANGER,
21
+ Divider,
22
+ ListHeader,
23
+ WHITE
24
+ } from './ui'
25
+ import { addressOf, displayName, userName } from './utils'
26
+
27
+ export enum UserListType {
28
+ ADMIN = 'admin',
29
+ BAN = 'ban'
30
+ }
31
+
32
+ interface Props {
33
+ type: UserListType
34
+ users: Array<SceneAdmin | SceneBanUser>
35
+ icons: ModerationIcons
36
+ canManage: boolean
37
+ onChanged: () => void
38
+ }
39
+
40
+ function counterText(type: UserListType, count: number): string {
41
+ if (type === UserListType.ADMIN) return `(${count} ${count === 1 ? 'admin' : 'admins'})`
42
+ return `(${count} ${count === 1 ? 'user' : 'users'})`
43
+ }
44
+
45
+ function canRemove(user: SceneAdmin | SceneBanUser): boolean {
46
+ return 'canBeRemoved' in user ? user.canBeRemoved : true
47
+ }
48
+
49
+ function UserRow(props: {
50
+ key?: Key
51
+ user: SceneAdmin | SceneBanUser
52
+ type: UserListType
53
+ icons: ModerationIcons
54
+ canManage: boolean
55
+ onAction: (user: SceneAdmin | SceneBanUser) => void
56
+ }): ReactEcs.JSX.Element {
57
+ const name = userName(props.user)
58
+ const address = addressOf(props.user)
59
+ const isOwner = props.type === UserListType.ADMIN && !canRemove(props.user)
60
+ const showAction = props.canManage && (props.type === UserListType.BAN || canRemove(props.user))
61
+ return (
62
+ <UiEntity uiTransform={{ width: '100%', height: 'auto', flexDirection: 'column' }}>
63
+ <UiEntity
64
+ uiTransform={{
65
+ width: '100%',
66
+ height: 46,
67
+ flexDirection: 'row',
68
+ alignItems: 'center',
69
+ padding: { left: 4, right: 4 }
70
+ }}
71
+ >
72
+ <UiEntity
73
+ uiTransform={{ width: 24, height: 24, flexShrink: 0, margin: { right: 8 } }}
74
+ uiBackground={{
75
+ textureMode: 'stretch',
76
+ texture: { src: props.icons.person },
77
+ color: WHITE
78
+ }}
79
+ />
80
+ <UiEntity
81
+ uiTransform={{ flexGrow: 1, height: '100%', flexDirection: 'column', justifyContent: 'center' }}
82
+ >
83
+ {name === '' ? null : (
84
+ <UiEntity uiTransform={{ height: 18, flexDirection: 'row', alignItems: 'center' }}>
85
+ <Label value={`<b>${name}</b>`} fontSize={14} color={WHITE} />
86
+ {name.includes('#') ? null : (
87
+ <UiEntity
88
+ uiTransform={{ width: 13, height: 13, flexShrink: 0, margin: { left: 4 } }}
89
+ uiBackground={{
90
+ textureMode: 'stretch',
91
+ texture: { src: props.icons.verified },
92
+ color: WHITE
93
+ }}
94
+ />
95
+ )}
96
+ {isOwner ? (
97
+ <UiEntity
98
+ uiTransform={{
99
+ height: 18,
100
+ alignItems: 'center',
101
+ justifyContent: 'center',
102
+ borderRadius: 4,
103
+ margin: { left: 8 },
104
+ padding: { left: 5, right: 5 }
105
+ }}
106
+ uiBackground={{ color: BADGE_GRAY }}
107
+ >
108
+ <Label value="<b>Owner</b>" fontSize={11} color={BLACK} />
109
+ </UiEntity>
110
+ ) : null}
111
+ </UiEntity>
112
+ )}
113
+ <Label
114
+ value={address}
115
+ fontSize={name === '' ? 13 : 11}
116
+ color={name === '' ? WHITE : ADDRESS_GRAY}
117
+ textAlign="middle-left"
118
+ uiTransform={{ height: 16 }}
119
+ />
120
+ </UiEntity>
121
+ {showAction ? (
122
+ <Button
123
+ label={props.type === UserListType.ADMIN ? 'Remove' : 'Unban'}
124
+ variant="text"
125
+ color={DANGER}
126
+ fontSize={14}
127
+ onClick={() => props.onAction(props.user)}
128
+ uiTransform={{ width: 78, height: 32, flexShrink: 0 }}
129
+ />
130
+ ) : null}
131
+ </UiEntity>
132
+ <Divider uiTransform={{ margin: { top: 0, bottom: 0 } }} />
133
+ </UiEntity>
134
+ )
135
+ }
136
+
137
+ export function UsersList(props: Props): ReactEcs.JSX.Element {
138
+ const page = clampPage(props.users.length)
139
+ const pages = pageCount(props.users.length)
140
+ const start = (page - 1) * USERS_PER_PAGE
141
+ const visible = props.users.slice(start, start + USERS_PER_PAGE)
142
+ const isAdminList = props.type === UserListType.ADMIN
143
+
144
+ const onAction = (user: SceneAdmin | SceneBanUser): void => {
145
+ if (isAdminList) {
146
+ moderationView.adminToRemove = 'address' in user ? user : null
147
+ moderationView.view = ModerationView.CONFIRM_REMOVE
148
+ return
149
+ }
150
+ const address = addressOf(user)
151
+ const label = displayName(user)
152
+ void deleteSceneBan(address).then(([failure]) => {
153
+ showMessage(
154
+ failure === null
155
+ ? `${label} has been unbanned from your scene`
156
+ : 'We were unable to unban this user'
157
+ )
158
+ if (failure === null) props.onChanged()
159
+ })
160
+ }
161
+
162
+ return (
163
+ <UiEntity uiTransform={{ width: '100%', height: 'auto', flexDirection: 'column' }}>
164
+ <ListHeader
165
+ icon={isAdminList ? props.icons.verified : props.icons.ban}
166
+ title={isAdminList ? 'ADMIN LIST' : 'SCENE BAN LIST'}
167
+ counter={counterText(props.type, props.users.length)}
168
+ closeIcon={props.icons.close}
169
+ onClose={() => openView(ModerationView.MAIN)}
170
+ />
171
+ {props.users.length === 0 ? (
172
+ <Label
173
+ value={isAdminList ? 'No admins yet.' : 'Nobody is banned from this scene.'}
174
+ fontSize={14}
175
+ color={BADGE_GRAY}
176
+ textAlign="middle-left"
177
+ uiTransform={{ width: '100%', height: 40 }}
178
+ />
179
+ ) : null}
180
+ {visible.map((user) => (
181
+ <UserRow
182
+ key={addressOf(user)}
183
+ user={user}
184
+ type={props.type}
185
+ icons={props.icons}
186
+ canManage={props.canManage}
187
+ onAction={onAction}
188
+ />
189
+ ))}
190
+ {props.users.length > USERS_PER_PAGE ? (
191
+ <UiEntity
192
+ uiTransform={{
193
+ width: '100%',
194
+ height: 44,
195
+ flexDirection: 'row',
196
+ alignItems: 'center',
197
+ justifyContent: 'space-between',
198
+ margin: { top: 10 }
199
+ }}
200
+ >
201
+ <Button
202
+ label="Prev"
203
+ variant="secondary"
204
+ icon={props.icons.chevronBack}
205
+ disabled={page <= 1}
206
+ fontSize={14}
207
+ onClick={() => {
208
+ moderationView.page = page - 1
209
+ }}
210
+ uiTransform={{ width: 104, height: 36 }}
211
+ />
212
+ <Label value={`${page} / ${pages}`} fontSize={14} color={WHITE} />
213
+ <Button
214
+ label="Next"
215
+ variant="secondary"
216
+ iconRight={props.icons.chevronForward}
217
+ disabled={page >= pages}
218
+ fontSize={14}
219
+ onClick={() => {
220
+ moderationView.page = page + 1
221
+ }}
222
+ uiTransform={{ width: 104, height: 36 }}
223
+ />
224
+ </UiEntity>
225
+ ) : null}
226
+ </UiEntity>
227
+ )
228
+ }
@@ -0,0 +1,35 @@
1
+ // The comms-gatekeeper calls this tab needs on top of scripts/api.ts. The host and
2
+ // the signedFetch wrapper come from there; only the request shapes the shared module
3
+ // does not expose (admin-by-NAME, the /scene-bans writes) are declared here.
4
+ import { ENDPOINTS, request, type Result } from '../../api'
5
+
6
+ export type AdminRef = { admin: string } | { name: string }
7
+ export type BanRef = { banned_address: string } | { banned_name: string }
8
+
9
+ export async function postSceneAdmin(ref: AdminRef): Promise<Result<unknown>> {
10
+ return request({
11
+ url: ENDPOINTS.sceneAdmin(),
12
+ init: { method: 'POST', headers: {}, body: JSON.stringify(ref) }
13
+ })
14
+ }
15
+
16
+ export async function deleteSceneAdmin(address: string): Promise<Result<unknown>> {
17
+ return request({
18
+ url: ENDPOINTS.sceneAdmin(),
19
+ init: { method: 'DELETE', headers: {}, body: JSON.stringify({ admin: address }) }
20
+ })
21
+ }
22
+
23
+ export async function postSceneBan(ref: BanRef): Promise<Result<unknown>> {
24
+ return request({
25
+ url: ENDPOINTS.sceneBans(),
26
+ init: { method: 'POST', headers: {}, body: JSON.stringify(ref) }
27
+ })
28
+ }
29
+
30
+ export async function deleteSceneBan(address: string): Promise<Result<unknown>> {
31
+ return request({
32
+ url: ENDPOINTS.sceneBans(),
33
+ init: { method: 'DELETE', headers: {}, body: JSON.stringify({ banned_address: address }) }
34
+ })
35
+ }
@@ -0,0 +1,23 @@
1
+ import { iconPath } from '../../icons'
2
+
3
+ export interface ModerationIcons {
4
+ verified: string
5
+ person: string
6
+ ban: string
7
+ close: string
8
+ chevronBack: string
9
+ chevronForward: string
10
+ error: string
11
+ }
12
+
13
+ export function moderationIcons(base: string): ModerationIcons {
14
+ return {
15
+ verified: iconPath(base, 'moderation/admin-panel-verified-user.png'),
16
+ person: iconPath(base, 'moderation/person-outline.png'),
17
+ ban: iconPath(base, 'moderation/ban.png'),
18
+ close: iconPath(base, 'moderation/close.png'),
19
+ chevronBack: iconPath(base, 'moderation/chevron-back.png'),
20
+ chevronForward: iconPath(base, 'moderation/chevron-forward.png'),
21
+ error: iconPath(base, 'moderation/error.png')
22
+ }
23
+ }
@@ -0,0 +1,95 @@
1
+ // Kick: `moderationControl.kickCoordinates` is a scene-relative spot the creator
2
+ // authors in the inspector, and a scene can only move ITS OWN player. So a kick is a
3
+ // comms command — the targeted client is the one that calls movePlayerTo on itself.
4
+ //
5
+ // The listener is registered at module load (admin.tsx imports the tab statically),
6
+ // not when the tab mounts: the player being kicked is by definition not the admin
7
+ // looking at the panel, so nothing of the panel UI is mounted on their client.
8
+ //
9
+ // Trust: the receiver re-reads /scene-admin and checks the sender against it rather
10
+ // than the panel's cached list, which only admins ever populate. Sending goes
11
+ // through communicationsController.send() for the same reason as message-bus.ts —
12
+ // MessageBus.emit()'s internal flush queue drops follow-up messages.
13
+ import { engine } from '@dcl/sdk/ecs'
14
+ import { MessageBus } from '@dcl/sdk/message-bus'
15
+ import { getPlayer } from '@dcl/sdk/players'
16
+ import { send as commsSend } from '~system/CommunicationsController'
17
+ import { movePlayerTo } from '~system/RestrictedActions'
18
+ import { getSceneAdmins, isPreview, toSceneAdmins } from '../../api'
19
+ import { AdminTools } from '../../components'
20
+
21
+ const KICK = 'admin:kick'
22
+
23
+ interface KickTarget {
24
+ address: string
25
+ name: string
26
+ }
27
+
28
+ function isRecord(value: unknown): value is Record<string, unknown> {
29
+ return typeof value === 'object' && value !== null
30
+ }
31
+
32
+ function readTarget(payload: unknown): KickTarget | null {
33
+ if (!isRecord(payload)) return null
34
+ const address = typeof payload.address === 'string' ? payload.address.toLowerCase() : ''
35
+ const name = typeof payload.name === 'string' ? payload.name.toLowerCase() : ''
36
+ if (address === '' && name === '') return null
37
+ return { address, name }
38
+ }
39
+
40
+ function isLocalPlayer(target: KickTarget): boolean {
41
+ const player = getPlayer()
42
+ if (player === null) return false
43
+ if (target.address !== '' && player.userId.toLowerCase() === target.address) return true
44
+ return target.name !== '' && player.name.toLowerCase() === target.name
45
+ }
46
+
47
+ function kickCoordinates(): { x: number; y: number; z: number } | null {
48
+ for (const [, config] of engine.getEntitiesWith(AdminTools)) {
49
+ if (!config.moderationControl.isEnabled) return null
50
+ const { x, y, z } = config.moderationControl.kickCoordinates
51
+ return { x, y, z }
52
+ }
53
+ return null
54
+ }
55
+
56
+ async function senderIsAdmin(sender: string): Promise<boolean> {
57
+ if (isPreview()) return true
58
+ if (sender === 'self') return false
59
+ const [error, response] = await getSceneAdmins()
60
+ if (error !== null) return false
61
+ return toSceneAdmins(response).some((admin) => admin.address === sender.toLowerCase())
62
+ }
63
+
64
+ async function handleKick(payload: unknown, sender: string): Promise<void> {
65
+ const target = readTarget(payload)
66
+ if (target === null || !isLocalPlayer(target)) return
67
+ const position = kickCoordinates()
68
+ if (position === null) return
69
+ if (!(await senderIsAdmin(sender))) return
70
+ try {
71
+ await movePlayerTo({ newRelativePosition: position })
72
+ } catch (error) {
73
+ console.log('admin-tools: kick could not move the player', error)
74
+ }
75
+ }
76
+
77
+ // Constructed on first listen, never at module load: MessageBus subscribes to
78
+ // the legacy comms event, which the Multiplayer Server does not implement — and
79
+ // this module loads on the server too, where the constructor would throw
80
+ // "not implemented" and take the rest of the scene's construction with it.
81
+ let receiver: MessageBus | null = null
82
+
83
+ /** Client only. Arms the kick listener; a second call is a no-op. */
84
+ export function listenForKicks(): void {
85
+ if (receiver !== null) return
86
+ receiver = new MessageBus()
87
+ receiver.on(KICK, (payload: unknown, sender: string) => {
88
+ void handleKick(payload, sender)
89
+ })
90
+ }
91
+
92
+ export function kickUser(value: string, byAddress: boolean): void {
93
+ const payload = byAddress ? { address: value } : { name: value }
94
+ commsSend({ message: JSON.stringify({ message: KICK, payload }) }).catch(() => {})
95
+ }