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