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