@thestatic-tv/dcl-sdk 2.2.10 → 2.3.0-beta.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/README.md +216 -66
- package/dist/index.d.mts +450 -38
- package/dist/index.d.ts +450 -38
- package/dist/index.js +1671 -142
- package/dist/index.mjs +1667 -141
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
AdminPanelUIModule: () => AdminPanelUIModule,
|
|
33
34
|
ChatUIModule: () => ChatUIModule,
|
|
34
35
|
GuideModule: () => GuideModule,
|
|
35
36
|
GuideUIModule: () => GuideUIModule,
|
|
@@ -41,7 +42,9 @@ __export(index_exports, {
|
|
|
41
42
|
StaticTVClient: () => StaticTVClient,
|
|
42
43
|
fetchUserData: () => fetchUserData,
|
|
43
44
|
getPlayerDisplayName: () => getPlayerDisplayName,
|
|
44
|
-
getPlayerWallet: () => getPlayerWallet
|
|
45
|
+
getPlayerWallet: () => getPlayerWallet,
|
|
46
|
+
setupStaticUI: () => setupStaticUI,
|
|
47
|
+
showNotification: () => showNotification
|
|
45
48
|
});
|
|
46
49
|
module.exports = __toCommonJS(index_exports);
|
|
47
50
|
|
|
@@ -136,8 +139,8 @@ function getPlayerWallet() {
|
|
|
136
139
|
return cachedWallet;
|
|
137
140
|
}
|
|
138
141
|
try {
|
|
139
|
-
const { getPlayer:
|
|
140
|
-
const player =
|
|
142
|
+
const { getPlayer: getPlayer3 } = require("@dcl/sdk/players");
|
|
143
|
+
const player = getPlayer3();
|
|
141
144
|
return player?.userId ?? null;
|
|
142
145
|
} catch {
|
|
143
146
|
return null;
|
|
@@ -148,8 +151,8 @@ function getPlayerDisplayName() {
|
|
|
148
151
|
return cachedDisplayName;
|
|
149
152
|
}
|
|
150
153
|
try {
|
|
151
|
-
const { getPlayer:
|
|
152
|
-
const player =
|
|
154
|
+
const { getPlayer: getPlayer3 } = require("@dcl/sdk/players");
|
|
155
|
+
const player = getPlayer3();
|
|
153
156
|
return player?.name ?? null;
|
|
154
157
|
} catch {
|
|
155
158
|
return null;
|
|
@@ -172,7 +175,7 @@ function ensureTimerSystem() {
|
|
|
172
175
|
try {
|
|
173
176
|
timer.callback();
|
|
174
177
|
} catch (e) {
|
|
175
|
-
console.error("[
|
|
178
|
+
console.error("[TheStatic] Timer error");
|
|
176
179
|
}
|
|
177
180
|
}
|
|
178
181
|
}
|
|
@@ -213,7 +216,7 @@ function ensureTimeoutSystem() {
|
|
|
213
216
|
try {
|
|
214
217
|
timeout.callback();
|
|
215
218
|
} catch (e) {
|
|
216
|
-
console.error("[
|
|
219
|
+
console.error("[TheStatic] Timeout error");
|
|
217
220
|
}
|
|
218
221
|
}
|
|
219
222
|
}
|
|
@@ -244,20 +247,41 @@ function dclClearTimeout(timeoutId) {
|
|
|
244
247
|
}
|
|
245
248
|
|
|
246
249
|
// src/modules/session.ts
|
|
250
|
+
function normalizeTier(tier) {
|
|
251
|
+
if (tier === "lite") return "free";
|
|
252
|
+
if (tier === "full") return "standard";
|
|
253
|
+
if (tier === "free" || tier === "standard" || tier === "pro") return tier;
|
|
254
|
+
return "free";
|
|
255
|
+
}
|
|
247
256
|
var SessionModule = class {
|
|
248
257
|
constructor(client) {
|
|
249
258
|
this.sessionId = null;
|
|
259
|
+
this._keyId = null;
|
|
250
260
|
this.heartbeatTimerId = null;
|
|
251
261
|
this.isActive = false;
|
|
252
|
-
this.
|
|
262
|
+
this._tier = "free";
|
|
253
263
|
this.client = client;
|
|
254
264
|
}
|
|
255
265
|
/**
|
|
256
|
-
* Get the
|
|
257
|
-
|
|
266
|
+
* Get the API key ID (used as default sceneId for Pro users)
|
|
267
|
+
*/
|
|
268
|
+
get keyId() {
|
|
269
|
+
return this._keyId;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get the SDK tier returned by the server
|
|
273
|
+
* - free: Session tracking only
|
|
274
|
+
* - standard: Guide, Chat, Heartbeat, Interactions
|
|
275
|
+
* - pro: Everything + Admin Panel
|
|
276
|
+
*/
|
|
277
|
+
get tier() {
|
|
278
|
+
return this._tier;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* @deprecated Use `tier` instead. Returns mapped value for compatibility.
|
|
258
282
|
*/
|
|
259
283
|
get sdkType() {
|
|
260
|
-
return this.
|
|
284
|
+
return this._tier === "free" ? "lite" : "full";
|
|
261
285
|
}
|
|
262
286
|
/**
|
|
263
287
|
* Get the appropriate session endpoint based on key type
|
|
@@ -304,13 +328,12 @@ var SessionModule = class {
|
|
|
304
328
|
});
|
|
305
329
|
if (response.success && response.sessionId) {
|
|
306
330
|
this.sessionId = response.sessionId;
|
|
331
|
+
this._keyId = response.keyId || null;
|
|
307
332
|
this.isActive = true;
|
|
308
|
-
this.
|
|
333
|
+
this._tier = normalizeTier(response.sdkType);
|
|
309
334
|
this.startHeartbeat();
|
|
310
|
-
this.client.log(`Session started: ${this.sessionId},
|
|
311
|
-
|
|
312
|
-
this.client._enableFullFeatures();
|
|
313
|
-
}
|
|
335
|
+
this.client.log(`Session started: ${this.sessionId}, tier: ${this._tier}`);
|
|
336
|
+
this.client._enableFeaturesForTier(this._tier, this._keyId);
|
|
314
337
|
return this.sessionId;
|
|
315
338
|
}
|
|
316
339
|
return null;
|
|
@@ -598,7 +621,8 @@ var UI_DIMENSIONS = {
|
|
|
598
621
|
// Guide UI - positioned to the left of chat (chat is 380px wide at right:20)
|
|
599
622
|
guide: {
|
|
600
623
|
width: 900,
|
|
601
|
-
height:
|
|
624
|
+
height: 580,
|
|
625
|
+
// Numeric value for scaling (matches chat/admin)
|
|
602
626
|
bottom: 55,
|
|
603
627
|
right: 410,
|
|
604
628
|
// 20 + 380 (chat width) + 10 (gap)
|
|
@@ -617,15 +641,33 @@ var UI_DIMENSIONS = {
|
|
|
617
641
|
padding: 15
|
|
618
642
|
}
|
|
619
643
|
},
|
|
620
|
-
// Chat UI -
|
|
644
|
+
// Chat UI - positioned at right side
|
|
621
645
|
chat: {
|
|
622
646
|
width: 380,
|
|
623
647
|
height: 580,
|
|
624
648
|
bottom: 55,
|
|
625
649
|
right: 20,
|
|
650
|
+
headerHeight: 40,
|
|
626
651
|
messagesPerPage: 5,
|
|
627
652
|
channelsPerPage: 6
|
|
628
653
|
},
|
|
654
|
+
// Admin Panel - positioned left of chat
|
|
655
|
+
admin: {
|
|
656
|
+
width: 400,
|
|
657
|
+
height: 580,
|
|
658
|
+
// Match chat height
|
|
659
|
+
maxHeight: 700,
|
|
660
|
+
bottom: 55,
|
|
661
|
+
right: 410,
|
|
662
|
+
// 20 + 380 (chat width) + 10 (gap)
|
|
663
|
+
headerHeight: 48,
|
|
664
|
+
tabHeight: 40,
|
|
665
|
+
footerHeight: 32,
|
|
666
|
+
sectionHeadHeight: 28,
|
|
667
|
+
buttonHeight: 36,
|
|
668
|
+
buttonHeightSmall: 30,
|
|
669
|
+
inputHeight: 36
|
|
670
|
+
},
|
|
629
671
|
// Shared
|
|
630
672
|
closeButton: {
|
|
631
673
|
size: 40,
|
|
@@ -633,18 +675,18 @@ var UI_DIMENSIONS = {
|
|
|
633
675
|
}
|
|
634
676
|
};
|
|
635
677
|
var DEFAULT_CHAT_THEME = {
|
|
636
|
-
header:
|
|
678
|
+
header: 16,
|
|
637
679
|
channelButton: 14,
|
|
638
680
|
channelDropdown: 14,
|
|
639
|
-
systemMessage:
|
|
640
|
-
chatUsername:
|
|
681
|
+
systemMessage: 13,
|
|
682
|
+
chatUsername: 14,
|
|
641
683
|
chatTimestamp: 11,
|
|
642
|
-
chatMessage:
|
|
643
|
-
input:
|
|
684
|
+
chatMessage: 14,
|
|
685
|
+
input: 14,
|
|
644
686
|
sendButton: 14,
|
|
645
|
-
userInfo:
|
|
687
|
+
userInfo: 13,
|
|
646
688
|
authStatus: 12,
|
|
647
|
-
notification:
|
|
689
|
+
notification: 16,
|
|
648
690
|
closeButton: 16
|
|
649
691
|
};
|
|
650
692
|
function scaleChatTheme(theme, fontScale) {
|
|
@@ -664,6 +706,30 @@ function scaleChatTheme(theme, fontScale) {
|
|
|
664
706
|
closeButton: Math.round(theme.closeButton * fontScale)
|
|
665
707
|
};
|
|
666
708
|
}
|
|
709
|
+
var DEFAULT_ADMIN_THEME = {
|
|
710
|
+
header: 16,
|
|
711
|
+
tabButton: 14,
|
|
712
|
+
sectionHead: 13,
|
|
713
|
+
label: 13,
|
|
714
|
+
labelSmall: 11,
|
|
715
|
+
button: 13,
|
|
716
|
+
buttonSmall: 11,
|
|
717
|
+
input: 13,
|
|
718
|
+
status: 12
|
|
719
|
+
};
|
|
720
|
+
function scaleAdminTheme(theme, fontScale) {
|
|
721
|
+
return {
|
|
722
|
+
header: Math.round(theme.header * fontScale),
|
|
723
|
+
tabButton: Math.round(theme.tabButton * fontScale),
|
|
724
|
+
sectionHead: Math.round(theme.sectionHead * fontScale),
|
|
725
|
+
label: Math.round(theme.label * fontScale),
|
|
726
|
+
labelSmall: Math.round(theme.labelSmall * fontScale),
|
|
727
|
+
button: Math.round(theme.button * fontScale),
|
|
728
|
+
buttonSmall: Math.round(theme.buttonSmall * fontScale),
|
|
729
|
+
input: Math.round(theme.input * fontScale),
|
|
730
|
+
status: Math.round(theme.status * fontScale)
|
|
731
|
+
};
|
|
732
|
+
}
|
|
667
733
|
|
|
668
734
|
// src/ui/components.tsx
|
|
669
735
|
var import_react_ecs = __toESM(require("@dcl/sdk/react-ecs"));
|
|
@@ -719,21 +785,21 @@ var PanelHeader = (props) => {
|
|
|
719
785
|
}),
|
|
720
786
|
import_react_ecs.default.createElement(import_react_ecs.UiEntity, { key: "pos-spacer", uiTransform: { width: 6 } })
|
|
721
787
|
] : [],
|
|
722
|
-
// Font controls
|
|
788
|
+
// Font controls (with visual disabled state at limits)
|
|
723
789
|
...props.showFontControls ? [
|
|
724
790
|
import_react_ecs.default.createElement(import_react_ecs.UiEntity, {
|
|
725
791
|
key: "font-down",
|
|
726
792
|
uiTransform: { width: 22, height: 22, justifyContent: "center", alignItems: "center" },
|
|
727
|
-
uiBackground: { color: THEME.colors.buttonBackground },
|
|
728
|
-
onMouseDown: () => props.onFontScaleDown?.(),
|
|
729
|
-
children: [import_react_ecs.default.createElement(import_react_ecs.UiEntity, { uiText: { value: "\u2212", fontSize: 14, color: THEME.colors.cyan } })]
|
|
793
|
+
uiBackground: { color: props.fontScaleAtMin ? import_math2.Color4.create(0.1, 0.1, 0.1, 0.3) : THEME.colors.buttonBackground },
|
|
794
|
+
onMouseDown: () => !props.fontScaleAtMin && props.onFontScaleDown?.(),
|
|
795
|
+
children: [import_react_ecs.default.createElement(import_react_ecs.UiEntity, { uiText: { value: "\u2212", fontSize: 14, color: props.fontScaleAtMin ? THEME.colors.gray : THEME.colors.cyan } })]
|
|
730
796
|
}),
|
|
731
797
|
import_react_ecs.default.createElement(import_react_ecs.UiEntity, {
|
|
732
798
|
key: "font-up",
|
|
733
799
|
uiTransform: { width: 22, height: 22, justifyContent: "center", alignItems: "center" },
|
|
734
|
-
uiBackground: { color: THEME.colors.buttonBackground },
|
|
735
|
-
onMouseDown: () => props.onFontScaleUp?.(),
|
|
736
|
-
children: [import_react_ecs.default.createElement(import_react_ecs.UiEntity, { uiText: { value: "+", fontSize: 14, color: THEME.colors.cyan } })]
|
|
800
|
+
uiBackground: { color: props.fontScaleAtMax ? import_math2.Color4.create(0.1, 0.1, 0.1, 0.3) : THEME.colors.buttonBackground },
|
|
801
|
+
onMouseDown: () => !props.fontScaleAtMax && props.onFontScaleUp?.(),
|
|
802
|
+
children: [import_react_ecs.default.createElement(import_react_ecs.UiEntity, { uiText: { value: "+", fontSize: 14, color: props.fontScaleAtMax ? THEME.colors.gray : THEME.colors.cyan } })]
|
|
737
803
|
}),
|
|
738
804
|
import_react_ecs.default.createElement(import_react_ecs.UiEntity, { key: "font-spacer", uiTransform: { width: 6 } })
|
|
739
805
|
] : [],
|
|
@@ -771,7 +837,6 @@ var GuideUIModule = class {
|
|
|
771
837
|
this.currentPage = 0;
|
|
772
838
|
this.itemsPerPage = 6;
|
|
773
839
|
this.searchQuery = "";
|
|
774
|
-
this.uiScale = 1;
|
|
775
840
|
// Current video tracking (for "PLAYING" indicator)
|
|
776
841
|
this._currentVideoId = null;
|
|
777
842
|
// =============================================================================
|
|
@@ -779,34 +844,46 @@ var GuideUIModule = class {
|
|
|
779
844
|
// =============================================================================
|
|
780
845
|
/**
|
|
781
846
|
* Get the guide UI component for rendering
|
|
782
|
-
*
|
|
847
|
+
* Always renders toggle button, plus panel when visible
|
|
783
848
|
*/
|
|
784
849
|
this.getComponent = () => {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
}
|
|
788
|
-
const windowW = this.s(UI_DIMENSIONS.guide.width);
|
|
850
|
+
const windowW = UI_DIMENSIONS.guide.width;
|
|
851
|
+
const windowH = UI_DIMENSIONS.guide.height;
|
|
789
852
|
return import_react_ecs2.default.createElement(import_react_ecs2.UiEntity, {
|
|
853
|
+
key: `guide-root-${this.client.uiScale}`,
|
|
790
854
|
uiTransform: {
|
|
791
|
-
width:
|
|
792
|
-
height:
|
|
793
|
-
positionType: "absolute"
|
|
794
|
-
position: { bottom: UI_DIMENSIONS.guide.bottom, right: UI_DIMENSIONS.guide.right },
|
|
795
|
-
flexDirection: "row",
|
|
796
|
-
border: { top: 2, bottom: 2, left: 2, right: 2 },
|
|
797
|
-
borderColor: THEME.colors.panelBorder
|
|
855
|
+
width: "100%",
|
|
856
|
+
height: "100%",
|
|
857
|
+
positionType: "absolute"
|
|
798
858
|
},
|
|
799
|
-
uiBackground: { color: THEME.colors.panel },
|
|
800
859
|
children: [
|
|
801
|
-
|
|
802
|
-
this.
|
|
803
|
-
|
|
860
|
+
// Always render toggle button
|
|
861
|
+
this.renderToggleButton(),
|
|
862
|
+
// Render panel when visible - Guide positioned relative to Chat's scaled width
|
|
863
|
+
// Guide.right = Chat.right + Chat.scaledWidth + gap (10px)
|
|
864
|
+
this._isVisible ? import_react_ecs2.default.createElement(import_react_ecs2.UiEntity, {
|
|
865
|
+
key: `guide-panel-${this.client.uiScale}`,
|
|
866
|
+
uiTransform: {
|
|
867
|
+
width: windowW,
|
|
868
|
+
height: windowH,
|
|
869
|
+
positionType: "absolute",
|
|
870
|
+
position: { bottom: UI_DIMENSIONS.guide.bottom, right: UI_DIMENSIONS.chat.right + this.s(UI_DIMENSIONS.chat.width) + 10 },
|
|
871
|
+
flexDirection: "row",
|
|
872
|
+
border: { top: 2, bottom: 2, left: 2, right: 2 },
|
|
873
|
+
borderColor: THEME.colors.panelBorder
|
|
874
|
+
},
|
|
875
|
+
uiBackground: { color: THEME.colors.panel },
|
|
876
|
+
children: [
|
|
877
|
+
this.renderLeftPanel(),
|
|
878
|
+
this.renderRightPanel(),
|
|
879
|
+
this.renderCloseButton()
|
|
880
|
+
]
|
|
881
|
+
}) : null
|
|
804
882
|
]
|
|
805
883
|
});
|
|
806
884
|
};
|
|
807
885
|
this.client = client;
|
|
808
886
|
this.config = config;
|
|
809
|
-
this.uiScale = config.uiScale || 1;
|
|
810
887
|
this._currentVideoId = config.currentVideoId || null;
|
|
811
888
|
}
|
|
812
889
|
/**
|
|
@@ -820,6 +897,8 @@ var GuideUIModule = class {
|
|
|
820
897
|
* Show the guide UI
|
|
821
898
|
*/
|
|
822
899
|
show() {
|
|
900
|
+
if (this._isVisible) return;
|
|
901
|
+
this.client.closeOtherPanels("guide");
|
|
823
902
|
this._isVisible = true;
|
|
824
903
|
this.fetchGuideData().catch(() => {
|
|
825
904
|
});
|
|
@@ -955,7 +1034,7 @@ var GuideUIModule = class {
|
|
|
955
1034
|
// --- UTILITIES ---
|
|
956
1035
|
// =============================================================================
|
|
957
1036
|
s(value) {
|
|
958
|
-
return Math.round(value * this.uiScale);
|
|
1037
|
+
return Math.round(value * this.client.uiScale);
|
|
959
1038
|
}
|
|
960
1039
|
handleVideoSelect(video) {
|
|
961
1040
|
if (this.config.onVideoSelect) {
|
|
@@ -991,15 +1070,26 @@ var GuideUIModule = class {
|
|
|
991
1070
|
import_react_ecs2.default.createElement(import_react_ecs2.UiEntity, {
|
|
992
1071
|
uiTransform: { width: "100%", flexDirection: "column", padding: 10 },
|
|
993
1072
|
children: [
|
|
994
|
-
// Title
|
|
1073
|
+
// Title row
|
|
995
1074
|
import_react_ecs2.default.createElement(import_react_ecs2.UiEntity, {
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1075
|
+
uiTransform: {
|
|
1076
|
+
width: "100%",
|
|
1077
|
+
height: this.s(40),
|
|
1078
|
+
marginBottom: 10,
|
|
1079
|
+
flexDirection: "row",
|
|
1080
|
+
alignItems: "center"
|
|
1001
1081
|
},
|
|
1002
|
-
|
|
1082
|
+
children: [
|
|
1083
|
+
// Title
|
|
1084
|
+
import_react_ecs2.default.createElement(import_react_ecs2.UiEntity, {
|
|
1085
|
+
uiText: {
|
|
1086
|
+
value: "THE STATIC TV",
|
|
1087
|
+
fontSize: this.s(UI_DIMENSIONS.guide.sidebar.headerSize),
|
|
1088
|
+
color: THEME.colors.cyan,
|
|
1089
|
+
textAlign: "middle-left"
|
|
1090
|
+
}
|
|
1091
|
+
})
|
|
1092
|
+
]
|
|
1003
1093
|
}),
|
|
1004
1094
|
// Random Signal button
|
|
1005
1095
|
import_react_ecs2.default.createElement(import_react_ecs2.UiEntity, {
|
|
@@ -1349,23 +1439,25 @@ var GuideUIModule = class {
|
|
|
1349
1439
|
});
|
|
1350
1440
|
}
|
|
1351
1441
|
renderToggleButton() {
|
|
1442
|
+
const buttonText = this._isVisible ? "CLOSE" : "GUIDE";
|
|
1443
|
+
const buttonColor = this._isVisible ? import_math3.Color4.create(0.2, 0.2, 0.28, 0.9) : import_math3.Color4.create(0, 0.5, 0.5, 0.9);
|
|
1444
|
+
const buttonPos = 20 + this.s(100) + 10 + this.s(100) + 10;
|
|
1352
1445
|
return import_react_ecs2.default.createElement(import_react_ecs2.UiEntity, {
|
|
1353
1446
|
uiTransform: {
|
|
1354
1447
|
positionType: "absolute",
|
|
1355
|
-
position: { right:
|
|
1356
|
-
// To the left of CHAT button
|
|
1448
|
+
position: { right: buttonPos, bottom: 10 },
|
|
1357
1449
|
width: this.s(100),
|
|
1358
1450
|
height: this.s(45),
|
|
1359
1451
|
justifyContent: "center",
|
|
1360
1452
|
alignItems: "center"
|
|
1361
1453
|
},
|
|
1362
|
-
uiBackground: { color:
|
|
1363
|
-
onMouseDown: () => this.show(),
|
|
1454
|
+
uiBackground: { color: buttonColor },
|
|
1455
|
+
onMouseDown: () => this._isVisible ? this.hide() : this.show(),
|
|
1364
1456
|
children: [
|
|
1365
1457
|
import_react_ecs2.default.createElement(import_react_ecs2.UiEntity, {
|
|
1366
1458
|
uiText: {
|
|
1367
|
-
value:
|
|
1368
|
-
fontSize: this.s(
|
|
1459
|
+
value: buttonText,
|
|
1460
|
+
fontSize: this.s(14),
|
|
1369
1461
|
color: THEME.colors.white,
|
|
1370
1462
|
textAlign: "middle-center"
|
|
1371
1463
|
}
|
|
@@ -1407,7 +1499,6 @@ var ChatUIModule = class {
|
|
|
1407
1499
|
this.chatScrollOffset = 0;
|
|
1408
1500
|
// UI preferences
|
|
1409
1501
|
this.position = "right";
|
|
1410
|
-
this.fontScale = 1;
|
|
1411
1502
|
// Timers
|
|
1412
1503
|
this.chatTimerId = null;
|
|
1413
1504
|
this.playerInfoTimerId = null;
|
|
@@ -1417,43 +1508,52 @@ var ChatUIModule = class {
|
|
|
1417
1508
|
// =============================================================================
|
|
1418
1509
|
/**
|
|
1419
1510
|
* Get the chat UI component for rendering
|
|
1420
|
-
*
|
|
1511
|
+
* Always renders toggle button, plus panel when visible
|
|
1421
1512
|
*/
|
|
1422
1513
|
this.getComponent = () => {
|
|
1423
|
-
|
|
1424
|
-
return this.renderToggleButton();
|
|
1425
|
-
}
|
|
1426
|
-
const scaledTheme = scaleChatTheme(DEFAULT_CHAT_THEME, this.fontScale);
|
|
1514
|
+
const scaledTheme = scaleChatTheme(DEFAULT_CHAT_THEME, this.client.uiScale);
|
|
1427
1515
|
const positionStyle = this.getPositionStyle();
|
|
1428
1516
|
return import_react_ecs3.default.createElement(import_react_ecs3.UiEntity, {
|
|
1429
1517
|
uiTransform: {
|
|
1430
|
-
width:
|
|
1431
|
-
height:
|
|
1432
|
-
positionType: "absolute"
|
|
1433
|
-
position: positionStyle,
|
|
1434
|
-
// Must be nested object, not spread!
|
|
1435
|
-
flexDirection: "column",
|
|
1436
|
-
border: { top: 2, bottom: 2, left: 2, right: 2 },
|
|
1437
|
-
borderColor: THEME.colors.panelBorder
|
|
1518
|
+
width: "100%",
|
|
1519
|
+
height: "100%",
|
|
1520
|
+
positionType: "absolute"
|
|
1438
1521
|
},
|
|
1439
|
-
uiBackground: { color: THEME.colors.panel },
|
|
1440
1522
|
children: [
|
|
1441
|
-
|
|
1442
|
-
this.
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1523
|
+
// Always render toggle button
|
|
1524
|
+
this.renderToggleButton(),
|
|
1525
|
+
// Render panel when visible - scaled sizes, fixed position
|
|
1526
|
+
// Key includes uiScale to force re-render when scale changes from other panels
|
|
1527
|
+
this._isVisible ? import_react_ecs3.default.createElement(import_react_ecs3.UiEntity, {
|
|
1528
|
+
key: `chat-panel-${this.client.uiScale}`,
|
|
1529
|
+
uiTransform: {
|
|
1530
|
+
width: this.s(UI_DIMENSIONS.chat.width),
|
|
1531
|
+
height: this.s(UI_DIMENSIONS.chat.height),
|
|
1532
|
+
positionType: "absolute",
|
|
1533
|
+
position: { bottom: UI_DIMENSIONS.chat.bottom, right: UI_DIMENSIONS.chat.right },
|
|
1534
|
+
flexDirection: "column",
|
|
1535
|
+
border: { top: 2, bottom: 2, left: 2, right: 2 },
|
|
1536
|
+
borderColor: THEME.colors.panelBorder
|
|
1537
|
+
},
|
|
1538
|
+
uiBackground: { color: THEME.colors.panel },
|
|
1539
|
+
children: [
|
|
1540
|
+
this.renderHeader(),
|
|
1541
|
+
this.renderChannelButton(scaledTheme),
|
|
1542
|
+
this.renderMessagesArea(scaledTheme),
|
|
1543
|
+
import_react_ecs3.default.createElement(import_react_ecs3.UiEntity, {
|
|
1544
|
+
uiTransform: { width: "100%", height: 1, flexShrink: 0 },
|
|
1545
|
+
uiBackground: { color: THEME.colors.panelBorder }
|
|
1546
|
+
}),
|
|
1547
|
+
this.renderUserInfoBar(scaledTheme),
|
|
1548
|
+
this.renderInputArea(scaledTheme),
|
|
1549
|
+
this.renderChannelDropdown(scaledTheme)
|
|
1550
|
+
]
|
|
1551
|
+
}) : null
|
|
1451
1552
|
]
|
|
1452
1553
|
});
|
|
1453
1554
|
};
|
|
1454
1555
|
this.client = client;
|
|
1455
1556
|
this.config = config;
|
|
1456
|
-
this.fontScale = config.fontScale || 1;
|
|
1457
1557
|
}
|
|
1458
1558
|
/**
|
|
1459
1559
|
* Initialize the chat system
|
|
@@ -1472,6 +1572,7 @@ var ChatUIModule = class {
|
|
|
1472
1572
|
* Show the chat UI
|
|
1473
1573
|
*/
|
|
1474
1574
|
show() {
|
|
1575
|
+
if (this._isVisible) return;
|
|
1475
1576
|
this._isVisible = true;
|
|
1476
1577
|
this._unreadCount = 0;
|
|
1477
1578
|
this.chatScrollOffset = 0;
|
|
@@ -1733,6 +1834,10 @@ var ChatUIModule = class {
|
|
|
1733
1834
|
const d = new Date(input);
|
|
1734
1835
|
if (!isNaN(d.getTime())) return d.getTime();
|
|
1735
1836
|
}
|
|
1837
|
+
if (input instanceof Date) return input.getTime();
|
|
1838
|
+
if (typeof input === "object" && "seconds" in input) {
|
|
1839
|
+
return input.seconds * 1e3;
|
|
1840
|
+
}
|
|
1736
1841
|
return 0;
|
|
1737
1842
|
}
|
|
1738
1843
|
formatTime(isoString) {
|
|
@@ -1745,6 +1850,10 @@ var ChatUIModule = class {
|
|
|
1745
1850
|
return "";
|
|
1746
1851
|
}
|
|
1747
1852
|
}
|
|
1853
|
+
/** Scale a dimension by shared uiScale */
|
|
1854
|
+
s(value) {
|
|
1855
|
+
return Math.round(value * this.client.uiScale);
|
|
1856
|
+
}
|
|
1748
1857
|
getPositionStyle() {
|
|
1749
1858
|
return { bottom: 55, right: 20 };
|
|
1750
1859
|
}
|
|
@@ -1752,15 +1861,9 @@ var ChatUIModule = class {
|
|
|
1752
1861
|
return PanelHeader({
|
|
1753
1862
|
title: "LIVE CHAT",
|
|
1754
1863
|
fontSize: 14,
|
|
1755
|
-
fontScale:
|
|
1864
|
+
fontScale: 1,
|
|
1756
1865
|
showPositionControls: false,
|
|
1757
|
-
showFontControls:
|
|
1758
|
-
onFontScaleUp: () => {
|
|
1759
|
-
this.fontScale = Math.min(1.4, this.fontScale + 0.1);
|
|
1760
|
-
},
|
|
1761
|
-
onFontScaleDown: () => {
|
|
1762
|
-
this.fontScale = Math.max(0.7, this.fontScale - 0.1);
|
|
1763
|
-
},
|
|
1866
|
+
showFontControls: false,
|
|
1764
1867
|
onClose: () => this.hide()
|
|
1765
1868
|
});
|
|
1766
1869
|
}
|
|
@@ -2135,23 +2238,24 @@ var ChatUIModule = class {
|
|
|
2135
2238
|
}
|
|
2136
2239
|
renderToggleButton() {
|
|
2137
2240
|
const unreadBadge = this._unreadCount > 0 ? ` (${this._unreadCount})` : "";
|
|
2241
|
+
const buttonText = this._isVisible ? "CLOSE" : `CHAT${unreadBadge}`;
|
|
2242
|
+
const buttonColor = this._isVisible ? import_math4.Color4.create(0.2, 0.2, 0.28, 0.9) : import_math4.Color4.create(0.6, 0, 0.5, 0.9);
|
|
2138
2243
|
return import_react_ecs3.default.createElement(import_react_ecs3.UiEntity, {
|
|
2139
2244
|
uiTransform: {
|
|
2140
2245
|
positionType: "absolute",
|
|
2141
2246
|
position: { right: 20, bottom: 10 },
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
height: 45,
|
|
2247
|
+
width: this.s(100),
|
|
2248
|
+
height: this.s(45),
|
|
2145
2249
|
justifyContent: "center",
|
|
2146
2250
|
alignItems: "center"
|
|
2147
2251
|
},
|
|
2148
|
-
uiBackground: { color:
|
|
2149
|
-
onMouseDown: () => this.show(),
|
|
2252
|
+
uiBackground: { color: buttonColor },
|
|
2253
|
+
onMouseDown: () => this._isVisible ? this.hide() : this.show(),
|
|
2150
2254
|
children: [
|
|
2151
2255
|
import_react_ecs3.default.createElement(import_react_ecs3.UiEntity, {
|
|
2152
2256
|
uiText: {
|
|
2153
|
-
value:
|
|
2154
|
-
fontSize:
|
|
2257
|
+
value: buttonText,
|
|
2258
|
+
fontSize: this.s(14),
|
|
2155
2259
|
color: THEME.colors.white,
|
|
2156
2260
|
textAlign: "middle-center"
|
|
2157
2261
|
}
|
|
@@ -2161,6 +2265,1232 @@ var ChatUIModule = class {
|
|
|
2161
2265
|
}
|
|
2162
2266
|
};
|
|
2163
2267
|
|
|
2268
|
+
// src/ui/admin-panel-ui.tsx
|
|
2269
|
+
var import_react_ecs4 = __toESM(require("@dcl/sdk/react-ecs"));
|
|
2270
|
+
var import_math5 = require("@dcl/sdk/math");
|
|
2271
|
+
var import_players2 = require("@dcl/sdk/players");
|
|
2272
|
+
var import_RestrictedActions3 = require("~system/RestrictedActions");
|
|
2273
|
+
var BAN_KICK_POSITION = import_math5.Vector3.create(16, -50, 16);
|
|
2274
|
+
var C = {
|
|
2275
|
+
bg: import_math5.Color4.create(0.08, 0.08, 0.12, 0.98),
|
|
2276
|
+
header: import_math5.Color4.create(0.9, 0.15, 0.15, 1),
|
|
2277
|
+
tabActive: import_math5.Color4.create(0, 0.7, 0.7, 1),
|
|
2278
|
+
tabInactive: import_math5.Color4.create(0.15, 0.15, 0.2, 1),
|
|
2279
|
+
section: import_math5.Color4.create(0.12, 0.12, 0.18, 1),
|
|
2280
|
+
btn: import_math5.Color4.create(0.2, 0.2, 0.28, 1),
|
|
2281
|
+
cyan: import_math5.Color4.create(0, 0.8, 0.8, 1),
|
|
2282
|
+
magenta: import_math5.Color4.create(0.85, 0.2, 0.55, 1),
|
|
2283
|
+
yellow: import_math5.Color4.create(0.95, 0.75, 0.1, 1),
|
|
2284
|
+
green: import_math5.Color4.create(0.2, 0.75, 0.3, 1),
|
|
2285
|
+
red: import_math5.Color4.create(0.85, 0.2, 0.2, 1),
|
|
2286
|
+
purple: import_math5.Color4.create(0.6, 0.3, 0.85, 1),
|
|
2287
|
+
orange: import_math5.Color4.create(0.95, 0.5, 0.15, 1),
|
|
2288
|
+
text: import_math5.Color4.White(),
|
|
2289
|
+
textDim: import_math5.Color4.create(0.6, 0.6, 0.7, 1)
|
|
2290
|
+
};
|
|
2291
|
+
var AdminPanelUIModule = class {
|
|
2292
|
+
// UI scaling - uses shared client.uiScale
|
|
2293
|
+
constructor(client, config) {
|
|
2294
|
+
// State
|
|
2295
|
+
this.isAdmin = false;
|
|
2296
|
+
this.isOwner = false;
|
|
2297
|
+
this.panelOpen = false;
|
|
2298
|
+
this.activeTab = "video";
|
|
2299
|
+
this.playerWallet = "";
|
|
2300
|
+
// Video tab state
|
|
2301
|
+
this.customVideoUrl = "";
|
|
2302
|
+
this.streamData = null;
|
|
2303
|
+
this.streamFetched = false;
|
|
2304
|
+
this.videoState = null;
|
|
2305
|
+
this.videoStateFetched = false;
|
|
2306
|
+
this.channelCreating = false;
|
|
2307
|
+
this.channelCreateError = "";
|
|
2308
|
+
this.channelDeleting = false;
|
|
2309
|
+
this.channelDeleteError = "";
|
|
2310
|
+
this.keyRotating = false;
|
|
2311
|
+
this.keyRotateStatus = "";
|
|
2312
|
+
this.streamControlling = false;
|
|
2313
|
+
this.streamControlStatus = "";
|
|
2314
|
+
this.pollIntervalId = null;
|
|
2315
|
+
this.trialClaiming = false;
|
|
2316
|
+
this.trialClaimError = "";
|
|
2317
|
+
// Mod tab state
|
|
2318
|
+
this.sceneAdmins = [];
|
|
2319
|
+
this.bannedWallets = [];
|
|
2320
|
+
this.newAdminWallet = "";
|
|
2321
|
+
this.newBanWallet = "";
|
|
2322
|
+
this.broadcastText = "";
|
|
2323
|
+
this.modStatus = "";
|
|
2324
|
+
this.modsFetched = false;
|
|
2325
|
+
// --- UI Components ---
|
|
2326
|
+
this.SectionHead = ({ label, color }) => /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2327
|
+
import_react_ecs4.UiEntity,
|
|
2328
|
+
{
|
|
2329
|
+
uiTransform: { width: "100%", height: this.s(UI_DIMENSIONS.admin.sectionHeadHeight), margin: { bottom: 8 }, padding: { left: 10 }, alignItems: "center" },
|
|
2330
|
+
uiBackground: { color: import_math5.Color4.create(color.r * 0.3, color.g * 0.3, color.b * 0.3, 0.5) }
|
|
2331
|
+
},
|
|
2332
|
+
/* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: label, fontSize: this.theme.sectionHead, color })
|
|
2333
|
+
);
|
|
2334
|
+
this.TabBtn = ({ label, tab }) => /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2335
|
+
import_react_ecs4.Button,
|
|
2336
|
+
{
|
|
2337
|
+
uiTransform: { flexGrow: 1, height: this.s(UI_DIMENSIONS.admin.tabHeight), justifyContent: "center", alignItems: "center" },
|
|
2338
|
+
uiBackground: { color: this.activeTab === tab ? C.tabActive : C.tabInactive },
|
|
2339
|
+
value: label,
|
|
2340
|
+
fontSize: this.theme.tabButton,
|
|
2341
|
+
color: this.activeTab === tab ? import_math5.Color4.Black() : C.text,
|
|
2342
|
+
textAlign: "middle-center",
|
|
2343
|
+
onMouseDown: () => this.setActiveTab(tab)
|
|
2344
|
+
}
|
|
2345
|
+
);
|
|
2346
|
+
this.VideoTab = () => {
|
|
2347
|
+
if (!this.streamFetched) {
|
|
2348
|
+
this.fetchStreamData();
|
|
2349
|
+
}
|
|
2350
|
+
const t = this.theme;
|
|
2351
|
+
return /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", width: "100%", padding: 10 } }, !this.streamData?.hasChannel && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(this.SectionHead, { label: "LIVE STREAM", color: C.btn }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "No streaming channel linked", fontSize: t.labelSmall, color: C.textDim, uiTransform: { margin: { bottom: 8 } } }), this.isOwner && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column" } }, this.streamData?.trialAvailable && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 10 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2352
|
+
import_react_ecs4.Button,
|
|
2353
|
+
{
|
|
2354
|
+
uiTransform: { width: this.s(200), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: { bottom: 6 } },
|
|
2355
|
+
uiBackground: { color: this.trialClaiming ? C.btn : C.green },
|
|
2356
|
+
value: this.trialClaiming ? "Claiming..." : "Start Free 4-Hour Trial",
|
|
2357
|
+
fontSize: t.button,
|
|
2358
|
+
color: C.text,
|
|
2359
|
+
onMouseDown: () => this.claimTrial()
|
|
2360
|
+
}
|
|
2361
|
+
), this.trialClaimError && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: this.trialClaimError, fontSize: t.status, color: C.red }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "One-time trial \u2022 4 hours of streaming", fontSize: t.labelSmall, color: C.textDim, uiTransform: { margin: { top: 4 } } })), !this.streamData?.trialAvailable && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column" } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2362
|
+
import_react_ecs4.Button,
|
|
2363
|
+
{
|
|
2364
|
+
uiTransform: { width: this.s(170), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: { bottom: 6 } },
|
|
2365
|
+
uiBackground: { color: this.channelCreating ? C.btn : C.cyan },
|
|
2366
|
+
value: this.channelCreating ? "Creating..." : "+ Create Channel",
|
|
2367
|
+
fontSize: t.button,
|
|
2368
|
+
color: C.text,
|
|
2369
|
+
onMouseDown: () => this.createChannel()
|
|
2370
|
+
}
|
|
2371
|
+
), this.channelCreateError && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: this.channelCreateError, fontSize: t.status, color: C.red }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Relay tier \u2022 $25/mo \u2022 8 hours streaming", fontSize: t.labelSmall, color: C.textDim, uiTransform: { margin: { top: 4 } } })))), this.streamData?.hasChannel && this.isOwner && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(this.SectionHead, { label: this.streamData.isLive ? "LIVE STREAM" : "STREAM SETTINGS", color: this.streamData.isLive ? C.red : C.yellow }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 8 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2372
|
+
import_react_ecs4.Label,
|
|
2373
|
+
{
|
|
2374
|
+
value: this.streamData.isLive ? `LIVE \u2022 ${this.streamData.currentViewers || 0} viewers` : "OFFLINE",
|
|
2375
|
+
fontSize: t.label,
|
|
2376
|
+
color: this.streamData.isLive ? C.red : C.textDim
|
|
2377
|
+
}
|
|
2378
|
+
), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: ` \u2022 ${this.streamData.tier?.toUpperCase() || "RELAY"}`, fontSize: t.labelSmall, color: C.cyan }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: ` \u2022 ${this.streamData.sparksBalance || 0} Sparks`, fontSize: t.labelSmall, color: C.textDim })), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2379
|
+
import_react_ecs4.Label,
|
|
2380
|
+
{
|
|
2381
|
+
value: `Channel: ${this.streamData.channelName || this.streamData.channelId}`,
|
|
2382
|
+
fontSize: t.labelSmall,
|
|
2383
|
+
color: C.textDim,
|
|
2384
|
+
uiTransform: { margin: { bottom: 10 } }
|
|
2385
|
+
}
|
|
2386
|
+
), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "RTMP Server:", fontSize: t.labelSmall, color: C.textDim }), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2387
|
+
import_react_ecs4.Label,
|
|
2388
|
+
{
|
|
2389
|
+
value: this.streamData.rtmpUrl || "Loading...",
|
|
2390
|
+
fontSize: t.label,
|
|
2391
|
+
color: this.streamData.rtmpUrl ? C.text : C.textDim,
|
|
2392
|
+
uiTransform: { margin: { left: 6 } }
|
|
2393
|
+
}
|
|
2394
|
+
)), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Stream Key:", fontSize: t.labelSmall, color: C.textDim }), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2395
|
+
import_react_ecs4.Label,
|
|
2396
|
+
{
|
|
2397
|
+
value: this.streamData.streamKey || "Loading...",
|
|
2398
|
+
fontSize: t.label,
|
|
2399
|
+
color: this.streamData.streamKey ? C.cyan : C.textDim,
|
|
2400
|
+
uiTransform: { margin: { left: 6 } }
|
|
2401
|
+
}
|
|
2402
|
+
)), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 10 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "HLS Playback:", fontSize: t.labelSmall, color: C.textDim }), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2403
|
+
import_react_ecs4.Label,
|
|
2404
|
+
{
|
|
2405
|
+
value: this.streamData.hlsUrl || "Not available",
|
|
2406
|
+
fontSize: t.label,
|
|
2407
|
+
color: this.streamData.hlsUrl ? C.green : C.textDim,
|
|
2408
|
+
uiTransform: { margin: { left: 6 } }
|
|
2409
|
+
}
|
|
2410
|
+
)), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 6 } } }, !this.streamData.isLive && /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2411
|
+
import_react_ecs4.Button,
|
|
2412
|
+
{
|
|
2413
|
+
uiTransform: { width: this.s(110), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2414
|
+
uiBackground: { color: this.streamControlling ? C.btn : C.green },
|
|
2415
|
+
value: this.streamControlling ? "Starting..." : "Start Stream",
|
|
2416
|
+
fontSize: t.buttonSmall,
|
|
2417
|
+
color: C.text,
|
|
2418
|
+
onMouseDown: () => this.startStream()
|
|
2419
|
+
}
|
|
2420
|
+
), this.streamData.isLive && /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2421
|
+
import_react_ecs4.Button,
|
|
2422
|
+
{
|
|
2423
|
+
uiTransform: { width: this.s(110), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2424
|
+
uiBackground: { color: this.streamControlling ? C.btn : C.red },
|
|
2425
|
+
value: this.streamControlling ? "Stopping..." : "Stop Stream",
|
|
2426
|
+
fontSize: t.buttonSmall,
|
|
2427
|
+
color: C.text,
|
|
2428
|
+
onMouseDown: () => this.stopStream()
|
|
2429
|
+
}
|
|
2430
|
+
), this.streamData.isLive && this.streamData.hlsUrl && /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2431
|
+
import_react_ecs4.Button,
|
|
2432
|
+
{
|
|
2433
|
+
uiTransform: { width: this.s(100), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2434
|
+
uiBackground: { color: C.cyan },
|
|
2435
|
+
value: "Play on Screen",
|
|
2436
|
+
fontSize: t.buttonSmall,
|
|
2437
|
+
color: C.text,
|
|
2438
|
+
onMouseDown: () => this.config.onVideoPlay?.(this.streamData.hlsUrl)
|
|
2439
|
+
}
|
|
2440
|
+
)), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 6 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2441
|
+
import_react_ecs4.Button,
|
|
2442
|
+
{
|
|
2443
|
+
uiTransform: { width: this.s(80), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2444
|
+
uiBackground: { color: C.btn },
|
|
2445
|
+
value: "Refresh",
|
|
2446
|
+
fontSize: t.buttonSmall,
|
|
2447
|
+
color: C.text,
|
|
2448
|
+
onMouseDown: () => this.refreshStreamStatus()
|
|
2449
|
+
}
|
|
2450
|
+
), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2451
|
+
import_react_ecs4.Button,
|
|
2452
|
+
{
|
|
2453
|
+
uiTransform: { width: this.s(95), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2454
|
+
uiBackground: { color: this.keyRotating ? C.btn : C.yellow },
|
|
2455
|
+
value: this.keyRotating ? "..." : "Rotate Key",
|
|
2456
|
+
fontSize: t.buttonSmall,
|
|
2457
|
+
color: C.text,
|
|
2458
|
+
onMouseDown: () => this.rotateStreamKey()
|
|
2459
|
+
}
|
|
2460
|
+
), !this.streamData.isLive && /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2461
|
+
import_react_ecs4.Button,
|
|
2462
|
+
{
|
|
2463
|
+
uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2464
|
+
uiBackground: { color: this.channelDeleting ? C.btn : C.red },
|
|
2465
|
+
value: this.channelDeleting ? "..." : "Delete",
|
|
2466
|
+
fontSize: t.buttonSmall,
|
|
2467
|
+
color: C.text,
|
|
2468
|
+
onMouseDown: () => this.deleteChannel()
|
|
2469
|
+
}
|
|
2470
|
+
)), this.streamControlStatus === "started" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Stream started - begin broadcasting in OBS", fontSize: t.status, color: C.green }), this.streamControlStatus === "stopped" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Stream stopped", fontSize: t.status, color: C.textDim }), this.keyRotateStatus === "success" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Key rotated! Update OBS", fontSize: t.status, color: C.green }), this.keyRotateStatus === "error" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Failed to rotate key", fontSize: t.status, color: C.red }), this.channelDeleteError && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: this.channelDeleteError, fontSize: t.status, color: C.red }), this.streamControlStatus === "error" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Stream control failed", fontSize: t.status, color: C.red })), /* @__PURE__ */ import_react_ecs4.default.createElement(this.SectionHead, { label: "PLAY NOW", color: C.orange }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2471
|
+
import_react_ecs4.Input,
|
|
2472
|
+
{
|
|
2473
|
+
uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
|
|
2474
|
+
uiBackground: { color: import_math5.Color4.create(0.15, 0.15, 0.2, 1) },
|
|
2475
|
+
placeholder: "Video URL...",
|
|
2476
|
+
placeholderColor: C.textDim,
|
|
2477
|
+
color: C.text,
|
|
2478
|
+
fontSize: t.input,
|
|
2479
|
+
value: this.customVideoUrl,
|
|
2480
|
+
onChange: (val) => {
|
|
2481
|
+
this.customVideoUrl = val;
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2485
|
+
import_react_ecs4.Button,
|
|
2486
|
+
{
|
|
2487
|
+
uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
|
|
2488
|
+
uiBackground: { color: C.green },
|
|
2489
|
+
value: "Play",
|
|
2490
|
+
fontSize: t.button,
|
|
2491
|
+
color: C.text,
|
|
2492
|
+
onMouseDown: () => {
|
|
2493
|
+
if (this.customVideoUrl) this.config.onVideoPlay?.(this.customVideoUrl);
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2497
|
+
import_react_ecs4.Button,
|
|
2498
|
+
{
|
|
2499
|
+
uiTransform: { width: this.s(65), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 6 } },
|
|
2500
|
+
uiBackground: { color: C.btn },
|
|
2501
|
+
value: "Clear",
|
|
2502
|
+
fontSize: t.button,
|
|
2503
|
+
color: C.text,
|
|
2504
|
+
onMouseDown: () => {
|
|
2505
|
+
this.customVideoUrl = "";
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
)), /* @__PURE__ */ import_react_ecs4.default.createElement(this.SectionHead, { label: "PLAYBACK", color: C.green }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2509
|
+
import_react_ecs4.Button,
|
|
2510
|
+
{
|
|
2511
|
+
uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2512
|
+
uiBackground: { color: C.green },
|
|
2513
|
+
value: "Play",
|
|
2514
|
+
fontSize: t.button,
|
|
2515
|
+
color: C.text,
|
|
2516
|
+
onMouseDown: () => this.config.onCommand?.("videoPlay", { playing: true })
|
|
2517
|
+
}
|
|
2518
|
+
), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2519
|
+
import_react_ecs4.Button,
|
|
2520
|
+
{
|
|
2521
|
+
uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2522
|
+
uiBackground: { color: C.red },
|
|
2523
|
+
value: "Stop",
|
|
2524
|
+
fontSize: t.button,
|
|
2525
|
+
color: C.text,
|
|
2526
|
+
onMouseDown: () => this.config.onVideoStop?.()
|
|
2527
|
+
}
|
|
2528
|
+
), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2529
|
+
import_react_ecs4.Button,
|
|
2530
|
+
{
|
|
2531
|
+
uiTransform: { width: this.s(100), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2532
|
+
uiBackground: { color: C.btn },
|
|
2533
|
+
value: "Reset Default",
|
|
2534
|
+
fontSize: t.buttonSmall,
|
|
2535
|
+
color: C.text,
|
|
2536
|
+
onMouseDown: () => this.config.onCommand?.("videoClear", {})
|
|
2537
|
+
}
|
|
2538
|
+
)), /* @__PURE__ */ import_react_ecs4.default.createElement(this.SectionHead, { label: "VIDEO SLOTS", color: C.cyan }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 10 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Button, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C.cyan }, value: "Play 1", fontSize: t.button, color: C.text, onMouseDown: () => this.playSlot("slot1") }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Button, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C.cyan }, value: "Play 2", fontSize: t.button, color: C.text, onMouseDown: () => this.playSlot("slot2") }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Button, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C.cyan }, value: "Play 3", fontSize: t.button, color: C.text, onMouseDown: () => this.playSlot("slot3") }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Button, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C.cyan }, value: "Play 4", fontSize: t.button, color: C.text, onMouseDown: () => this.playSlot("slot4") }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Button, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C.cyan }, value: "Play 5", fontSize: t.button, color: C.text, onMouseDown: () => this.playSlot("slot5") })), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2539
|
+
import_react_ecs4.Button,
|
|
2540
|
+
{
|
|
2541
|
+
uiTransform: { height: this.s(24) },
|
|
2542
|
+
uiBackground: { color: import_math5.Color4.create(0, 0, 0, 0) },
|
|
2543
|
+
value: "Edit slots at thestatic.tv \u2192",
|
|
2544
|
+
fontSize: t.labelSmall,
|
|
2545
|
+
color: C.cyan,
|
|
2546
|
+
onMouseDown: () => (0, import_RestrictedActions3.openExternalUrl)({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
|
|
2547
|
+
}
|
|
2548
|
+
));
|
|
2549
|
+
};
|
|
2550
|
+
this.ModTab = () => {
|
|
2551
|
+
const t = this.theme;
|
|
2552
|
+
return /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", width: "100%", padding: 10 } }, this.modStatus === "loading" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Loading...", fontSize: t.status, color: C.yellow }), this.modStatus === "saved" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Saved!", fontSize: t.status, color: C.green }), this.modStatus === "error" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Error - check input", fontSize: t.status, color: C.red }), /* @__PURE__ */ import_react_ecs4.default.createElement(this.SectionHead, { label: "BROADCAST", color: C.orange }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2553
|
+
import_react_ecs4.Input,
|
|
2554
|
+
{
|
|
2555
|
+
uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
|
|
2556
|
+
uiBackground: { color: import_math5.Color4.create(0.15, 0.15, 0.2, 1) },
|
|
2557
|
+
placeholder: "Message to all players...",
|
|
2558
|
+
placeholderColor: C.textDim,
|
|
2559
|
+
color: C.text,
|
|
2560
|
+
fontSize: t.input,
|
|
2561
|
+
value: this.broadcastText,
|
|
2562
|
+
onChange: (val) => {
|
|
2563
|
+
this.broadcastText = val;
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2567
|
+
import_react_ecs4.Button,
|
|
2568
|
+
{
|
|
2569
|
+
uiTransform: { width: this.s(65), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
|
|
2570
|
+
uiBackground: { color: C.orange },
|
|
2571
|
+
value: "Send",
|
|
2572
|
+
fontSize: t.button,
|
|
2573
|
+
color: C.text,
|
|
2574
|
+
onMouseDown: () => this.sendBroadcast()
|
|
2575
|
+
}
|
|
2576
|
+
)), /* @__PURE__ */ import_react_ecs4.default.createElement(this.SectionHead, { label: "CHAOS MODE", color: C.red }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2577
|
+
import_react_ecs4.Button,
|
|
2578
|
+
{
|
|
2579
|
+
uiTransform: { width: this.s(130), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: 4 },
|
|
2580
|
+
uiBackground: { color: C.red },
|
|
2581
|
+
value: "KICK ALL",
|
|
2582
|
+
fontSize: t.button,
|
|
2583
|
+
color: C.text,
|
|
2584
|
+
onMouseDown: () => {
|
|
2585
|
+
this.log("KICK ALL clicked");
|
|
2586
|
+
this.config.onCommand?.("kickAll", {});
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
)), /* @__PURE__ */ import_react_ecs4.default.createElement(this.SectionHead, { label: "SCENE ADMINS", color: C.purple }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, this.sceneAdmins.length === 0 && this.modStatus !== "loading" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "No scene admins", fontSize: t.labelSmall, color: C.textDim }), this.sceneAdmins.map((wallet, i) => /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2590
|
+
import_react_ecs4.UiEntity,
|
|
2591
|
+
{
|
|
2592
|
+
key: `admin-${i}`,
|
|
2593
|
+
uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 5 }, width: "100%" }
|
|
2594
|
+
},
|
|
2595
|
+
/* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2596
|
+
import_react_ecs4.Label,
|
|
2597
|
+
{
|
|
2598
|
+
value: `${wallet.slice(0, 6)}...${wallet.slice(-4)}`,
|
|
2599
|
+
fontSize: t.label,
|
|
2600
|
+
color: C.text,
|
|
2601
|
+
uiTransform: { width: this.s(130) }
|
|
2602
|
+
}
|
|
2603
|
+
),
|
|
2604
|
+
/* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2605
|
+
import_react_ecs4.Button,
|
|
2606
|
+
{
|
|
2607
|
+
uiTransform: { width: this.s(70), height: this.s(28), margin: { left: 8 } },
|
|
2608
|
+
uiBackground: { color: C.btn },
|
|
2609
|
+
value: "Remove",
|
|
2610
|
+
fontSize: t.buttonSmall,
|
|
2611
|
+
color: C.text,
|
|
2612
|
+
onMouseDown: () => this.removeSceneAdmin(wallet)
|
|
2613
|
+
}
|
|
2614
|
+
)
|
|
2615
|
+
))), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2616
|
+
import_react_ecs4.Input,
|
|
2617
|
+
{
|
|
2618
|
+
uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
|
|
2619
|
+
uiBackground: { color: import_math5.Color4.create(0.15, 0.15, 0.2, 1) },
|
|
2620
|
+
placeholder: "0x... add admin",
|
|
2621
|
+
placeholderColor: C.textDim,
|
|
2622
|
+
color: C.text,
|
|
2623
|
+
fontSize: t.input,
|
|
2624
|
+
value: this.newAdminWallet,
|
|
2625
|
+
onChange: (val) => {
|
|
2626
|
+
this.newAdminWallet = val;
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2630
|
+
import_react_ecs4.Button,
|
|
2631
|
+
{
|
|
2632
|
+
uiTransform: { width: this.s(60), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
|
|
2633
|
+
uiBackground: { color: C.purple },
|
|
2634
|
+
value: "Add",
|
|
2635
|
+
fontSize: t.button,
|
|
2636
|
+
color: C.text,
|
|
2637
|
+
onMouseDown: () => {
|
|
2638
|
+
if (this.newAdminWallet) this.addSceneAdmin(this.newAdminWallet);
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
)), /* @__PURE__ */ import_react_ecs4.default.createElement(this.SectionHead, { label: "BANNED WALLETS", color: C.red }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, this.bannedWallets.length === 0 && this.modStatus !== "loading" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "No banned wallets", fontSize: t.labelSmall, color: C.textDim }), this.bannedWallets.map((wallet, i) => /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2642
|
+
import_react_ecs4.UiEntity,
|
|
2643
|
+
{
|
|
2644
|
+
key: `ban-${i}`,
|
|
2645
|
+
uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 5 }, width: "100%" }
|
|
2646
|
+
},
|
|
2647
|
+
/* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2648
|
+
import_react_ecs4.Label,
|
|
2649
|
+
{
|
|
2650
|
+
value: `${wallet.slice(0, 6)}...${wallet.slice(-4)}`,
|
|
2651
|
+
fontSize: t.label,
|
|
2652
|
+
color: C.red,
|
|
2653
|
+
uiTransform: { width: this.s(130) }
|
|
2654
|
+
}
|
|
2655
|
+
),
|
|
2656
|
+
/* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2657
|
+
import_react_ecs4.Button,
|
|
2658
|
+
{
|
|
2659
|
+
uiTransform: { width: this.s(70), height: this.s(28), margin: { left: 8 } },
|
|
2660
|
+
uiBackground: { color: C.green },
|
|
2661
|
+
value: "Unban",
|
|
2662
|
+
fontSize: t.buttonSmall,
|
|
2663
|
+
color: C.text,
|
|
2664
|
+
onMouseDown: () => this.unbanWallet(wallet)
|
|
2665
|
+
}
|
|
2666
|
+
)
|
|
2667
|
+
))), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 10 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2668
|
+
import_react_ecs4.Input,
|
|
2669
|
+
{
|
|
2670
|
+
uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
|
|
2671
|
+
uiBackground: { color: import_math5.Color4.create(0.15, 0.15, 0.2, 1) },
|
|
2672
|
+
placeholder: "0x... ban wallet",
|
|
2673
|
+
placeholderColor: C.textDim,
|
|
2674
|
+
color: C.text,
|
|
2675
|
+
fontSize: t.input,
|
|
2676
|
+
value: this.newBanWallet,
|
|
2677
|
+
onChange: (val) => {
|
|
2678
|
+
this.newBanWallet = val;
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2682
|
+
import_react_ecs4.Button,
|
|
2683
|
+
{
|
|
2684
|
+
uiTransform: { width: this.s(60), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
|
|
2685
|
+
uiBackground: { color: C.red },
|
|
2686
|
+
value: "Ban",
|
|
2687
|
+
fontSize: t.button,
|
|
2688
|
+
color: C.text,
|
|
2689
|
+
onMouseDown: () => {
|
|
2690
|
+
if (this.newBanWallet) this.banWallet(this.newBanWallet);
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
)), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2694
|
+
import_react_ecs4.Button,
|
|
2695
|
+
{
|
|
2696
|
+
uiTransform: { height: this.s(24) },
|
|
2697
|
+
uiBackground: { color: import_math5.Color4.create(0, 0, 0, 0) },
|
|
2698
|
+
value: "Manage at thestatic.tv \u2192",
|
|
2699
|
+
fontSize: t.labelSmall,
|
|
2700
|
+
color: C.cyan,
|
|
2701
|
+
onMouseDown: () => (0, import_RestrictedActions3.openExternalUrl)({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
|
|
2702
|
+
}
|
|
2703
|
+
));
|
|
2704
|
+
};
|
|
2705
|
+
/**
|
|
2706
|
+
* Get the React-ECS component for the admin panel
|
|
2707
|
+
*/
|
|
2708
|
+
this.getComponent = () => {
|
|
2709
|
+
if (!this.isAdmin) return null;
|
|
2710
|
+
const t = this.theme;
|
|
2711
|
+
const tabs = [];
|
|
2712
|
+
if (this.config.sceneTabs && this.config.sceneTabs.length > 0) {
|
|
2713
|
+
this.config.sceneTabs.forEach((tab) => tabs.push({ label: tab.label, id: tab.id }));
|
|
2714
|
+
}
|
|
2715
|
+
if (this.config.showVideoTab !== false) {
|
|
2716
|
+
tabs.push({ label: "VIDEO", id: "video" });
|
|
2717
|
+
}
|
|
2718
|
+
if (this.config.showModTab !== false && this.isOwner) {
|
|
2719
|
+
tabs.push({ label: "MOD", id: "mod" });
|
|
2720
|
+
}
|
|
2721
|
+
if (!tabs.find((tab) => tab.id === this.activeTab) && tabs.length > 0) {
|
|
2722
|
+
this.activeTab = tabs[0].id;
|
|
2723
|
+
}
|
|
2724
|
+
return /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2725
|
+
import_react_ecs4.UiEntity,
|
|
2726
|
+
{
|
|
2727
|
+
uiTransform: {
|
|
2728
|
+
width: "100%",
|
|
2729
|
+
height: "100%",
|
|
2730
|
+
positionType: "absolute"
|
|
2731
|
+
}
|
|
2732
|
+
},
|
|
2733
|
+
/* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2734
|
+
import_react_ecs4.UiEntity,
|
|
2735
|
+
{
|
|
2736
|
+
uiTransform: {
|
|
2737
|
+
position: { right: 20 + this.s(100) + 10, bottom: 10 },
|
|
2738
|
+
positionType: "absolute"
|
|
2739
|
+
}
|
|
2740
|
+
},
|
|
2741
|
+
/* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2742
|
+
import_react_ecs4.Button,
|
|
2743
|
+
{
|
|
2744
|
+
uiTransform: { width: this.s(100), height: this.s(45) },
|
|
2745
|
+
uiBackground: { color: this.panelOpen ? C.btn : C.header },
|
|
2746
|
+
value: this.panelOpen ? "CLOSE" : "ADMIN",
|
|
2747
|
+
fontSize: this.s(14),
|
|
2748
|
+
color: C.text,
|
|
2749
|
+
onMouseDown: () => this.toggle()
|
|
2750
|
+
}
|
|
2751
|
+
)
|
|
2752
|
+
),
|
|
2753
|
+
this.panelOpen && /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2754
|
+
import_react_ecs4.UiEntity,
|
|
2755
|
+
{
|
|
2756
|
+
key: `admin-panel-${this.client.uiScale}`,
|
|
2757
|
+
uiTransform: {
|
|
2758
|
+
width: this.s(UI_DIMENSIONS.admin.width),
|
|
2759
|
+
height: this.s(UI_DIMENSIONS.admin.height),
|
|
2760
|
+
maxHeight: this.s(UI_DIMENSIONS.admin.maxHeight),
|
|
2761
|
+
position: { right: UI_DIMENSIONS.chat.right + this.s(UI_DIMENSIONS.chat.width) + 10, bottom: UI_DIMENSIONS.admin.bottom },
|
|
2762
|
+
positionType: "absolute",
|
|
2763
|
+
flexDirection: "column"
|
|
2764
|
+
},
|
|
2765
|
+
uiBackground: { color: C.bg }
|
|
2766
|
+
},
|
|
2767
|
+
/* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2768
|
+
import_react_ecs4.UiEntity,
|
|
2769
|
+
{
|
|
2770
|
+
uiTransform: {
|
|
2771
|
+
width: "100%",
|
|
2772
|
+
height: this.s(UI_DIMENSIONS.admin.headerHeight),
|
|
2773
|
+
justifyContent: "space-between",
|
|
2774
|
+
alignItems: "center",
|
|
2775
|
+
flexDirection: "row",
|
|
2776
|
+
padding: { left: 12, right: 8 }
|
|
2777
|
+
},
|
|
2778
|
+
uiBackground: { color: C.header }
|
|
2779
|
+
},
|
|
2780
|
+
/* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: this.config.title || "ADMIN PANEL", fontSize: t.header, color: C.text }),
|
|
2781
|
+
/* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2782
|
+
import_react_ecs4.Button,
|
|
2783
|
+
{
|
|
2784
|
+
uiTransform: { width: this.s(28), height: this.s(28) },
|
|
2785
|
+
uiBackground: { color: import_math5.Color4.create(0, 0, 0, 0.3) },
|
|
2786
|
+
value: "X",
|
|
2787
|
+
fontSize: t.header,
|
|
2788
|
+
color: THEME.colors.red,
|
|
2789
|
+
onMouseDown: () => this.hide()
|
|
2790
|
+
}
|
|
2791
|
+
)
|
|
2792
|
+
),
|
|
2793
|
+
/* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { width: "100%", height: this.s(UI_DIMENSIONS.admin.tabHeight), flexDirection: "row" } }, tabs.map((tab) => /* @__PURE__ */ import_react_ecs4.default.createElement(this.TabBtn, { label: tab.label, tab: tab.id }))),
|
|
2794
|
+
/* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2795
|
+
import_react_ecs4.UiEntity,
|
|
2796
|
+
{
|
|
2797
|
+
uiTransform: {
|
|
2798
|
+
width: "100%",
|
|
2799
|
+
flexGrow: 1,
|
|
2800
|
+
overflow: "scroll",
|
|
2801
|
+
flexDirection: "column"
|
|
2802
|
+
}
|
|
2803
|
+
},
|
|
2804
|
+
this.activeTab === "video" && /* @__PURE__ */ import_react_ecs4.default.createElement(this.VideoTab, null),
|
|
2805
|
+
this.activeTab === "mod" && this.isOwner && /* @__PURE__ */ import_react_ecs4.default.createElement(this.ModTab, null),
|
|
2806
|
+
this.config.sceneTabs?.map((tab) => this.activeTab === tab.id && tab.render())
|
|
2807
|
+
),
|
|
2808
|
+
/* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2809
|
+
import_react_ecs4.UiEntity,
|
|
2810
|
+
{
|
|
2811
|
+
uiTransform: {
|
|
2812
|
+
width: "100%",
|
|
2813
|
+
height: this.s(UI_DIMENSIONS.admin.footerHeight),
|
|
2814
|
+
justifyContent: "center",
|
|
2815
|
+
alignItems: "center"
|
|
2816
|
+
},
|
|
2817
|
+
uiBackground: { color: C.section }
|
|
2818
|
+
},
|
|
2819
|
+
/* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2820
|
+
import_react_ecs4.Button,
|
|
2821
|
+
{
|
|
2822
|
+
uiTransform: { height: this.s(24) },
|
|
2823
|
+
uiBackground: { color: import_math5.Color4.create(0, 0, 0, 0) },
|
|
2824
|
+
value: `thestatic.tv/scene/${this.config.sceneId} \u2192`,
|
|
2825
|
+
fontSize: t.labelSmall,
|
|
2826
|
+
color: C.cyan,
|
|
2827
|
+
onMouseDown: () => (0, import_RestrictedActions3.openExternalUrl)({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
|
|
2828
|
+
}
|
|
2829
|
+
)
|
|
2830
|
+
)
|
|
2831
|
+
)
|
|
2832
|
+
);
|
|
2833
|
+
};
|
|
2834
|
+
this.client = client;
|
|
2835
|
+
if (!config.sceneId) {
|
|
2836
|
+
throw new Error("[AdminPanel] sceneId is required");
|
|
2837
|
+
}
|
|
2838
|
+
this.config = {
|
|
2839
|
+
showVideoTab: true,
|
|
2840
|
+
showModTab: true,
|
|
2841
|
+
title: "ADMIN PANEL",
|
|
2842
|
+
debug: false,
|
|
2843
|
+
...config,
|
|
2844
|
+
sceneId: config.sceneId
|
|
2845
|
+
// Ensure sceneId is set
|
|
2846
|
+
};
|
|
2847
|
+
this.baseUrl = client.getBaseUrl();
|
|
2848
|
+
if (config.headerColor) {
|
|
2849
|
+
C.header = import_math5.Color4.create(
|
|
2850
|
+
config.headerColor.r,
|
|
2851
|
+
config.headerColor.g,
|
|
2852
|
+
config.headerColor.b,
|
|
2853
|
+
config.headerColor.a
|
|
2854
|
+
);
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
log(msg, ...args) {
|
|
2858
|
+
if (this.config.debug) {
|
|
2859
|
+
console.log(`[AdminPanel] ${msg}`, ...args);
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
/** Scale a dimension by shared uiScale */
|
|
2863
|
+
s(value) {
|
|
2864
|
+
return Math.round(value * this.client.uiScale);
|
|
2865
|
+
}
|
|
2866
|
+
/** Get scaled theme */
|
|
2867
|
+
get theme() {
|
|
2868
|
+
return scaleAdminTheme(DEFAULT_ADMIN_THEME, this.client.uiScale);
|
|
2869
|
+
}
|
|
2870
|
+
/**
|
|
2871
|
+
* Initialize the admin panel - checks admin status and fetches video state
|
|
2872
|
+
*/
|
|
2873
|
+
async init() {
|
|
2874
|
+
await this.checkAdminStatus();
|
|
2875
|
+
await this.fetchVideoState();
|
|
2876
|
+
this.autoPlayDefault();
|
|
2877
|
+
this.log("Initialized");
|
|
2878
|
+
}
|
|
2879
|
+
/**
|
|
2880
|
+
* Check if current player is an admin for this scene
|
|
2881
|
+
*/
|
|
2882
|
+
async checkAdminStatus() {
|
|
2883
|
+
const player = (0, import_players2.getPlayer)();
|
|
2884
|
+
if (!player?.userId) {
|
|
2885
|
+
this.log("No player data yet");
|
|
2886
|
+
return;
|
|
2887
|
+
}
|
|
2888
|
+
this.playerWallet = player.userId;
|
|
2889
|
+
if (this.config.forceAdmin) {
|
|
2890
|
+
this.isAdmin = true;
|
|
2891
|
+
this.isOwner = true;
|
|
2892
|
+
this.log("Admin status FORCED (forceAdmin: true)");
|
|
2893
|
+
return;
|
|
2894
|
+
}
|
|
2895
|
+
try {
|
|
2896
|
+
const res = await fetch(
|
|
2897
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/admin-check?wallet=${player.userId}`
|
|
2898
|
+
);
|
|
2899
|
+
if (res.ok) {
|
|
2900
|
+
const data = await res.json();
|
|
2901
|
+
this.isAdmin = data.showButton ?? data.hasAccess;
|
|
2902
|
+
this.isOwner = data.isOwner || data.isSceneAdmin;
|
|
2903
|
+
if (data.isBanned) {
|
|
2904
|
+
this.config.onBroadcast?.("You have been banned from this scene.");
|
|
2905
|
+
this.banKickPlayer();
|
|
2906
|
+
this.log("Player is banned - kicking");
|
|
2907
|
+
}
|
|
2908
|
+
this.log("Admin status:", this.isAdmin, "Owner:", this.isOwner);
|
|
2909
|
+
}
|
|
2910
|
+
} catch (err) {
|
|
2911
|
+
this.log("Admin check error:", err);
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
/**
|
|
2915
|
+
* Toggle the admin panel open/closed
|
|
2916
|
+
*/
|
|
2917
|
+
toggle() {
|
|
2918
|
+
if (this.panelOpen) {
|
|
2919
|
+
this.hide();
|
|
2920
|
+
} else {
|
|
2921
|
+
this.show();
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
/**
|
|
2925
|
+
* Show the admin panel
|
|
2926
|
+
*/
|
|
2927
|
+
show() {
|
|
2928
|
+
if (this.panelOpen) return;
|
|
2929
|
+
this.client.closeOtherPanels("admin");
|
|
2930
|
+
this.panelOpen = true;
|
|
2931
|
+
if (this.activeTab === "video") {
|
|
2932
|
+
this.startStreamPolling();
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
/**
|
|
2936
|
+
* Hide the admin panel
|
|
2937
|
+
*/
|
|
2938
|
+
hide() {
|
|
2939
|
+
if (!this.panelOpen) return;
|
|
2940
|
+
this.panelOpen = false;
|
|
2941
|
+
this.stopStreamPolling();
|
|
2942
|
+
}
|
|
2943
|
+
/**
|
|
2944
|
+
* Check if the panel is currently open
|
|
2945
|
+
*/
|
|
2946
|
+
get isOpen() {
|
|
2947
|
+
return this.panelOpen;
|
|
2948
|
+
}
|
|
2949
|
+
/**
|
|
2950
|
+
* Check if current user has admin access
|
|
2951
|
+
*/
|
|
2952
|
+
get hasAccess() {
|
|
2953
|
+
return this.isAdmin;
|
|
2954
|
+
}
|
|
2955
|
+
/**
|
|
2956
|
+
* Register a custom scene tab (Pro tier)
|
|
2957
|
+
*/
|
|
2958
|
+
registerSceneTab(tab) {
|
|
2959
|
+
if (!this.config.sceneTabs) {
|
|
2960
|
+
this.config.sceneTabs = [];
|
|
2961
|
+
}
|
|
2962
|
+
this.config.sceneTabs.push(tab);
|
|
2963
|
+
this.log("Registered scene tab:", tab.label);
|
|
2964
|
+
}
|
|
2965
|
+
// --- Stream Polling ---
|
|
2966
|
+
startStreamPolling() {
|
|
2967
|
+
if (this.pollIntervalId !== null) return;
|
|
2968
|
+
this.pollIntervalId = dclSetInterval(() => {
|
|
2969
|
+
if (this.activeTab === "video" && this.panelOpen && this.streamData?.hasChannel) {
|
|
2970
|
+
this.refreshStreamStatus();
|
|
2971
|
+
}
|
|
2972
|
+
}, 1e4);
|
|
2973
|
+
this.log("Stream polling started");
|
|
2974
|
+
}
|
|
2975
|
+
stopStreamPolling() {
|
|
2976
|
+
if (this.pollIntervalId !== null) {
|
|
2977
|
+
dclClearInterval(this.pollIntervalId);
|
|
2978
|
+
this.pollIntervalId = null;
|
|
2979
|
+
this.log("Stream polling stopped");
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
// --- Stream API Calls ---
|
|
2983
|
+
async fetchStreamData() {
|
|
2984
|
+
if (this.streamFetched || !this.playerWallet) return;
|
|
2985
|
+
try {
|
|
2986
|
+
const res = await fetch(
|
|
2987
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/stream?wallet=${this.playerWallet}`
|
|
2988
|
+
);
|
|
2989
|
+
if (res.ok) {
|
|
2990
|
+
this.streamData = await res.json();
|
|
2991
|
+
this.streamFetched = true;
|
|
2992
|
+
this.log("Stream data fetched:", this.streamData?.channelId || "no channel");
|
|
2993
|
+
}
|
|
2994
|
+
} catch (err) {
|
|
2995
|
+
this.log("Stream fetch error:", err);
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
async refreshStreamStatus() {
|
|
2999
|
+
if (!this.playerWallet) return;
|
|
3000
|
+
try {
|
|
3001
|
+
const res = await fetch(
|
|
3002
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/stream?wallet=${this.playerWallet}`
|
|
3003
|
+
);
|
|
3004
|
+
if (res.ok) {
|
|
3005
|
+
this.streamData = await res.json();
|
|
3006
|
+
}
|
|
3007
|
+
} catch (err) {
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
// --- Video State (Slots) ---
|
|
3011
|
+
async fetchVideoState() {
|
|
3012
|
+
if (this.videoStateFetched) return;
|
|
3013
|
+
try {
|
|
3014
|
+
const res = await fetch(
|
|
3015
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/video-state`
|
|
3016
|
+
);
|
|
3017
|
+
if (res.ok) {
|
|
3018
|
+
const data = await res.json();
|
|
3019
|
+
this.videoState = {
|
|
3020
|
+
defaultSlot: data.defaultSlot || null,
|
|
3021
|
+
videoSlots: data.videoSlots || {}
|
|
3022
|
+
};
|
|
3023
|
+
this.videoStateFetched = true;
|
|
3024
|
+
this.log("Video state fetched:", this.videoState.defaultSlot || "no default");
|
|
3025
|
+
}
|
|
3026
|
+
} catch (err) {
|
|
3027
|
+
this.log("Video state fetch error:", err);
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
/**
|
|
3031
|
+
* Play a video slot by ID - looks up URL and calls onVideoPlay
|
|
3032
|
+
*/
|
|
3033
|
+
playSlot(slotId) {
|
|
3034
|
+
const slot = this.videoState?.videoSlots?.[slotId];
|
|
3035
|
+
if (slot?.url) {
|
|
3036
|
+
this.log("Playing slot:", slotId, slot.url);
|
|
3037
|
+
this.config.onVideoPlay?.(slot.url);
|
|
3038
|
+
} else {
|
|
3039
|
+
this.log("Slot has no URL:", slotId);
|
|
3040
|
+
this.config.onVideoSlotPlay?.(slotId);
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
/**
|
|
3044
|
+
* Auto-play the default slot if configured
|
|
3045
|
+
*/
|
|
3046
|
+
autoPlayDefault() {
|
|
3047
|
+
if (!this.videoState?.defaultSlot) return;
|
|
3048
|
+
const defaultSlot = this.videoState.defaultSlot;
|
|
3049
|
+
const slot = this.videoState.videoSlots?.[defaultSlot];
|
|
3050
|
+
if (slot?.url) {
|
|
3051
|
+
this.log("Auto-playing default slot:", defaultSlot);
|
|
3052
|
+
this.config.onVideoPlay?.(slot.url);
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
async createChannel() {
|
|
3056
|
+
if (!this.playerWallet || this.channelCreating) return;
|
|
3057
|
+
this.channelCreating = true;
|
|
3058
|
+
this.channelCreateError = "";
|
|
3059
|
+
try {
|
|
3060
|
+
this.log("Creating channel for scene...");
|
|
3061
|
+
const res = await fetch(
|
|
3062
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/stream/create`,
|
|
3063
|
+
{
|
|
3064
|
+
method: "POST",
|
|
3065
|
+
headers: { "Content-Type": "application/json" },
|
|
3066
|
+
body: JSON.stringify({ wallet: this.playerWallet })
|
|
3067
|
+
}
|
|
3068
|
+
);
|
|
3069
|
+
const data = await res.json();
|
|
3070
|
+
if (res.ok) {
|
|
3071
|
+
this.log("Channel created:", data.channel);
|
|
3072
|
+
this.streamFetched = false;
|
|
3073
|
+
await this.fetchStreamData();
|
|
3074
|
+
this.config.onBroadcast?.(`Channel created: ${data.channel.channelId}`);
|
|
3075
|
+
} else {
|
|
3076
|
+
this.channelCreateError = data.error || "Failed to create channel";
|
|
3077
|
+
this.log("Channel creation failed:", data.error);
|
|
3078
|
+
}
|
|
3079
|
+
} catch (err) {
|
|
3080
|
+
this.channelCreateError = "Network error creating channel";
|
|
3081
|
+
this.log("Channel creation error:", err);
|
|
3082
|
+
}
|
|
3083
|
+
this.channelCreating = false;
|
|
3084
|
+
}
|
|
3085
|
+
async claimTrial() {
|
|
3086
|
+
if (!this.playerWallet || this.trialClaiming) return;
|
|
3087
|
+
this.trialClaiming = true;
|
|
3088
|
+
this.trialClaimError = "";
|
|
3089
|
+
try {
|
|
3090
|
+
this.log("Claiming streaming trial...");
|
|
3091
|
+
const res = await fetch(
|
|
3092
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/stream/claim-trial`,
|
|
3093
|
+
{
|
|
3094
|
+
method: "POST",
|
|
3095
|
+
headers: { "Content-Type": "application/json" },
|
|
3096
|
+
body: JSON.stringify({ wallet: this.playerWallet })
|
|
3097
|
+
}
|
|
3098
|
+
);
|
|
3099
|
+
const data = await res.json();
|
|
3100
|
+
if (res.ok) {
|
|
3101
|
+
this.log("Trial claimed:", data.channel);
|
|
3102
|
+
this.streamFetched = false;
|
|
3103
|
+
await this.fetchStreamData();
|
|
3104
|
+
this.config.onBroadcast?.(`4-hour streaming trial activated!`);
|
|
3105
|
+
} else {
|
|
3106
|
+
this.trialClaimError = data.error || "Failed to claim trial";
|
|
3107
|
+
this.log("Trial claim failed:", data.error);
|
|
3108
|
+
}
|
|
3109
|
+
} catch (err) {
|
|
3110
|
+
this.trialClaimError = "Network error claiming trial";
|
|
3111
|
+
this.log("Trial claim error:", err);
|
|
3112
|
+
}
|
|
3113
|
+
this.trialClaiming = false;
|
|
3114
|
+
}
|
|
3115
|
+
async deleteChannel() {
|
|
3116
|
+
if (!this.playerWallet || this.channelDeleting || !this.streamData?.hasChannel) return;
|
|
3117
|
+
this.channelDeleting = true;
|
|
3118
|
+
this.channelDeleteError = "";
|
|
3119
|
+
try {
|
|
3120
|
+
this.log("Deleting channel for scene...");
|
|
3121
|
+
const res = await fetch(
|
|
3122
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/stream/delete`,
|
|
3123
|
+
{
|
|
3124
|
+
method: "POST",
|
|
3125
|
+
headers: { "Content-Type": "application/json" },
|
|
3126
|
+
body: JSON.stringify({ wallet: this.playerWallet })
|
|
3127
|
+
}
|
|
3128
|
+
);
|
|
3129
|
+
const data = await res.json();
|
|
3130
|
+
if (res.ok) {
|
|
3131
|
+
this.log("Channel deleted:", data);
|
|
3132
|
+
this.streamData = null;
|
|
3133
|
+
this.streamFetched = false;
|
|
3134
|
+
this.config.onBroadcast?.("Channel deleted successfully");
|
|
3135
|
+
} else {
|
|
3136
|
+
this.channelDeleteError = data.error || "Failed to delete channel";
|
|
3137
|
+
this.log("Channel deletion failed:", data.error);
|
|
3138
|
+
}
|
|
3139
|
+
} catch (err) {
|
|
3140
|
+
this.channelDeleteError = "Network error deleting channel";
|
|
3141
|
+
this.log("Channel deletion error:", err);
|
|
3142
|
+
}
|
|
3143
|
+
this.channelDeleting = false;
|
|
3144
|
+
}
|
|
3145
|
+
async startStream() {
|
|
3146
|
+
if (!this.playerWallet || this.streamControlling || !this.streamData?.hasChannel || this.streamData.isLive) return;
|
|
3147
|
+
if ((this.streamData.sparksBalance || 0) <= 0) {
|
|
3148
|
+
this.streamControlStatus = "error";
|
|
3149
|
+
this.config.onBroadcast?.("No Sparks - cannot start stream");
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
3152
|
+
this.streamControlling = true;
|
|
3153
|
+
this.streamControlStatus = "";
|
|
3154
|
+
try {
|
|
3155
|
+
this.log("Starting stream...");
|
|
3156
|
+
const res = await fetch(
|
|
3157
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/stream`,
|
|
3158
|
+
{
|
|
3159
|
+
method: "POST",
|
|
3160
|
+
headers: { "Content-Type": "application/json" },
|
|
3161
|
+
body: JSON.stringify({ action: "start", wallet: this.playerWallet })
|
|
3162
|
+
}
|
|
3163
|
+
);
|
|
3164
|
+
const data = await res.json();
|
|
3165
|
+
if (res.ok) {
|
|
3166
|
+
this.streamControlStatus = "started";
|
|
3167
|
+
this.log("Stream started");
|
|
3168
|
+
this.config.onBroadcast?.("Stream started!");
|
|
3169
|
+
this.streamFetched = false;
|
|
3170
|
+
await this.fetchStreamData();
|
|
3171
|
+
} else {
|
|
3172
|
+
this.streamControlStatus = "error";
|
|
3173
|
+
this.log("Start stream failed:", data.error);
|
|
3174
|
+
this.config.onBroadcast?.(data.error || "Failed to start");
|
|
3175
|
+
}
|
|
3176
|
+
} catch (err) {
|
|
3177
|
+
this.streamControlStatus = "error";
|
|
3178
|
+
this.log("Start stream error:", err);
|
|
3179
|
+
}
|
|
3180
|
+
this.streamControlling = false;
|
|
3181
|
+
}
|
|
3182
|
+
async stopStream() {
|
|
3183
|
+
if (!this.playerWallet || this.streamControlling || !this.streamData?.hasChannel || !this.streamData.isLive) return;
|
|
3184
|
+
this.streamControlling = true;
|
|
3185
|
+
this.streamControlStatus = "";
|
|
3186
|
+
try {
|
|
3187
|
+
this.log("Stopping stream...");
|
|
3188
|
+
const res = await fetch(
|
|
3189
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/stream`,
|
|
3190
|
+
{
|
|
3191
|
+
method: "POST",
|
|
3192
|
+
headers: { "Content-Type": "application/json" },
|
|
3193
|
+
body: JSON.stringify({ action: "stop", wallet: this.playerWallet })
|
|
3194
|
+
}
|
|
3195
|
+
);
|
|
3196
|
+
const data = await res.json();
|
|
3197
|
+
if (res.ok) {
|
|
3198
|
+
this.streamControlStatus = "stopped";
|
|
3199
|
+
this.log("Stream stopped");
|
|
3200
|
+
this.config.onBroadcast?.("Stream stopped");
|
|
3201
|
+
this.streamFetched = false;
|
|
3202
|
+
await this.fetchStreamData();
|
|
3203
|
+
} else {
|
|
3204
|
+
this.streamControlStatus = "error";
|
|
3205
|
+
this.log("Stop stream failed:", data.error);
|
|
3206
|
+
}
|
|
3207
|
+
} catch (err) {
|
|
3208
|
+
this.streamControlStatus = "error";
|
|
3209
|
+
this.log("Stop stream error:", err);
|
|
3210
|
+
}
|
|
3211
|
+
this.streamControlling = false;
|
|
3212
|
+
}
|
|
3213
|
+
async rotateStreamKey() {
|
|
3214
|
+
if (!this.playerWallet || this.keyRotating || !this.streamData?.hasChannel) return;
|
|
3215
|
+
this.keyRotating = true;
|
|
3216
|
+
this.keyRotateStatus = "";
|
|
3217
|
+
try {
|
|
3218
|
+
this.log("Rotating stream key...");
|
|
3219
|
+
const res = await fetch(
|
|
3220
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/stream`,
|
|
3221
|
+
{
|
|
3222
|
+
method: "POST",
|
|
3223
|
+
headers: { "Content-Type": "application/json" },
|
|
3224
|
+
body: JSON.stringify({ action: "rotateKey", wallet: this.playerWallet })
|
|
3225
|
+
}
|
|
3226
|
+
);
|
|
3227
|
+
const data = await res.json();
|
|
3228
|
+
if (res.ok) {
|
|
3229
|
+
this.keyRotateStatus = "success";
|
|
3230
|
+
this.log("Key rotated:", data.message);
|
|
3231
|
+
this.config.onBroadcast?.("Stream key rotated - update OBS settings");
|
|
3232
|
+
} else {
|
|
3233
|
+
this.keyRotateStatus = "error";
|
|
3234
|
+
this.log("Key rotation failed:", data.error);
|
|
3235
|
+
}
|
|
3236
|
+
} catch (err) {
|
|
3237
|
+
this.keyRotateStatus = "error";
|
|
3238
|
+
this.log("Key rotation error:", err);
|
|
3239
|
+
}
|
|
3240
|
+
this.keyRotating = false;
|
|
3241
|
+
}
|
|
3242
|
+
// --- Mod Tab API Calls ---
|
|
3243
|
+
async fetchModData() {
|
|
3244
|
+
if (!this.playerWallet) return;
|
|
3245
|
+
this.modStatus = "loading";
|
|
3246
|
+
try {
|
|
3247
|
+
const res = await fetch(
|
|
3248
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/config?wallet=${this.playerWallet}`,
|
|
3249
|
+
{ headers: { "Content-Type": "application/json" } }
|
|
3250
|
+
);
|
|
3251
|
+
if (res.ok) {
|
|
3252
|
+
const data = await res.json();
|
|
3253
|
+
this.sceneAdmins = data.sceneAdmins || [];
|
|
3254
|
+
this.bannedWallets = data.bannedWallets || [];
|
|
3255
|
+
this.modsFetched = true;
|
|
3256
|
+
this.modStatus = "";
|
|
3257
|
+
this.log("Fetched mod data:", { sceneAdmins: this.sceneAdmins, bannedWallets: this.bannedWallets });
|
|
3258
|
+
} else {
|
|
3259
|
+
this.modStatus = "error";
|
|
3260
|
+
}
|
|
3261
|
+
} catch (err) {
|
|
3262
|
+
this.modStatus = "error";
|
|
3263
|
+
this.log("Fetch mod data error:", err);
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
async addSceneAdmin(wallet) {
|
|
3267
|
+
if (!wallet || !this.playerWallet) return;
|
|
3268
|
+
const normalized = wallet.toLowerCase().trim();
|
|
3269
|
+
if (!/^0x[a-f0-9]{40}$/i.test(normalized)) {
|
|
3270
|
+
this.modStatus = "error";
|
|
3271
|
+
return;
|
|
3272
|
+
}
|
|
3273
|
+
if (this.sceneAdmins.includes(normalized)) return;
|
|
3274
|
+
this.modStatus = "loading";
|
|
3275
|
+
try {
|
|
3276
|
+
const res = await fetch(
|
|
3277
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/config`,
|
|
3278
|
+
{
|
|
3279
|
+
method: "POST",
|
|
3280
|
+
headers: { "Content-Type": "application/json" },
|
|
3281
|
+
body: JSON.stringify({
|
|
3282
|
+
sceneAdmins: [...this.sceneAdmins, normalized],
|
|
3283
|
+
wallet: this.playerWallet
|
|
3284
|
+
})
|
|
3285
|
+
}
|
|
3286
|
+
);
|
|
3287
|
+
if (res.ok) {
|
|
3288
|
+
this.sceneAdmins = [...this.sceneAdmins, normalized];
|
|
3289
|
+
this.newAdminWallet = "";
|
|
3290
|
+
this.modStatus = "saved";
|
|
3291
|
+
this.log("Added scene admin:", normalized);
|
|
3292
|
+
} else {
|
|
3293
|
+
this.modStatus = "error";
|
|
3294
|
+
}
|
|
3295
|
+
} catch (err) {
|
|
3296
|
+
this.modStatus = "error";
|
|
3297
|
+
this.log("Add admin error:", err);
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
async removeSceneAdmin(wallet) {
|
|
3301
|
+
if (!wallet || !this.playerWallet) return;
|
|
3302
|
+
this.modStatus = "loading";
|
|
3303
|
+
try {
|
|
3304
|
+
const res = await fetch(
|
|
3305
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/config`,
|
|
3306
|
+
{
|
|
3307
|
+
method: "POST",
|
|
3308
|
+
headers: { "Content-Type": "application/json" },
|
|
3309
|
+
body: JSON.stringify({
|
|
3310
|
+
sceneAdmins: this.sceneAdmins.filter((w) => w !== wallet),
|
|
3311
|
+
wallet: this.playerWallet
|
|
3312
|
+
})
|
|
3313
|
+
}
|
|
3314
|
+
);
|
|
3315
|
+
if (res.ok) {
|
|
3316
|
+
this.sceneAdmins = this.sceneAdmins.filter((w) => w !== wallet);
|
|
3317
|
+
this.modStatus = "saved";
|
|
3318
|
+
this.log("Removed scene admin:", wallet);
|
|
3319
|
+
} else {
|
|
3320
|
+
this.modStatus = "error";
|
|
3321
|
+
}
|
|
3322
|
+
} catch (err) {
|
|
3323
|
+
this.modStatus = "error";
|
|
3324
|
+
this.log("Remove admin error:", err);
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
async banWallet(wallet) {
|
|
3328
|
+
if (!wallet || !this.playerWallet) return;
|
|
3329
|
+
const normalized = wallet.toLowerCase().trim();
|
|
3330
|
+
if (!/^0x[a-f0-9]{40}$/i.test(normalized)) {
|
|
3331
|
+
this.modStatus = "error";
|
|
3332
|
+
return;
|
|
3333
|
+
}
|
|
3334
|
+
if (this.bannedWallets.includes(normalized)) return;
|
|
3335
|
+
this.modStatus = "loading";
|
|
3336
|
+
try {
|
|
3337
|
+
const res = await fetch(
|
|
3338
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/config`,
|
|
3339
|
+
{
|
|
3340
|
+
method: "POST",
|
|
3341
|
+
headers: { "Content-Type": "application/json" },
|
|
3342
|
+
body: JSON.stringify({
|
|
3343
|
+
bannedWallets: [...this.bannedWallets, normalized],
|
|
3344
|
+
wallet: this.playerWallet
|
|
3345
|
+
})
|
|
3346
|
+
}
|
|
3347
|
+
);
|
|
3348
|
+
if (res.ok) {
|
|
3349
|
+
this.bannedWallets = [...this.bannedWallets, normalized];
|
|
3350
|
+
this.newBanWallet = "";
|
|
3351
|
+
this.modStatus = "saved";
|
|
3352
|
+
this.log("Banned wallet:", normalized);
|
|
3353
|
+
this.config.onCommand?.("kickBanned", { wallet: normalized });
|
|
3354
|
+
} else {
|
|
3355
|
+
this.modStatus = "error";
|
|
3356
|
+
}
|
|
3357
|
+
} catch (err) {
|
|
3358
|
+
this.modStatus = "error";
|
|
3359
|
+
this.log("Ban error:", err);
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
async unbanWallet(wallet) {
|
|
3363
|
+
if (!wallet || !this.playerWallet) return;
|
|
3364
|
+
this.modStatus = "loading";
|
|
3365
|
+
try {
|
|
3366
|
+
const res = await fetch(
|
|
3367
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/config`,
|
|
3368
|
+
{
|
|
3369
|
+
method: "POST",
|
|
3370
|
+
headers: { "Content-Type": "application/json" },
|
|
3371
|
+
body: JSON.stringify({
|
|
3372
|
+
bannedWallets: this.bannedWallets.filter((w) => w !== wallet),
|
|
3373
|
+
wallet: this.playerWallet
|
|
3374
|
+
})
|
|
3375
|
+
}
|
|
3376
|
+
);
|
|
3377
|
+
if (res.ok) {
|
|
3378
|
+
this.bannedWallets = this.bannedWallets.filter((w) => w !== wallet);
|
|
3379
|
+
this.modStatus = "saved";
|
|
3380
|
+
this.log("Unbanned wallet:", wallet);
|
|
3381
|
+
} else {
|
|
3382
|
+
this.modStatus = "error";
|
|
3383
|
+
}
|
|
3384
|
+
} catch (err) {
|
|
3385
|
+
this.modStatus = "error";
|
|
3386
|
+
this.log("Unban error:", err);
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
banKickPlayer() {
|
|
3390
|
+
(0, import_RestrictedActions3.movePlayerTo)({ newRelativePosition: BAN_KICK_POSITION });
|
|
3391
|
+
this.log("Player ban-kicked");
|
|
3392
|
+
}
|
|
3393
|
+
sendBroadcast() {
|
|
3394
|
+
if (!this.broadcastText.trim()) return;
|
|
3395
|
+
this.config.onBroadcast?.(this.broadcastText.trim());
|
|
3396
|
+
this.broadcastText = "";
|
|
3397
|
+
this.log("Broadcast sent");
|
|
3398
|
+
}
|
|
3399
|
+
setActiveTab(tab) {
|
|
3400
|
+
const previousTab = this.activeTab;
|
|
3401
|
+
this.activeTab = tab;
|
|
3402
|
+
if (tab === "mod" && !this.modsFetched && this.isOwner) {
|
|
3403
|
+
this.fetchModData();
|
|
3404
|
+
}
|
|
3405
|
+
if (tab === "video" && previousTab !== "video") {
|
|
3406
|
+
this.startStreamPolling();
|
|
3407
|
+
} else if (tab !== "video" && previousTab === "video") {
|
|
3408
|
+
this.stopStreamPolling();
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
};
|
|
3412
|
+
|
|
3413
|
+
// src/ui/ui-renderer.tsx
|
|
3414
|
+
var import_react_ecs5 = __toESM(require("@dcl/sdk/react-ecs"));
|
|
3415
|
+
var import_math6 = require("@dcl/sdk/math");
|
|
3416
|
+
var import_ecs2 = require("@dcl/sdk/ecs");
|
|
3417
|
+
var notificationText = "";
|
|
3418
|
+
var notificationVisible = false;
|
|
3419
|
+
var notificationEndTime = 0;
|
|
3420
|
+
var notificationInitialized = false;
|
|
3421
|
+
function showNotification(message, durationMs = 5e3) {
|
|
3422
|
+
notificationText = message;
|
|
3423
|
+
notificationVisible = true;
|
|
3424
|
+
notificationEndTime = Date.now() + durationMs;
|
|
3425
|
+
}
|
|
3426
|
+
function initNotificationSystem() {
|
|
3427
|
+
if (notificationInitialized) return;
|
|
3428
|
+
notificationInitialized = true;
|
|
3429
|
+
import_ecs2.engine.addSystem(() => {
|
|
3430
|
+
if (notificationVisible && Date.now() > notificationEndTime) {
|
|
3431
|
+
notificationVisible = false;
|
|
3432
|
+
}
|
|
3433
|
+
});
|
|
3434
|
+
}
|
|
3435
|
+
function NotificationBanner() {
|
|
3436
|
+
if (!notificationVisible) return null;
|
|
3437
|
+
return /* @__PURE__ */ import_react_ecs5.default.createElement(
|
|
3438
|
+
import_react_ecs5.UiEntity,
|
|
3439
|
+
{
|
|
3440
|
+
uiTransform: {
|
|
3441
|
+
positionType: "absolute",
|
|
3442
|
+
position: { top: 80 },
|
|
3443
|
+
width: 500,
|
|
3444
|
+
height: 60,
|
|
3445
|
+
padding: 16,
|
|
3446
|
+
alignSelf: "center",
|
|
3447
|
+
justifyContent: "center",
|
|
3448
|
+
alignItems: "center"
|
|
3449
|
+
},
|
|
3450
|
+
uiBackground: { color: import_math6.Color4.create(0.1, 0.1, 0.15, 0.95) }
|
|
3451
|
+
},
|
|
3452
|
+
/* @__PURE__ */ import_react_ecs5.default.createElement(
|
|
3453
|
+
import_react_ecs5.Label,
|
|
3454
|
+
{
|
|
3455
|
+
value: notificationText,
|
|
3456
|
+
fontSize: 18,
|
|
3457
|
+
color: import_math6.Color4.create(0, 1, 1, 1),
|
|
3458
|
+
textAlign: "middle-center"
|
|
3459
|
+
}
|
|
3460
|
+
)
|
|
3461
|
+
);
|
|
3462
|
+
}
|
|
3463
|
+
function createStaticUI(client) {
|
|
3464
|
+
initNotificationSystem();
|
|
3465
|
+
return function StaticUI() {
|
|
3466
|
+
const currentScale = client.uiScale;
|
|
3467
|
+
const guideComponent = client.guideUI?.getComponent() ?? null;
|
|
3468
|
+
const chatComponent = client.chatUI?.getComponent() ?? null;
|
|
3469
|
+
const adminComponent = client.adminPanel?.getComponent() ?? null;
|
|
3470
|
+
return /* @__PURE__ */ import_react_ecs5.default.createElement(
|
|
3471
|
+
import_react_ecs5.UiEntity,
|
|
3472
|
+
{
|
|
3473
|
+
key: `static-ui-root-${currentScale}`,
|
|
3474
|
+
uiTransform: {
|
|
3475
|
+
width: "100%",
|
|
3476
|
+
height: "100%",
|
|
3477
|
+
positionType: "absolute",
|
|
3478
|
+
flexDirection: "column",
|
|
3479
|
+
alignItems: "center"
|
|
3480
|
+
}
|
|
3481
|
+
},
|
|
3482
|
+
/* @__PURE__ */ import_react_ecs5.default.createElement(NotificationBanner, null),
|
|
3483
|
+
guideComponent,
|
|
3484
|
+
chatComponent,
|
|
3485
|
+
adminComponent
|
|
3486
|
+
);
|
|
3487
|
+
};
|
|
3488
|
+
}
|
|
3489
|
+
function setupStaticUI(client) {
|
|
3490
|
+
const StaticUI = createStaticUI(client);
|
|
3491
|
+
import_react_ecs5.ReactEcsRenderer.setUiRenderer(StaticUI);
|
|
3492
|
+
}
|
|
3493
|
+
|
|
2164
3494
|
// src/StaticTVClient.ts
|
|
2165
3495
|
var DEFAULT_BASE_URL = "https://thestatic.tv/api/v1/dcl";
|
|
2166
3496
|
var KEY_TYPE_CHANNEL = "channel";
|
|
@@ -2173,34 +3503,41 @@ var StaticTVClient = class {
|
|
|
2173
3503
|
*
|
|
2174
3504
|
* @example
|
|
2175
3505
|
* ```typescript
|
|
2176
|
-
*
|
|
2177
|
-
* const staticTV = new StaticTVClient({
|
|
2178
|
-
* apiKey: 'dclk_your_channel_key_here',
|
|
2179
|
-
* debug: true
|
|
2180
|
-
* });
|
|
3506
|
+
* let staticTV: StaticTVClient
|
|
2181
3507
|
*
|
|
2182
|
-
*
|
|
2183
|
-
*
|
|
2184
|
-
*
|
|
2185
|
-
*
|
|
3508
|
+
* export function main() {
|
|
3509
|
+
* // All keys use dcls_ prefix - features determined by subscription
|
|
3510
|
+
* staticTV = new StaticTVClient({
|
|
3511
|
+
* apiKey: 'dcls_your_key_here'
|
|
3512
|
+
* })
|
|
3513
|
+
* // Session tracking starts automatically!
|
|
3514
|
+
* }
|
|
2186
3515
|
* ```
|
|
2187
3516
|
*/
|
|
2188
3517
|
constructor(config) {
|
|
2189
3518
|
this._keyType = null;
|
|
3519
|
+
this._keyId = null;
|
|
2190
3520
|
this._disabled = false;
|
|
2191
|
-
this.
|
|
2192
|
-
|
|
3521
|
+
this._tier = "free";
|
|
3522
|
+
this._standardFeaturesEnabled = false;
|
|
3523
|
+
this._proFeaturesEnabled = false;
|
|
3524
|
+
this._pendingProConfig = null;
|
|
3525
|
+
/** Guide module - fetch channel lineup (standard/pro tier) */
|
|
2193
3526
|
this.guide = null;
|
|
2194
|
-
/** Session module - track visitor sessions (all
|
|
3527
|
+
/** Session module - track visitor sessions (all tiers, null when disabled) */
|
|
2195
3528
|
this.session = null;
|
|
2196
|
-
/** Heartbeat module - track video watching (
|
|
3529
|
+
/** Heartbeat module - track video watching (standard/pro tier) */
|
|
2197
3530
|
this.heartbeat = null;
|
|
2198
|
-
/** Interactions module - like/follow channels (
|
|
3531
|
+
/** Interactions module - like/follow channels (standard/pro tier) */
|
|
2199
3532
|
this.interactions = null;
|
|
2200
|
-
/** Guide UI module - channel browser UI (
|
|
3533
|
+
/** Guide UI module - channel browser UI (standard/pro tier) */
|
|
2201
3534
|
this.guideUI = null;
|
|
2202
|
-
/** Chat UI module - real-time chat UI (
|
|
3535
|
+
/** Chat UI module - real-time chat UI (standard/pro tier) */
|
|
2203
3536
|
this.chatUI = null;
|
|
3537
|
+
/** Admin Panel module - Video/Mod tabs (pro tier only) */
|
|
3538
|
+
this.adminPanel = null;
|
|
3539
|
+
/** UI scale - fixed at 1.0. DCL's UI system auto-scales based on viewport. */
|
|
3540
|
+
this.uiScale = 1;
|
|
2204
3541
|
this.config = {
|
|
2205
3542
|
autoStartSession: true,
|
|
2206
3543
|
sessionHeartbeatInterval: 3e4,
|
|
@@ -2210,7 +3547,6 @@ var StaticTVClient = class {
|
|
|
2210
3547
|
};
|
|
2211
3548
|
this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
|
|
2212
3549
|
if (!config.apiKey) {
|
|
2213
|
-
console.log("[StaticTV] No apiKey provided - tracking disabled. Scene will load normally.");
|
|
2214
3550
|
this._disabled = true;
|
|
2215
3551
|
this._keyType = null;
|
|
2216
3552
|
this.session = null;
|
|
@@ -2226,14 +3562,15 @@ var StaticTVClient = class {
|
|
|
2226
3562
|
} else if (config.apiKey.startsWith("dcls_")) {
|
|
2227
3563
|
this._keyType = KEY_TYPE_SCENE;
|
|
2228
3564
|
} else {
|
|
2229
|
-
console.
|
|
3565
|
+
console.warn("[TheStatic] Invalid API key format - get your key at thestatic.tv/dashboard");
|
|
2230
3566
|
this._disabled = true;
|
|
2231
3567
|
this._keyType = null;
|
|
2232
3568
|
return;
|
|
2233
3569
|
}
|
|
2234
3570
|
this.session = new SessionModule(this);
|
|
2235
3571
|
if (this._keyType === KEY_TYPE_CHANNEL) {
|
|
2236
|
-
this.
|
|
3572
|
+
this._tier = "standard";
|
|
3573
|
+
this._initStandardModules();
|
|
2237
3574
|
}
|
|
2238
3575
|
if (this.config.autoStartSession) {
|
|
2239
3576
|
fetchUserData().then(() => {
|
|
@@ -2249,6 +3586,10 @@ var StaticTVClient = class {
|
|
|
2249
3586
|
}
|
|
2250
3587
|
this.log(`StaticTVClient initialized (${this._keyType} mode)`);
|
|
2251
3588
|
}
|
|
3589
|
+
/** Get the API base URL (for internal module use) */
|
|
3590
|
+
getBaseUrl() {
|
|
3591
|
+
return this.baseUrl;
|
|
3592
|
+
}
|
|
2252
3593
|
/**
|
|
2253
3594
|
* Get the key type (channel, scene, or null if disabled)
|
|
2254
3595
|
*/
|
|
@@ -2262,11 +3603,23 @@ var StaticTVClient = class {
|
|
|
2262
3603
|
return this._disabled;
|
|
2263
3604
|
}
|
|
2264
3605
|
/**
|
|
2265
|
-
*
|
|
2266
|
-
|
|
3606
|
+
* Get the current SDK tier (free, standard, or pro)
|
|
3607
|
+
*/
|
|
3608
|
+
get tier() {
|
|
3609
|
+
return this._tier;
|
|
3610
|
+
}
|
|
3611
|
+
/**
|
|
3612
|
+
* Check if this is a free tier client (session tracking only)
|
|
3613
|
+
* Returns true until session confirms a higher tier
|
|
3614
|
+
*/
|
|
3615
|
+
get isFree() {
|
|
3616
|
+
return this._tier === "free";
|
|
3617
|
+
}
|
|
3618
|
+
/**
|
|
3619
|
+
* @deprecated Use `isFree` instead. Kept for backward compatibility.
|
|
2267
3620
|
*/
|
|
2268
3621
|
get isLite() {
|
|
2269
|
-
return
|
|
3622
|
+
return this.isFree;
|
|
2270
3623
|
}
|
|
2271
3624
|
/**
|
|
2272
3625
|
* Make an authenticated API request
|
|
@@ -2284,14 +3637,29 @@ var StaticTVClient = class {
|
|
|
2284
3637
|
});
|
|
2285
3638
|
}
|
|
2286
3639
|
/**
|
|
2287
|
-
* Log a message
|
|
3640
|
+
* Log a debug message (only when debug: true)
|
|
2288
3641
|
* @internal
|
|
2289
3642
|
*/
|
|
2290
3643
|
log(message, ...args) {
|
|
2291
3644
|
if (this.config.debug) {
|
|
2292
|
-
console.log(`[
|
|
3645
|
+
console.log(`[TheStatic] ${message}`, ...args);
|
|
2293
3646
|
}
|
|
2294
3647
|
}
|
|
3648
|
+
/**
|
|
3649
|
+
* Log a warning (always shown)
|
|
3650
|
+
* @internal
|
|
3651
|
+
*/
|
|
3652
|
+
warn(message) {
|
|
3653
|
+
console.warn(`[TheStatic] ${message}`);
|
|
3654
|
+
}
|
|
3655
|
+
/**
|
|
3656
|
+
* Log an error (always shown, user-friendly format)
|
|
3657
|
+
* @internal
|
|
3658
|
+
*/
|
|
3659
|
+
error(message, err) {
|
|
3660
|
+
const errorDetail = err instanceof Error ? err.message : String(err || "");
|
|
3661
|
+
console.error(`[TheStatic] ${message}${errorDetail ? `: ${errorDetail}` : ""}`);
|
|
3662
|
+
}
|
|
2295
3663
|
/**
|
|
2296
3664
|
* Get the current configuration
|
|
2297
3665
|
* @internal
|
|
@@ -2300,41 +3668,199 @@ var StaticTVClient = class {
|
|
|
2300
3668
|
return this.config;
|
|
2301
3669
|
}
|
|
2302
3670
|
/**
|
|
2303
|
-
* Initialize
|
|
3671
|
+
* Initialize standard feature modules (guide, heartbeat, interactions, UI)
|
|
2304
3672
|
* @internal
|
|
2305
3673
|
*/
|
|
2306
|
-
|
|
2307
|
-
if (this.
|
|
3674
|
+
_initStandardModules() {
|
|
3675
|
+
if (this._standardFeaturesEnabled) return;
|
|
2308
3676
|
this.guide = new GuideModule(this);
|
|
2309
3677
|
this.heartbeat = new HeartbeatModule(this);
|
|
2310
3678
|
this.interactions = new InteractionsModule(this);
|
|
2311
3679
|
this.guideUI = new GuideUIModule(this, this.config.guideUI);
|
|
2312
3680
|
this.chatUI = new ChatUIModule(this, this.config.chatUI);
|
|
2313
|
-
this.
|
|
3681
|
+
this._standardFeaturesEnabled = true;
|
|
2314
3682
|
this.chatUI.init().catch((err) => {
|
|
2315
3683
|
this.log(`Chat init failed: ${err}`);
|
|
2316
3684
|
});
|
|
2317
|
-
this.log("
|
|
3685
|
+
this.log("Standard features enabled (guide, chat, heartbeat, interactions)");
|
|
2318
3686
|
}
|
|
2319
3687
|
/**
|
|
2320
|
-
*
|
|
2321
|
-
*
|
|
3688
|
+
* Initialize pro feature modules (admin panel)
|
|
3689
|
+
* @internal
|
|
3690
|
+
*/
|
|
3691
|
+
_initProModules() {
|
|
3692
|
+
if (this._proFeaturesEnabled || !this._pendingProConfig) return;
|
|
3693
|
+
const configWithDefaults = {
|
|
3694
|
+
...this._pendingProConfig,
|
|
3695
|
+
sceneId: this._pendingProConfig.sceneId || this._keyId || void 0
|
|
3696
|
+
};
|
|
3697
|
+
if (!configWithDefaults.sceneId) {
|
|
3698
|
+
this.log("Pro features: No sceneId and no keyId available - admin panel disabled");
|
|
3699
|
+
return;
|
|
3700
|
+
}
|
|
3701
|
+
this.adminPanel = new AdminPanelUIModule(this, configWithDefaults);
|
|
3702
|
+
this._proFeaturesEnabled = true;
|
|
3703
|
+
this.adminPanel.init().catch((err) => {
|
|
3704
|
+
this.log(`Admin panel init failed: ${err}`);
|
|
3705
|
+
});
|
|
3706
|
+
this.log(`Pro features enabled (admin panel) - sceneId: ${configWithDefaults.sceneId}`);
|
|
3707
|
+
}
|
|
3708
|
+
/**
|
|
3709
|
+
* Called by SessionModule when server returns the tier
|
|
3710
|
+
* Enables modules based on tier level
|
|
3711
|
+
* @internal
|
|
3712
|
+
*/
|
|
3713
|
+
_enableFeaturesForTier(tier, keyId) {
|
|
3714
|
+
this._tier = tier;
|
|
3715
|
+
if (keyId) {
|
|
3716
|
+
this._keyId = keyId;
|
|
3717
|
+
}
|
|
3718
|
+
if (tier === "standard" || tier === "pro") {
|
|
3719
|
+
this._initStandardModules();
|
|
3720
|
+
}
|
|
3721
|
+
if (tier === "pro" && this._pendingProConfig) {
|
|
3722
|
+
this._initProModules();
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
/**
|
|
3726
|
+
* @deprecated Use `_enableFeaturesForTier` instead
|
|
2322
3727
|
* @internal
|
|
2323
3728
|
*/
|
|
2324
3729
|
_enableFullFeatures() {
|
|
2325
|
-
this.
|
|
3730
|
+
this._enableFeaturesForTier("standard");
|
|
2326
3731
|
}
|
|
2327
3732
|
/**
|
|
2328
|
-
* Check if
|
|
3733
|
+
* Check if standard features are enabled (guide, chat, etc.)
|
|
3734
|
+
*/
|
|
3735
|
+
get hasStandardFeatures() {
|
|
3736
|
+
return this._standardFeaturesEnabled;
|
|
3737
|
+
}
|
|
3738
|
+
/**
|
|
3739
|
+
* @deprecated Use `hasStandardFeatures` instead
|
|
2329
3740
|
*/
|
|
2330
3741
|
get hasFullFeatures() {
|
|
2331
|
-
return this.
|
|
3742
|
+
return this._standardFeaturesEnabled;
|
|
3743
|
+
}
|
|
3744
|
+
/**
|
|
3745
|
+
* Check if pro features are enabled (admin panel)
|
|
3746
|
+
*/
|
|
3747
|
+
get hasProFeatures() {
|
|
3748
|
+
return this._proFeaturesEnabled;
|
|
2332
3749
|
}
|
|
2333
3750
|
/**
|
|
2334
|
-
*
|
|
3751
|
+
* @deprecated Use `tier` instead
|
|
2335
3752
|
*/
|
|
2336
3753
|
get sdkType() {
|
|
2337
|
-
return this.
|
|
3754
|
+
return this._tier === "free" ? "lite" : "full";
|
|
3755
|
+
}
|
|
3756
|
+
/**
|
|
3757
|
+
* Configure Pro features (Admin Panel with Video + Mod tabs)
|
|
3758
|
+
* Call this after creating the client to configure admin panel.
|
|
3759
|
+
* The panel will auto-enable when server confirms Pro tier.
|
|
3760
|
+
*
|
|
3761
|
+
* @param config Admin panel configuration (optional - defaults work for basic usage)
|
|
3762
|
+
*
|
|
3763
|
+
* @example
|
|
3764
|
+
* ```typescript
|
|
3765
|
+
* const staticTV = new StaticTVClient({ apiKey: 'dcls_...' })
|
|
3766
|
+
*
|
|
3767
|
+
* // Simplest usage - just enable with callbacks:
|
|
3768
|
+
* staticTV.enableProFeatures({
|
|
3769
|
+
* onVideoPlay: (url) => videoPlayer.play(url),
|
|
3770
|
+
* onVideoStop: () => videoPlayer.stop()
|
|
3771
|
+
* })
|
|
3772
|
+
*
|
|
3773
|
+
* // Advanced usage - custom sceneId and title:
|
|
3774
|
+
* staticTV.enableProFeatures({
|
|
3775
|
+
* sceneId: 'my-scene', // optional - defaults to API key ID
|
|
3776
|
+
* title: 'MY SCENE ADMIN',
|
|
3777
|
+
* onVideoPlay: (url) => videoPlayer.play(url),
|
|
3778
|
+
* onVideoStop: () => videoPlayer.stop(),
|
|
3779
|
+
* onBroadcast: (text) => showNotification(text)
|
|
3780
|
+
* })
|
|
3781
|
+
* ```
|
|
3782
|
+
*/
|
|
3783
|
+
enableProFeatures(config = {}) {
|
|
3784
|
+
if (this._proFeaturesEnabled) {
|
|
3785
|
+
this.log("Pro features already enabled");
|
|
3786
|
+
return;
|
|
3787
|
+
}
|
|
3788
|
+
this._pendingProConfig = config;
|
|
3789
|
+
if (this._tier === "pro") {
|
|
3790
|
+
this._initProModules();
|
|
3791
|
+
} else {
|
|
3792
|
+
this.log("Pro features configured - will enable when Pro tier is confirmed");
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
/**
|
|
3796
|
+
* Register a custom scene tab for the admin panel (Pro tier)
|
|
3797
|
+
* Must call enableProFeatures() first.
|
|
3798
|
+
*
|
|
3799
|
+
* @param tab The tab definition with label, id, and render function
|
|
3800
|
+
*
|
|
3801
|
+
* @example
|
|
3802
|
+
* ```typescript
|
|
3803
|
+
* staticTV.registerSceneTab({
|
|
3804
|
+
* label: 'LIGHTS',
|
|
3805
|
+
* id: 'lights',
|
|
3806
|
+
* render: () => <MyLightsControls />
|
|
3807
|
+
* })
|
|
3808
|
+
* ```
|
|
3809
|
+
*/
|
|
3810
|
+
registerSceneTab(tab) {
|
|
3811
|
+
if (!this.adminPanel) {
|
|
3812
|
+
this.log("Cannot register scene tab - call enableProFeatures() first");
|
|
3813
|
+
return;
|
|
3814
|
+
}
|
|
3815
|
+
this.adminPanel.registerSceneTab(tab);
|
|
3816
|
+
}
|
|
3817
|
+
/**
|
|
3818
|
+
* Close Admin/Guide panels (they share the same screen space)
|
|
3819
|
+
* Chat is independent and stays open.
|
|
3820
|
+
* @param except The panel that should stay open: 'admin' | 'guide'
|
|
3821
|
+
*/
|
|
3822
|
+
closeOtherPanels(except) {
|
|
3823
|
+
if (except !== "guide" && this.guideUI?.isVisible) {
|
|
3824
|
+
this.guideUI.hide();
|
|
3825
|
+
}
|
|
3826
|
+
if (except !== "admin" && this.adminPanel?.isOpen) {
|
|
3827
|
+
this.adminPanel.hide();
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
/**
|
|
3831
|
+
* Set up the UI renderer for all SDK panels
|
|
3832
|
+
* Call this in your scene's main() function to render Guide, Chat, Admin panels.
|
|
3833
|
+
* No need to create your own ui.tsx - the SDK handles everything.
|
|
3834
|
+
*
|
|
3835
|
+
* @example
|
|
3836
|
+
* ```typescript
|
|
3837
|
+
* const staticTV = new StaticTVClient({ apiKey: 'dcls_...' })
|
|
3838
|
+
*
|
|
3839
|
+
* export function main() {
|
|
3840
|
+
* staticTV.setupUI()
|
|
3841
|
+
* // That's it! All panels will render automatically
|
|
3842
|
+
* }
|
|
3843
|
+
* ```
|
|
3844
|
+
*/
|
|
3845
|
+
setupUI() {
|
|
3846
|
+
setupStaticUI(this);
|
|
3847
|
+
this.log("UI renderer initialized");
|
|
3848
|
+
}
|
|
3849
|
+
/**
|
|
3850
|
+
* Show a notification message on screen
|
|
3851
|
+
* Works with both SDK-rendered UI and custom UI setups.
|
|
3852
|
+
*
|
|
3853
|
+
* @param message The message to display
|
|
3854
|
+
* @param durationMs How long to show (default 5000ms)
|
|
3855
|
+
*
|
|
3856
|
+
* @example
|
|
3857
|
+
* ```typescript
|
|
3858
|
+
* staticTV.showNotification('Stream started!')
|
|
3859
|
+
* staticTV.showNotification('Custom message', 10000) // 10 seconds
|
|
3860
|
+
* ```
|
|
3861
|
+
*/
|
|
3862
|
+
showNotification(message, durationMs = 5e3) {
|
|
3863
|
+
showNotification(message, durationMs);
|
|
2338
3864
|
}
|
|
2339
3865
|
/**
|
|
2340
3866
|
* Cleanup when done (call before scene unload)
|
|
@@ -2358,6 +3884,7 @@ var StaticTVClient = class {
|
|
|
2358
3884
|
};
|
|
2359
3885
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2360
3886
|
0 && (module.exports = {
|
|
3887
|
+
AdminPanelUIModule,
|
|
2361
3888
|
ChatUIModule,
|
|
2362
3889
|
GuideModule,
|
|
2363
3890
|
GuideUIModule,
|
|
@@ -2369,5 +3896,7 @@ var StaticTVClient = class {
|
|
|
2369
3896
|
StaticTVClient,
|
|
2370
3897
|
fetchUserData,
|
|
2371
3898
|
getPlayerDisplayName,
|
|
2372
|
-
getPlayerWallet
|
|
3899
|
+
getPlayerWallet,
|
|
3900
|
+
setupStaticUI,
|
|
3901
|
+
showNotification
|
|
2373
3902
|
});
|