@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.
- package/editor.css +1 -0
- package/editor.js +33891 -0
- package/package.json +8 -4
- package/prefabs/admin-tools/composite.json +77 -0
- package/prefabs/admin-tools/data.json +28 -0
- package/prefabs/admin-tools/icons/admin-panel-control-button.png +0 -0
- package/prefabs/admin-tools/icons/admin-panel-moderation-control-button.png +0 -0
- package/prefabs/admin-tools/icons/admin-panel-rewards-control-button.png +0 -0
- package/prefabs/admin-tools/icons/admin-panel-smart-item-control-button.png +0 -0
- package/prefabs/admin-tools/icons/admin-panel-text-announcement-control-button.png +0 -0
- package/prefabs/admin-tools/icons/admin-panel-video-control-button.png +0 -0
- package/prefabs/admin-tools/icons/admin-tool-background.png +0 -0
- package/prefabs/admin-tools/icons/announcements/text-announcement-chat-message.png +0 -0
- package/prefabs/admin-tools/icons/announcements/text-announcement-check.png +0 -0
- package/prefabs/admin-tools/icons/announcements/text-announcement-close-button.png +0 -0
- package/prefabs/admin-tools/icons/moderation/admin-panel-verified-user.png +0 -0
- package/prefabs/admin-tools/icons/moderation/ban.png +0 -0
- package/prefabs/admin-tools/icons/moderation/chevron-back.png +0 -0
- package/prefabs/admin-tools/icons/moderation/chevron-forward.png +0 -0
- package/prefabs/admin-tools/icons/moderation/close.png +0 -0
- package/prefabs/admin-tools/icons/moderation/error.png +0 -0
- package/prefabs/admin-tools/icons/moderation/person-outline.png +0 -0
- package/prefabs/admin-tools/icons/moderation-control-icon.png +0 -0
- package/prefabs/admin-tools/icons/rewards/close.png +0 -0
- package/prefabs/admin-tools/icons/rewards/send.png +0 -0
- package/prefabs/admin-tools/icons/rewards-control.png +0 -0
- package/prefabs/admin-tools/icons/smart-item-control.png +0 -0
- package/prefabs/admin-tools/icons/text-announcement-control.png +0 -0
- package/prefabs/admin-tools/icons/video/chevron-down.png +0 -0
- package/prefabs/admin-tools/icons/video/chevron-up.png +0 -0
- package/prefabs/admin-tools/icons/video/copy-to-clipboard.png +0 -0
- package/prefabs/admin-tools/icons/video/error.png +0 -0
- package/prefabs/admin-tools/icons/video/eye-off.png +0 -0
- package/prefabs/admin-tools/icons/video/eye.png +0 -0
- package/prefabs/admin-tools/icons/video/help.png +0 -0
- package/prefabs/admin-tools/icons/video/info.png +0 -0
- package/prefabs/admin-tools/icons/video/person-outline.png +0 -0
- package/prefabs/admin-tools/icons/video/star.png +0 -0
- package/prefabs/admin-tools/icons/video/video-control-dcl-cast.png +0 -0
- package/prefabs/admin-tools/icons/video/video-control-live.png +0 -0
- package/prefabs/admin-tools/icons/video/video-control-loop.png +0 -0
- package/prefabs/admin-tools/icons/video/video-control-mute.png +0 -0
- package/prefabs/admin-tools/icons/video/video-control-pause-button.png +0 -0
- package/prefabs/admin-tools/icons/video/video-control-play-button.png +0 -0
- package/prefabs/admin-tools/icons/video/video-control-video-icon.png +0 -0
- package/prefabs/admin-tools/icons/video/video-control-volume-minus-button.png +0 -0
- package/prefabs/admin-tools/icons/video/video-control-volume-plus-button.png +0 -0
- package/prefabs/admin-tools/icons/video-control.png +0 -0
- package/prefabs/admin-tools/scripts/actions.ts +206 -0
- package/prefabs/admin-tools/scripts/admin.tsx +253 -0
- package/prefabs/admin-tools/scripts/api.ts +125 -0
- package/prefabs/admin-tools/scripts/components.ts +190 -0
- package/prefabs/admin-tools/scripts/icons.ts +53 -0
- package/prefabs/admin-tools/scripts/message-bus.ts +297 -0
- package/prefabs/admin-tools/scripts/state.ts +69 -0
- package/prefabs/admin-tools/scripts/tabs/Moderation.tsx +155 -0
- package/prefabs/admin-tools/scripts/tabs/Rewards.tsx +136 -0
- package/prefabs/admin-tools/scripts/tabs/SmartItemActions.tsx +167 -0
- package/prefabs/admin-tools/scripts/tabs/TextAnnouncements.tsx +195 -0
- package/prefabs/admin-tools/scripts/tabs/VideoControl.tsx +156 -0
- package/prefabs/admin-tools/scripts/tabs/announcements/Overlay.tsx +94 -0
- package/prefabs/admin-tools/scripts/tabs/announcements/icons.ts +15 -0
- package/prefabs/admin-tools/scripts/tabs/announcements/state.ts +71 -0
- package/prefabs/admin-tools/scripts/tabs/announcements/ui.tsx +37 -0
- package/prefabs/admin-tools/scripts/tabs/moderation/AddUserInput.tsx +184 -0
- package/prefabs/admin-tools/scripts/tabs/moderation/RemoveAdminConfirmation.tsx +93 -0
- package/prefabs/admin-tools/scripts/tabs/moderation/UsersList.tsx +228 -0
- package/prefabs/admin-tools/scripts/tabs/moderation/api.ts +35 -0
- package/prefabs/admin-tools/scripts/tabs/moderation/icons.ts +23 -0
- package/prefabs/admin-tools/scripts/tabs/moderation/kick.ts +95 -0
- package/prefabs/admin-tools/scripts/tabs/moderation/state.ts +69 -0
- package/prefabs/admin-tools/scripts/tabs/moderation/ui.tsx +172 -0
- package/prefabs/admin-tools/scripts/tabs/moderation/utils.ts +88 -0
- package/prefabs/admin-tools/scripts/tabs/rewards/Button.tsx +52 -0
- package/prefabs/admin-tools/scripts/tabs/rewards/Captcha.tsx +88 -0
- package/prefabs/admin-tools/scripts/tabs/rewards/api.ts +138 -0
- package/prefabs/admin-tools/scripts/tabs/rewards/claim.ts +99 -0
- package/prefabs/admin-tools/scripts/tabs/rewards/controller.ts +103 -0
- package/prefabs/admin-tools/scripts/tabs/rewards/icons.ts +13 -0
- package/prefabs/admin-tools/scripts/tabs/rewards/state.ts +32 -0
- package/prefabs/admin-tools/scripts/tabs/smart-items/selection.ts +90 -0
- package/prefabs/admin-tools/scripts/tabs/video/DclCast.tsx +226 -0
- package/prefabs/admin-tools/scripts/tabs/video/LiveStream.tsx +284 -0
- package/prefabs/admin-tools/scripts/tabs/video/SpeakerShowcase.tsx +112 -0
- package/prefabs/admin-tools/scripts/tabs/video/VideoUrl.tsx +107 -0
- package/prefabs/admin-tools/scripts/tabs/video/Volume.tsx +69 -0
- package/prefabs/admin-tools/scripts/tabs/video/api.ts +172 -0
- package/prefabs/admin-tools/scripts/tabs/video/controls.ts +108 -0
- package/prefabs/admin-tools/scripts/tabs/video/icons.ts +59 -0
- package/prefabs/admin-tools/scripts/tabs/video/speakers.ts +43 -0
- package/prefabs/admin-tools/scripts/tabs/video/state.ts +82 -0
- package/prefabs/admin-tools/scripts/tabs/video/ui.tsx +243 -0
- package/prefabs/admin-tools/scripts/types.ts +46 -0
- package/prefabs/admin-tools/scripts/ui.tsx +122 -0
- package/prefabs/admin-tools/thumbnail.png +0 -0
- package/prefabs/announcer/ai.md +76 -0
- package/prefabs/announcer/composite.json +31 -0
- package/prefabs/announcer/data.json +35 -0
- package/prefabs/announcer/scripts/announcer.tsx +79 -0
- package/prefabs/announcer/scripts/pure/toast.ts +30 -0
- package/prefabs/announcer/thumbnail.png +0 -0
- package/prefabs/black-chair/HighBackChair.glb +0 -0
- package/prefabs/black-chair/composite.json +74 -0
- package/prefabs/black-chair/data.json +30 -0
- package/prefabs/black-chair/scripts/seat.ts +191 -0
- package/prefabs/black-chair/thumbnail.png +0 -0
- package/prefabs/catalog.json +1 -0
- package/prefabs/classic-bench/Bench_01.glb +0 -0
- package/prefabs/classic-bench/composite.json +100 -0
- package/prefabs/classic-bench/data.json +34 -0
- package/prefabs/classic-bench/file1.png +0 -0
- package/prefabs/classic-bench/scripts/seat.ts +191 -0
- package/prefabs/classic-bench/thumbnail.png +0 -0
- package/prefabs/classic-bench-armrests/Bench 1.glb +0 -0
- package/prefabs/classic-bench-armrests/composite.json +100 -0
- package/prefabs/classic-bench-armrests/data.json +30 -0
- package/prefabs/classic-bench-armrests/scripts/seat.ts +191 -0
- package/prefabs/classic-bench-armrests/thumbnail.png +0 -0
- package/prefabs/classroom-chair/Chair_02.glb +0 -0
- package/prefabs/classroom-chair/composite.json +74 -0
- package/prefabs/classroom-chair/data.json +34 -0
- package/prefabs/classroom-chair/file1.png +0 -0
- package/prefabs/classroom-chair/scripts/seat.ts +191 -0
- package/prefabs/classroom-chair/thumbnail.png +0 -0
- package/prefabs/court-chair/Chair_Wood_01.glb +0 -0
- package/prefabs/court-chair/FanstasyPack_TX.png +0 -0
- package/prefabs/court-chair/composite.json +74 -0
- package/prefabs/court-chair/data.json +34 -0
- package/prefabs/court-chair/scripts/seat.ts +191 -0
- package/prefabs/court-chair/thumbnail.png +0 -0
- package/prefabs/curved-couch/CurvedCouch.glb +0 -0
- package/prefabs/curved-couch/composite.json +100 -0
- package/prefabs/curved-couch/data.json +30 -0
- package/prefabs/curved-couch/scripts/seat.ts +191 -0
- package/prefabs/curved-couch/thumbnail.png +0 -0
- package/prefabs/game-flow/ai.md +94 -0
- package/prefabs/game-flow/composite.json +45 -0
- package/prefabs/game-flow/data.json +27 -0
- package/prefabs/game-flow/scripts/game-flow.ts +168 -0
- package/prefabs/game-flow/scripts/pure/flow.ts +185 -0
- package/prefabs/game-flow/thumbnail.png +0 -0
- package/prefabs/health-respawn/ai.md +76 -0
- package/prefabs/health-respawn/composite.json +31 -0
- package/prefabs/health-respawn/data.json +38 -0
- package/prefabs/health-respawn/scripts/health-respawn.ts +98 -0
- package/prefabs/health-respawn/scripts/health.ts +22 -0
- package/prefabs/health-respawn/scripts/pure/health.ts +60 -0
- package/prefabs/health-respawn/thumbnail.png +0 -0
- package/prefabs/high-stool/HighStool.glb +0 -0
- package/prefabs/high-stool/composite.json +74 -0
- package/prefabs/high-stool/data.json +30 -0
- package/prefabs/high-stool/scripts/seat.ts +191 -0
- package/prefabs/high-stool/thumbnail.png +0 -0
- package/prefabs/large-couch/ThreeSeater_Couch.glb +0 -0
- package/prefabs/large-couch/composite.json +100 -0
- package/prefabs/large-couch/data.json +30 -0
- package/prefabs/large-couch/scripts/seat.ts +191 -0
- package/prefabs/large-couch/thumbnail.png +0 -0
- package/prefabs/leaderboard/ai.md +75 -0
- package/prefabs/leaderboard/board-panel.glb +0 -0
- package/prefabs/leaderboard/composite.json +82 -0
- package/prefabs/leaderboard/data.json +35 -0
- package/prefabs/leaderboard/scripts/leaderboard.ts +66 -0
- package/prefabs/leaderboard/scripts/pure/board.ts +71 -0
- package/prefabs/leaderboard/thumbnail.png +0 -0
- package/prefabs/loveseat/Couch_01.glb +0 -0
- package/prefabs/loveseat/composite.json +100 -0
- package/prefabs/loveseat/data.json +34 -0
- package/prefabs/loveseat/file1.png +0 -0
- package/prefabs/loveseat/scripts/seat.ts +191 -0
- package/prefabs/loveseat/thumbnail.png +0 -0
- package/prefabs/modern-sofa/ModernSofa.glb +0 -0
- package/prefabs/modern-sofa/composite.json +100 -0
- package/prefabs/modern-sofa/data.json +30 -0
- package/prefabs/modern-sofa/scripts/seat.ts +191 -0
- package/prefabs/modern-sofa/thumbnail.png +0 -0
- package/prefabs/outdoor-chair/Chair_07.glb +0 -0
- package/prefabs/outdoor-chair/composite.json +74 -0
- package/prefabs/outdoor-chair/data.json +34 -0
- package/prefabs/outdoor-chair/file1.png +0 -0
- package/prefabs/outdoor-chair/scripts/seat.ts +191 -0
- package/prefabs/outdoor-chair/thumbnail.png +0 -0
- package/prefabs/rustic-bench/Bench_01.glb +0 -0
- package/prefabs/rustic-bench/PiratesPack_TX.png.png +0 -0
- package/prefabs/rustic-bench/composite.json +100 -0
- package/prefabs/rustic-bench/data.json +34 -0
- package/prefabs/rustic-bench/scripts/seat.ts +191 -0
- package/prefabs/rustic-bench/thumbnail.png +0 -0
- package/prefabs/rustic-round-stool/Chairwood_01.glb +0 -0
- package/prefabs/rustic-round-stool/PiratesPack_TX.png.png +0 -0
- package/prefabs/rustic-round-stool/composite.json +74 -0
- package/prefabs/rustic-round-stool/data.json +34 -0
- package/prefabs/rustic-round-stool/scripts/seat.ts +191 -0
- package/prefabs/rustic-round-stool/thumbnail.png +0 -0
- package/prefabs/rustic-square-stool/Chairwood_02.glb +0 -0
- package/prefabs/rustic-square-stool/PiratesPack_TX.png.png +0 -0
- package/prefabs/rustic-square-stool/composite.json +74 -0
- package/prefabs/rustic-square-stool/data.json +34 -0
- package/prefabs/rustic-square-stool/scripts/seat.ts +191 -0
- package/prefabs/rustic-square-stool/thumbnail.png +0 -0
- package/prefabs/server-clock/ai.md +67 -0
- package/prefabs/server-clock/composite.json +45 -0
- package/prefabs/server-clock/data.json +24 -0
- package/prefabs/server-clock/scripts/clock-hud.tsx +55 -0
- package/prefabs/server-clock/scripts/server-clock.ts +71 -0
- package/prefabs/server-clock/thumbnail.png +0 -0
- package/prefabs/simple-chair/Chair_03.glb +0 -0
- package/prefabs/simple-chair/composite.json +74 -0
- package/prefabs/simple-chair/data.json +34 -0
- package/prefabs/simple-chair/file1.png +0 -0
- package/prefabs/simple-chair/scripts/seat.ts +191 -0
- package/prefabs/simple-chair/thumbnail.png +0 -0
- package/prefabs/sit-spot/composite.json +45 -0
- package/prefabs/sit-spot/data.json +30 -0
- package/prefabs/sit-spot/scripts/seat.ts +191 -0
- package/prefabs/sit-spot/thumbnail.png +0 -0
- package/prefabs/sit-spot-edge/composite.json +42 -0
- package/prefabs/sit-spot-edge/data.json +32 -0
- package/prefabs/sit-spot-edge/emotes/Sit_Edge1_emote.glb +0 -0
- package/prefabs/sit-spot-edge/emotes/Sit_Edge2_emote.glb +0 -0
- package/prefabs/sit-spot-edge/scripts/seat.ts +191 -0
- package/prefabs/sit-spot-edge/thumbnail.png +0 -0
- package/prefabs/small-couch/TwoSeater_Couch.glb +0 -0
- package/prefabs/small-couch/composite.json +100 -0
- package/prefabs/small-couch/data.json +30 -0
- package/prefabs/small-couch/scripts/seat.ts +191 -0
- package/prefabs/small-couch/thumbnail.png +0 -0
- package/prefabs/spawner/ai.md +109 -0
- package/prefabs/spawner/composite.json +73 -0
- package/prefabs/spawner/data.json +22 -0
- package/prefabs/spawner/scripts/spawner.ts +380 -0
- package/prefabs/spawner/thumbnail.png +0 -0
- package/prefabs/steampunk-bench/Steampunk Bench.glb +0 -0
- package/prefabs/steampunk-bench/composite.json +100 -0
- package/prefabs/steampunk-bench/data.json +30 -0
- package/prefabs/steampunk-bench/scripts/seat.ts +191 -0
- package/prefabs/steampunk-bench/thumbnail.png +0 -0
- package/prefabs/steampunk-chair/Steampunk Chair.glb +0 -0
- package/prefabs/steampunk-chair/composite.json +74 -0
- package/prefabs/steampunk-chair/data.json +30 -0
- package/prefabs/steampunk-chair/scripts/seat.ts +191 -0
- package/prefabs/steampunk-chair/thumbnail.png +0 -0
- package/prefabs/tall-stool/Stool_D.glb +0 -0
- package/prefabs/tall-stool/composite.json +74 -0
- package/prefabs/tall-stool/data.json +30 -0
- package/prefabs/tall-stool/scripts/seat.ts +191 -0
- package/prefabs/tall-stool/thumbnail.png +0 -0
- package/prefabs/trigger-zone/ai.md +115 -0
- package/prefabs/trigger-zone/composite.json +52 -0
- package/prefabs/trigger-zone/data.json +31 -0
- package/prefabs/trigger-zone/scripts/trigger-zone.ts +89 -0
- package/prefabs/trigger-zone/thumbnail.png +0 -0
- package/prefabs/video-screen/composite.json +98 -0
- package/prefabs/video-screen/data.json +21 -0
- package/prefabs/video-screen/thumbnail.png +0 -0
- package/prefabs/video-screen/video_player.glb +0 -0
- package/prefabs/wooden-chair/Chair 1.glb +0 -0
- package/prefabs/wooden-chair/composite.json +74 -0
- package/prefabs/wooden-chair/data.json +30 -0
- package/prefabs/wooden-chair/scripts/seat.ts +191 -0
- package/prefabs/wooden-chair/thumbnail.png +0 -0
- package/prefabs/wooden-chair-armrests/Chair 3.glb +0 -0
- package/prefabs/wooden-chair-armrests/composite.json +74 -0
- package/prefabs/wooden-chair-armrests/data.json +30 -0
- package/prefabs/wooden-chair-armrests/scripts/seat.ts +191 -0
- package/prefabs/wooden-chair-armrests/thumbnail.png +0 -0
- package/scene/about +17 -0
- package/scene/bafkreias2w4xuqaic6sdcvblrisayi6xg7drfowzpt5ubjkmrk3mx6czc4 +1 -0
- package/scene/bafkreiby2gmthtauf7cckttphmhpecsueyypi2hzlke4lcodg7fm4cm2sq +23 -0
- package/scene/bafkreidhwqwajqk4nsuffef5fkdrgmp5ql6vnn2knpqsuqalyosv5o6lk4 +1 -0
- package/scene/bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku +0 -0
- package/scene/bafybeihw3xlbff4sf5sc3kvmlmlwf2visnty5lfymawlunzt5vrkp5fd7u +90 -0
- package/web-build/esbuild.wasm +0 -0
- package/web-build/index.js +3 -0
- package/web-build/sdk-snapshot.json +1 -0
- package/web-build/worker.js +701 -0
- 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
|
+
}
|