@thestatic-tv/dcl-sdk 2.3.0-dev.0 → 2.4.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 +55 -413
- package/dist/index.d.mts +359 -69
- package/dist/index.d.ts +359 -69
- package/dist/index.js +986 -412
- package/dist/index.mjs +983 -411
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -42,7 +42,9 @@ __export(index_exports, {
|
|
|
42
42
|
StaticTVClient: () => StaticTVClient,
|
|
43
43
|
fetchUserData: () => fetchUserData,
|
|
44
44
|
getPlayerDisplayName: () => getPlayerDisplayName,
|
|
45
|
-
getPlayerWallet: () => getPlayerWallet
|
|
45
|
+
getPlayerWallet: () => getPlayerWallet,
|
|
46
|
+
setupStaticUI: () => setupStaticUI,
|
|
47
|
+
showNotification: () => showNotification
|
|
46
48
|
});
|
|
47
49
|
module.exports = __toCommonJS(index_exports);
|
|
48
50
|
|
|
@@ -173,7 +175,7 @@ function ensureTimerSystem() {
|
|
|
173
175
|
try {
|
|
174
176
|
timer.callback();
|
|
175
177
|
} catch (e) {
|
|
176
|
-
console.error("[
|
|
178
|
+
console.error("[TheStatic] Timer error");
|
|
177
179
|
}
|
|
178
180
|
}
|
|
179
181
|
}
|
|
@@ -214,7 +216,7 @@ function ensureTimeoutSystem() {
|
|
|
214
216
|
try {
|
|
215
217
|
timeout.callback();
|
|
216
218
|
} catch (e) {
|
|
217
|
-
console.error("[
|
|
219
|
+
console.error("[TheStatic] Timeout error");
|
|
218
220
|
}
|
|
219
221
|
}
|
|
220
222
|
}
|
|
@@ -245,20 +247,41 @@ function dclClearTimeout(timeoutId) {
|
|
|
245
247
|
}
|
|
246
248
|
|
|
247
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
|
+
}
|
|
248
256
|
var SessionModule = class {
|
|
249
257
|
constructor(client) {
|
|
250
258
|
this.sessionId = null;
|
|
259
|
+
this._keyId = null;
|
|
251
260
|
this.heartbeatTimerId = null;
|
|
252
261
|
this.isActive = false;
|
|
253
|
-
this.
|
|
262
|
+
this._tier = "free";
|
|
254
263
|
this.client = client;
|
|
255
264
|
}
|
|
256
265
|
/**
|
|
257
|
-
* Get the
|
|
258
|
-
|
|
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.
|
|
259
282
|
*/
|
|
260
283
|
get sdkType() {
|
|
261
|
-
return this.
|
|
284
|
+
return this._tier === "free" ? "lite" : "full";
|
|
262
285
|
}
|
|
263
286
|
/**
|
|
264
287
|
* Get the appropriate session endpoint based on key type
|
|
@@ -305,13 +328,12 @@ var SessionModule = class {
|
|
|
305
328
|
});
|
|
306
329
|
if (response.success && response.sessionId) {
|
|
307
330
|
this.sessionId = response.sessionId;
|
|
331
|
+
this._keyId = response.keyId || null;
|
|
308
332
|
this.isActive = true;
|
|
309
|
-
this.
|
|
333
|
+
this._tier = normalizeTier(response.sdkType);
|
|
310
334
|
this.startHeartbeat();
|
|
311
|
-
this.client.log(`Session started: ${this.sessionId},
|
|
312
|
-
|
|
313
|
-
this.client._enableFullFeatures();
|
|
314
|
-
}
|
|
335
|
+
this.client.log(`Session started: ${this.sessionId}, tier: ${this._tier}`);
|
|
336
|
+
this.client._enableFeaturesForTier(this._tier, this._keyId);
|
|
315
337
|
return this.sessionId;
|
|
316
338
|
}
|
|
317
339
|
return null;
|
|
@@ -599,7 +621,8 @@ var UI_DIMENSIONS = {
|
|
|
599
621
|
// Guide UI - positioned to the left of chat (chat is 380px wide at right:20)
|
|
600
622
|
guide: {
|
|
601
623
|
width: 900,
|
|
602
|
-
height:
|
|
624
|
+
height: 580,
|
|
625
|
+
// Numeric value for scaling (matches chat/admin)
|
|
603
626
|
bottom: 55,
|
|
604
627
|
right: 410,
|
|
605
628
|
// 20 + 380 (chat width) + 10 (gap)
|
|
@@ -618,15 +641,33 @@ var UI_DIMENSIONS = {
|
|
|
618
641
|
padding: 15
|
|
619
642
|
}
|
|
620
643
|
},
|
|
621
|
-
// Chat UI -
|
|
644
|
+
// Chat UI - positioned at right side
|
|
622
645
|
chat: {
|
|
623
646
|
width: 380,
|
|
624
647
|
height: 580,
|
|
625
648
|
bottom: 55,
|
|
626
649
|
right: 20,
|
|
650
|
+
headerHeight: 40,
|
|
627
651
|
messagesPerPage: 5,
|
|
628
652
|
channelsPerPage: 6
|
|
629
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
|
+
},
|
|
630
671
|
// Shared
|
|
631
672
|
closeButton: {
|
|
632
673
|
size: 40,
|
|
@@ -634,18 +675,18 @@ var UI_DIMENSIONS = {
|
|
|
634
675
|
}
|
|
635
676
|
};
|
|
636
677
|
var DEFAULT_CHAT_THEME = {
|
|
637
|
-
header:
|
|
678
|
+
header: 16,
|
|
638
679
|
channelButton: 14,
|
|
639
680
|
channelDropdown: 14,
|
|
640
|
-
systemMessage:
|
|
641
|
-
chatUsername:
|
|
681
|
+
systemMessage: 13,
|
|
682
|
+
chatUsername: 14,
|
|
642
683
|
chatTimestamp: 11,
|
|
643
|
-
chatMessage:
|
|
644
|
-
input:
|
|
684
|
+
chatMessage: 14,
|
|
685
|
+
input: 14,
|
|
645
686
|
sendButton: 14,
|
|
646
|
-
userInfo:
|
|
687
|
+
userInfo: 13,
|
|
647
688
|
authStatus: 12,
|
|
648
|
-
notification:
|
|
689
|
+
notification: 16,
|
|
649
690
|
closeButton: 16
|
|
650
691
|
};
|
|
651
692
|
function scaleChatTheme(theme, fontScale) {
|
|
@@ -665,6 +706,30 @@ function scaleChatTheme(theme, fontScale) {
|
|
|
665
706
|
closeButton: Math.round(theme.closeButton * fontScale)
|
|
666
707
|
};
|
|
667
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
|
+
}
|
|
668
733
|
|
|
669
734
|
// src/ui/components.tsx
|
|
670
735
|
var import_react_ecs = __toESM(require("@dcl/sdk/react-ecs"));
|
|
@@ -720,21 +785,21 @@ var PanelHeader = (props) => {
|
|
|
720
785
|
}),
|
|
721
786
|
import_react_ecs.default.createElement(import_react_ecs.UiEntity, { key: "pos-spacer", uiTransform: { width: 6 } })
|
|
722
787
|
] : [],
|
|
723
|
-
// Font controls
|
|
788
|
+
// Font controls (with visual disabled state at limits)
|
|
724
789
|
...props.showFontControls ? [
|
|
725
790
|
import_react_ecs.default.createElement(import_react_ecs.UiEntity, {
|
|
726
791
|
key: "font-down",
|
|
727
792
|
uiTransform: { width: 22, height: 22, justifyContent: "center", alignItems: "center" },
|
|
728
|
-
uiBackground: { color: THEME.colors.buttonBackground },
|
|
729
|
-
onMouseDown: () => props.onFontScaleDown?.(),
|
|
730
|
-
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 } })]
|
|
731
796
|
}),
|
|
732
797
|
import_react_ecs.default.createElement(import_react_ecs.UiEntity, {
|
|
733
798
|
key: "font-up",
|
|
734
799
|
uiTransform: { width: 22, height: 22, justifyContent: "center", alignItems: "center" },
|
|
735
|
-
uiBackground: { color: THEME.colors.buttonBackground },
|
|
736
|
-
onMouseDown: () => props.onFontScaleUp?.(),
|
|
737
|
-
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 } })]
|
|
738
803
|
}),
|
|
739
804
|
import_react_ecs.default.createElement(import_react_ecs.UiEntity, { key: "font-spacer", uiTransform: { width: 6 } })
|
|
740
805
|
] : [],
|
|
@@ -772,7 +837,6 @@ var GuideUIModule = class {
|
|
|
772
837
|
this.currentPage = 0;
|
|
773
838
|
this.itemsPerPage = 6;
|
|
774
839
|
this.searchQuery = "";
|
|
775
|
-
this.uiScale = 1;
|
|
776
840
|
// Current video tracking (for "PLAYING" indicator)
|
|
777
841
|
this._currentVideoId = null;
|
|
778
842
|
// =============================================================================
|
|
@@ -780,34 +844,46 @@ var GuideUIModule = class {
|
|
|
780
844
|
// =============================================================================
|
|
781
845
|
/**
|
|
782
846
|
* Get the guide UI component for rendering
|
|
783
|
-
*
|
|
847
|
+
* Always renders toggle button, plus panel when visible
|
|
784
848
|
*/
|
|
785
849
|
this.getComponent = () => {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
}
|
|
789
|
-
const windowW = this.s(UI_DIMENSIONS.guide.width);
|
|
850
|
+
const windowW = UI_DIMENSIONS.guide.width;
|
|
851
|
+
const windowH = UI_DIMENSIONS.guide.height;
|
|
790
852
|
return import_react_ecs2.default.createElement(import_react_ecs2.UiEntity, {
|
|
853
|
+
key: `guide-root-${this.client.uiScale}`,
|
|
791
854
|
uiTransform: {
|
|
792
|
-
width:
|
|
793
|
-
height:
|
|
794
|
-
positionType: "absolute"
|
|
795
|
-
position: { bottom: UI_DIMENSIONS.guide.bottom, right: UI_DIMENSIONS.guide.right },
|
|
796
|
-
flexDirection: "row",
|
|
797
|
-
border: { top: 2, bottom: 2, left: 2, right: 2 },
|
|
798
|
-
borderColor: THEME.colors.panelBorder
|
|
855
|
+
width: "100%",
|
|
856
|
+
height: "100%",
|
|
857
|
+
positionType: "absolute"
|
|
799
858
|
},
|
|
800
|
-
uiBackground: { color: THEME.colors.panel },
|
|
801
859
|
children: [
|
|
802
|
-
|
|
803
|
-
this.
|
|
804
|
-
|
|
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
|
|
805
882
|
]
|
|
806
883
|
});
|
|
807
884
|
};
|
|
808
885
|
this.client = client;
|
|
809
886
|
this.config = config;
|
|
810
|
-
this.uiScale = config.uiScale || 1;
|
|
811
887
|
this._currentVideoId = config.currentVideoId || null;
|
|
812
888
|
}
|
|
813
889
|
/**
|
|
@@ -821,6 +897,8 @@ var GuideUIModule = class {
|
|
|
821
897
|
* Show the guide UI
|
|
822
898
|
*/
|
|
823
899
|
show() {
|
|
900
|
+
if (this._isVisible) return;
|
|
901
|
+
this.client.closeOtherPanels("guide");
|
|
824
902
|
this._isVisible = true;
|
|
825
903
|
this.fetchGuideData().catch(() => {
|
|
826
904
|
});
|
|
@@ -956,7 +1034,7 @@ var GuideUIModule = class {
|
|
|
956
1034
|
// --- UTILITIES ---
|
|
957
1035
|
// =============================================================================
|
|
958
1036
|
s(value) {
|
|
959
|
-
return Math.round(value * this.uiScale);
|
|
1037
|
+
return Math.round(value * this.client.uiScale);
|
|
960
1038
|
}
|
|
961
1039
|
handleVideoSelect(video) {
|
|
962
1040
|
if (this.config.onVideoSelect) {
|
|
@@ -971,7 +1049,7 @@ var GuideUIModule = class {
|
|
|
971
1049
|
renderLeftPanel() {
|
|
972
1050
|
const sidebarW = this.s(UI_DIMENSIONS.guide.sidebar.width);
|
|
973
1051
|
const liveCount = this.liveVideos.length;
|
|
974
|
-
const signalCount = this.videos.filter((v) => !v.isLive).length;
|
|
1052
|
+
const signalCount = this.videos.filter((v) => !v.isLive && !v.isCreator).length;
|
|
975
1053
|
const nodesPlaylist = this.featuredPlaylists.find((p) => p.name === "NODES");
|
|
976
1054
|
const nodesCount = nodesPlaylist ? nodesPlaylist.videos.filter((v) => v.isCreator).length : 0;
|
|
977
1055
|
const displayedPlaylists = this.featuredPlaylists.filter((p) => p.name !== "NODES").slice(0, 6);
|
|
@@ -992,15 +1070,26 @@ var GuideUIModule = class {
|
|
|
992
1070
|
import_react_ecs2.default.createElement(import_react_ecs2.UiEntity, {
|
|
993
1071
|
uiTransform: { width: "100%", flexDirection: "column", padding: 10 },
|
|
994
1072
|
children: [
|
|
995
|
-
// Title
|
|
1073
|
+
// Title row
|
|
996
1074
|
import_react_ecs2.default.createElement(import_react_ecs2.UiEntity, {
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1075
|
+
uiTransform: {
|
|
1076
|
+
width: "100%",
|
|
1077
|
+
height: this.s(40),
|
|
1078
|
+
marginBottom: 10,
|
|
1079
|
+
flexDirection: "row",
|
|
1080
|
+
alignItems: "center"
|
|
1002
1081
|
},
|
|
1003
|
-
|
|
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
|
+
]
|
|
1004
1093
|
}),
|
|
1005
1094
|
// Random Signal button
|
|
1006
1095
|
import_react_ecs2.default.createElement(import_react_ecs2.UiEntity, {
|
|
@@ -1057,7 +1146,7 @@ var GuideUIModule = class {
|
|
|
1057
1146
|
}),
|
|
1058
1147
|
// Menu buttons
|
|
1059
1148
|
this.renderMenuButton("LIVE NOW", liveCount, THEME.colors.red, liveCount > 0 ? "\u25CF" : void 0, "ACTIVE STREAMS"),
|
|
1060
|
-
this.renderMenuButton("SIGNALS", signalCount, THEME.colors.white, void 0, "
|
|
1149
|
+
this.renderMenuButton("SIGNALS", signalCount, THEME.colors.white, void 0, "VODS & RECORDINGS"),
|
|
1061
1150
|
this.renderMenuButton("NODES", nodesCount, THEME.colors.magenta, void 0, "CHANNELS & CREATORS"),
|
|
1062
1151
|
// Divider
|
|
1063
1152
|
import_react_ecs2.default.createElement(import_react_ecs2.UiEntity, {
|
|
@@ -1139,7 +1228,7 @@ var GuideUIModule = class {
|
|
|
1139
1228
|
let videosToShow = [];
|
|
1140
1229
|
const nodePl = this.featuredPlaylists.find((p) => p.name === "NODES");
|
|
1141
1230
|
if (this.activeTab === "SIGNALS") {
|
|
1142
|
-
videosToShow = this.videos.filter((v) => !v.isLive);
|
|
1231
|
+
videosToShow = this.videos.filter((v) => !v.isLive && !v.isCreator);
|
|
1143
1232
|
} else if (this.activeTab === "NODES") {
|
|
1144
1233
|
videosToShow = nodePl ? nodePl.videos.filter((v) => v.isCreator) : [];
|
|
1145
1234
|
} else if (this.activeTab === "LIVE NOW") {
|
|
@@ -1350,23 +1439,25 @@ var GuideUIModule = class {
|
|
|
1350
1439
|
});
|
|
1351
1440
|
}
|
|
1352
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;
|
|
1353
1445
|
return import_react_ecs2.default.createElement(import_react_ecs2.UiEntity, {
|
|
1354
1446
|
uiTransform: {
|
|
1355
1447
|
positionType: "absolute",
|
|
1356
|
-
position: { right:
|
|
1357
|
-
// To the left of CHAT button
|
|
1448
|
+
position: { right: buttonPos, bottom: 10 },
|
|
1358
1449
|
width: this.s(100),
|
|
1359
1450
|
height: this.s(45),
|
|
1360
1451
|
justifyContent: "center",
|
|
1361
1452
|
alignItems: "center"
|
|
1362
1453
|
},
|
|
1363
|
-
uiBackground: { color:
|
|
1364
|
-
onMouseDown: () => this.show(),
|
|
1454
|
+
uiBackground: { color: buttonColor },
|
|
1455
|
+
onMouseDown: () => this._isVisible ? this.hide() : this.show(),
|
|
1365
1456
|
children: [
|
|
1366
1457
|
import_react_ecs2.default.createElement(import_react_ecs2.UiEntity, {
|
|
1367
1458
|
uiText: {
|
|
1368
|
-
value:
|
|
1369
|
-
fontSize: this.s(
|
|
1459
|
+
value: buttonText,
|
|
1460
|
+
fontSize: this.s(14),
|
|
1370
1461
|
color: THEME.colors.white,
|
|
1371
1462
|
textAlign: "middle-center"
|
|
1372
1463
|
}
|
|
@@ -1408,7 +1499,6 @@ var ChatUIModule = class {
|
|
|
1408
1499
|
this.chatScrollOffset = 0;
|
|
1409
1500
|
// UI preferences
|
|
1410
1501
|
this.position = "right";
|
|
1411
|
-
this.fontScale = 1;
|
|
1412
1502
|
// Timers
|
|
1413
1503
|
this.chatTimerId = null;
|
|
1414
1504
|
this.playerInfoTimerId = null;
|
|
@@ -1418,43 +1508,52 @@ var ChatUIModule = class {
|
|
|
1418
1508
|
// =============================================================================
|
|
1419
1509
|
/**
|
|
1420
1510
|
* Get the chat UI component for rendering
|
|
1421
|
-
*
|
|
1511
|
+
* Always renders toggle button, plus panel when visible
|
|
1422
1512
|
*/
|
|
1423
1513
|
this.getComponent = () => {
|
|
1424
|
-
|
|
1425
|
-
return this.renderToggleButton();
|
|
1426
|
-
}
|
|
1427
|
-
const scaledTheme = scaleChatTheme(DEFAULT_CHAT_THEME, this.fontScale);
|
|
1514
|
+
const scaledTheme = scaleChatTheme(DEFAULT_CHAT_THEME, this.client.uiScale);
|
|
1428
1515
|
const positionStyle = this.getPositionStyle();
|
|
1429
1516
|
return import_react_ecs3.default.createElement(import_react_ecs3.UiEntity, {
|
|
1430
1517
|
uiTransform: {
|
|
1431
|
-
width:
|
|
1432
|
-
height:
|
|
1433
|
-
positionType: "absolute"
|
|
1434
|
-
position: positionStyle,
|
|
1435
|
-
// Must be nested object, not spread!
|
|
1436
|
-
flexDirection: "column",
|
|
1437
|
-
border: { top: 2, bottom: 2, left: 2, right: 2 },
|
|
1438
|
-
borderColor: THEME.colors.panelBorder
|
|
1518
|
+
width: "100%",
|
|
1519
|
+
height: "100%",
|
|
1520
|
+
positionType: "absolute"
|
|
1439
1521
|
},
|
|
1440
|
-
uiBackground: { color: THEME.colors.panel },
|
|
1441
1522
|
children: [
|
|
1442
|
-
|
|
1443
|
-
this.
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
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
|
|
1452
1552
|
]
|
|
1453
1553
|
});
|
|
1454
1554
|
};
|
|
1455
1555
|
this.client = client;
|
|
1456
1556
|
this.config = config;
|
|
1457
|
-
this.fontScale = config.fontScale || 1;
|
|
1458
1557
|
}
|
|
1459
1558
|
/**
|
|
1460
1559
|
* Initialize the chat system
|
|
@@ -1473,6 +1572,7 @@ var ChatUIModule = class {
|
|
|
1473
1572
|
* Show the chat UI
|
|
1474
1573
|
*/
|
|
1475
1574
|
show() {
|
|
1575
|
+
if (this._isVisible) return;
|
|
1476
1576
|
this._isVisible = true;
|
|
1477
1577
|
this._unreadCount = 0;
|
|
1478
1578
|
this.chatScrollOffset = 0;
|
|
@@ -1734,6 +1834,10 @@ var ChatUIModule = class {
|
|
|
1734
1834
|
const d = new Date(input);
|
|
1735
1835
|
if (!isNaN(d.getTime())) return d.getTime();
|
|
1736
1836
|
}
|
|
1837
|
+
if (input instanceof Date) return input.getTime();
|
|
1838
|
+
if (typeof input === "object" && "seconds" in input) {
|
|
1839
|
+
return input.seconds * 1e3;
|
|
1840
|
+
}
|
|
1737
1841
|
return 0;
|
|
1738
1842
|
}
|
|
1739
1843
|
formatTime(isoString) {
|
|
@@ -1746,6 +1850,10 @@ var ChatUIModule = class {
|
|
|
1746
1850
|
return "";
|
|
1747
1851
|
}
|
|
1748
1852
|
}
|
|
1853
|
+
/** Scale a dimension by shared uiScale */
|
|
1854
|
+
s(value) {
|
|
1855
|
+
return Math.round(value * this.client.uiScale);
|
|
1856
|
+
}
|
|
1749
1857
|
getPositionStyle() {
|
|
1750
1858
|
return { bottom: 55, right: 20 };
|
|
1751
1859
|
}
|
|
@@ -1753,15 +1861,9 @@ var ChatUIModule = class {
|
|
|
1753
1861
|
return PanelHeader({
|
|
1754
1862
|
title: "LIVE CHAT",
|
|
1755
1863
|
fontSize: 14,
|
|
1756
|
-
fontScale:
|
|
1864
|
+
fontScale: 1,
|
|
1757
1865
|
showPositionControls: false,
|
|
1758
|
-
showFontControls:
|
|
1759
|
-
onFontScaleUp: () => {
|
|
1760
|
-
this.fontScale = Math.min(1.4, this.fontScale + 0.1);
|
|
1761
|
-
},
|
|
1762
|
-
onFontScaleDown: () => {
|
|
1763
|
-
this.fontScale = Math.max(0.7, this.fontScale - 0.1);
|
|
1764
|
-
},
|
|
1866
|
+
showFontControls: false,
|
|
1765
1867
|
onClose: () => this.hide()
|
|
1766
1868
|
});
|
|
1767
1869
|
}
|
|
@@ -2136,23 +2238,24 @@ var ChatUIModule = class {
|
|
|
2136
2238
|
}
|
|
2137
2239
|
renderToggleButton() {
|
|
2138
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);
|
|
2139
2243
|
return import_react_ecs3.default.createElement(import_react_ecs3.UiEntity, {
|
|
2140
2244
|
uiTransform: {
|
|
2141
2245
|
positionType: "absolute",
|
|
2142
2246
|
position: { right: 20, bottom: 10 },
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
height: 45,
|
|
2247
|
+
width: this.s(100),
|
|
2248
|
+
height: this.s(45),
|
|
2146
2249
|
justifyContent: "center",
|
|
2147
2250
|
alignItems: "center"
|
|
2148
2251
|
},
|
|
2149
|
-
uiBackground: { color:
|
|
2150
|
-
onMouseDown: () => this.show(),
|
|
2252
|
+
uiBackground: { color: buttonColor },
|
|
2253
|
+
onMouseDown: () => this._isVisible ? this.hide() : this.show(),
|
|
2151
2254
|
children: [
|
|
2152
2255
|
import_react_ecs3.default.createElement(import_react_ecs3.UiEntity, {
|
|
2153
2256
|
uiText: {
|
|
2154
|
-
value:
|
|
2155
|
-
fontSize:
|
|
2257
|
+
value: buttonText,
|
|
2258
|
+
fontSize: this.s(14),
|
|
2156
2259
|
color: THEME.colors.white,
|
|
2157
2260
|
textAlign: "middle-center"
|
|
2158
2261
|
}
|
|
@@ -2186,6 +2289,7 @@ var C = {
|
|
|
2186
2289
|
textDim: import_math5.Color4.create(0.6, 0.6, 0.7, 1)
|
|
2187
2290
|
};
|
|
2188
2291
|
var AdminPanelUIModule = class {
|
|
2292
|
+
// UI scaling - uses shared client.uiScale
|
|
2189
2293
|
constructor(client, config) {
|
|
2190
2294
|
// State
|
|
2191
2295
|
this.isAdmin = false;
|
|
@@ -2197,6 +2301,8 @@ var AdminPanelUIModule = class {
|
|
|
2197
2301
|
this.customVideoUrl = "";
|
|
2198
2302
|
this.streamData = null;
|
|
2199
2303
|
this.streamFetched = false;
|
|
2304
|
+
this.videoState = null;
|
|
2305
|
+
this.videoStateFetched = false;
|
|
2200
2306
|
this.channelCreating = false;
|
|
2201
2307
|
this.channelCreateError = "";
|
|
2202
2308
|
this.channelDeleting = false;
|
|
@@ -2220,18 +2326,18 @@ var AdminPanelUIModule = class {
|
|
|
2220
2326
|
this.SectionHead = ({ label, color }) => /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2221
2327
|
import_react_ecs4.UiEntity,
|
|
2222
2328
|
{
|
|
2223
|
-
uiTransform: { width: "100%", height:
|
|
2329
|
+
uiTransform: { width: "100%", height: this.s(UI_DIMENSIONS.admin.sectionHeadHeight), margin: { bottom: 8 }, padding: { left: 10 }, alignItems: "center" },
|
|
2224
2330
|
uiBackground: { color: import_math5.Color4.create(color.r * 0.3, color.g * 0.3, color.b * 0.3, 0.5) }
|
|
2225
2331
|
},
|
|
2226
|
-
/* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: label, fontSize:
|
|
2332
|
+
/* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: label, fontSize: this.theme.sectionHead, color })
|
|
2227
2333
|
);
|
|
2228
2334
|
this.TabBtn = ({ label, tab }) => /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2229
2335
|
import_react_ecs4.Button,
|
|
2230
2336
|
{
|
|
2231
|
-
uiTransform: { flexGrow: 1, height:
|
|
2337
|
+
uiTransform: { flexGrow: 1, height: this.s(UI_DIMENSIONS.admin.tabHeight), justifyContent: "center", alignItems: "center" },
|
|
2232
2338
|
uiBackground: { color: this.activeTab === tab ? C.tabActive : C.tabInactive },
|
|
2233
2339
|
value: label,
|
|
2234
|
-
fontSize:
|
|
2340
|
+
fontSize: this.theme.tabButton,
|
|
2235
2341
|
color: this.activeTab === tab ? import_math5.Color4.Black() : C.text,
|
|
2236
2342
|
textAlign: "middle-center",
|
|
2237
2343
|
onMouseDown: () => this.setActiveTab(tab)
|
|
@@ -2241,134 +2347,135 @@ var AdminPanelUIModule = class {
|
|
|
2241
2347
|
if (!this.streamFetched) {
|
|
2242
2348
|
this.fetchStreamData();
|
|
2243
2349
|
}
|
|
2244
|
-
|
|
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(
|
|
2245
2352
|
import_react_ecs4.Button,
|
|
2246
2353
|
{
|
|
2247
|
-
uiTransform: { width:
|
|
2354
|
+
uiTransform: { width: this.s(200), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: { bottom: 6 } },
|
|
2248
2355
|
uiBackground: { color: this.trialClaiming ? C.btn : C.green },
|
|
2249
|
-
value: this.trialClaiming ? "Claiming..." : "
|
|
2250
|
-
fontSize:
|
|
2356
|
+
value: this.trialClaiming ? "Claiming..." : "Start Free 4-Hour Trial",
|
|
2357
|
+
fontSize: t.button,
|
|
2251
2358
|
color: C.text,
|
|
2252
2359
|
onMouseDown: () => this.claimTrial()
|
|
2253
2360
|
}
|
|
2254
|
-
), this.trialClaimError && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: this.trialClaimError, fontSize:
|
|
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(
|
|
2255
2362
|
import_react_ecs4.Button,
|
|
2256
2363
|
{
|
|
2257
|
-
uiTransform: { width:
|
|
2364
|
+
uiTransform: { width: this.s(170), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: { bottom: 6 } },
|
|
2258
2365
|
uiBackground: { color: this.channelCreating ? C.btn : C.cyan },
|
|
2259
2366
|
value: this.channelCreating ? "Creating..." : "+ Create Channel",
|
|
2260
|
-
fontSize:
|
|
2367
|
+
fontSize: t.button,
|
|
2261
2368
|
color: C.text,
|
|
2262
2369
|
onMouseDown: () => this.createChannel()
|
|
2263
2370
|
}
|
|
2264
|
-
), this.channelCreateError && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: this.channelCreateError, fontSize:
|
|
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(
|
|
2265
2372
|
import_react_ecs4.Label,
|
|
2266
2373
|
{
|
|
2267
2374
|
value: this.streamData.isLive ? `LIVE \u2022 ${this.streamData.currentViewers || 0} viewers` : "OFFLINE",
|
|
2268
|
-
fontSize:
|
|
2375
|
+
fontSize: t.label,
|
|
2269
2376
|
color: this.streamData.isLive ? C.red : C.textDim
|
|
2270
2377
|
}
|
|
2271
|
-
), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: ` \u2022 ${this.streamData.tier?.toUpperCase() || "RELAY"}`, fontSize:
|
|
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(
|
|
2272
2379
|
import_react_ecs4.Label,
|
|
2273
2380
|
{
|
|
2274
2381
|
value: `Channel: ${this.streamData.channelName || this.streamData.channelId}`,
|
|
2275
|
-
fontSize:
|
|
2382
|
+
fontSize: t.labelSmall,
|
|
2276
2383
|
color: C.textDim,
|
|
2277
|
-
uiTransform: { margin: { bottom:
|
|
2384
|
+
uiTransform: { margin: { bottom: 10 } }
|
|
2278
2385
|
}
|
|
2279
|
-
), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom:
|
|
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(
|
|
2280
2387
|
import_react_ecs4.Label,
|
|
2281
2388
|
{
|
|
2282
2389
|
value: this.streamData.rtmpUrl || "Loading...",
|
|
2283
|
-
fontSize:
|
|
2390
|
+
fontSize: t.label,
|
|
2284
2391
|
color: this.streamData.rtmpUrl ? C.text : C.textDim,
|
|
2285
|
-
uiTransform: { margin: { left:
|
|
2392
|
+
uiTransform: { margin: { left: 6 } }
|
|
2286
2393
|
}
|
|
2287
|
-
)), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom:
|
|
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(
|
|
2288
2395
|
import_react_ecs4.Label,
|
|
2289
2396
|
{
|
|
2290
2397
|
value: this.streamData.streamKey || "Loading...",
|
|
2291
|
-
fontSize:
|
|
2398
|
+
fontSize: t.label,
|
|
2292
2399
|
color: this.streamData.streamKey ? C.cyan : C.textDim,
|
|
2293
|
-
uiTransform: { margin: { left:
|
|
2400
|
+
uiTransform: { margin: { left: 6 } }
|
|
2294
2401
|
}
|
|
2295
|
-
)), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom:
|
|
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(
|
|
2296
2403
|
import_react_ecs4.Label,
|
|
2297
2404
|
{
|
|
2298
2405
|
value: this.streamData.hlsUrl || "Not available",
|
|
2299
|
-
fontSize:
|
|
2406
|
+
fontSize: t.label,
|
|
2300
2407
|
color: this.streamData.hlsUrl ? C.green : C.textDim,
|
|
2301
|
-
uiTransform: { margin: { left:
|
|
2408
|
+
uiTransform: { margin: { left: 6 } }
|
|
2302
2409
|
}
|
|
2303
|
-
)), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom:
|
|
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(
|
|
2304
2411
|
import_react_ecs4.Button,
|
|
2305
2412
|
{
|
|
2306
|
-
uiTransform: { width:
|
|
2413
|
+
uiTransform: { width: this.s(110), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2307
2414
|
uiBackground: { color: this.streamControlling ? C.btn : C.green },
|
|
2308
2415
|
value: this.streamControlling ? "Starting..." : "Start Stream",
|
|
2309
|
-
fontSize:
|
|
2416
|
+
fontSize: t.buttonSmall,
|
|
2310
2417
|
color: C.text,
|
|
2311
2418
|
onMouseDown: () => this.startStream()
|
|
2312
2419
|
}
|
|
2313
2420
|
), this.streamData.isLive && /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2314
2421
|
import_react_ecs4.Button,
|
|
2315
2422
|
{
|
|
2316
|
-
uiTransform: { width:
|
|
2423
|
+
uiTransform: { width: this.s(110), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2317
2424
|
uiBackground: { color: this.streamControlling ? C.btn : C.red },
|
|
2318
2425
|
value: this.streamControlling ? "Stopping..." : "Stop Stream",
|
|
2319
|
-
fontSize:
|
|
2426
|
+
fontSize: t.buttonSmall,
|
|
2320
2427
|
color: C.text,
|
|
2321
2428
|
onMouseDown: () => this.stopStream()
|
|
2322
2429
|
}
|
|
2323
2430
|
), this.streamData.isLive && this.streamData.hlsUrl && /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2324
2431
|
import_react_ecs4.Button,
|
|
2325
2432
|
{
|
|
2326
|
-
uiTransform: { width:
|
|
2433
|
+
uiTransform: { width: this.s(100), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2327
2434
|
uiBackground: { color: C.cyan },
|
|
2328
2435
|
value: "Play on Screen",
|
|
2329
|
-
fontSize:
|
|
2436
|
+
fontSize: t.buttonSmall,
|
|
2330
2437
|
color: C.text,
|
|
2331
2438
|
onMouseDown: () => this.config.onVideoPlay?.(this.streamData.hlsUrl)
|
|
2332
2439
|
}
|
|
2333
|
-
)), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom:
|
|
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(
|
|
2334
2441
|
import_react_ecs4.Button,
|
|
2335
2442
|
{
|
|
2336
|
-
uiTransform: { width:
|
|
2443
|
+
uiTransform: { width: this.s(80), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2337
2444
|
uiBackground: { color: C.btn },
|
|
2338
2445
|
value: "Refresh",
|
|
2339
|
-
fontSize:
|
|
2446
|
+
fontSize: t.buttonSmall,
|
|
2340
2447
|
color: C.text,
|
|
2341
2448
|
onMouseDown: () => this.refreshStreamStatus()
|
|
2342
2449
|
}
|
|
2343
2450
|
), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2344
2451
|
import_react_ecs4.Button,
|
|
2345
2452
|
{
|
|
2346
|
-
uiTransform: { width:
|
|
2453
|
+
uiTransform: { width: this.s(95), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2347
2454
|
uiBackground: { color: this.keyRotating ? C.btn : C.yellow },
|
|
2348
2455
|
value: this.keyRotating ? "..." : "Rotate Key",
|
|
2349
|
-
fontSize:
|
|
2456
|
+
fontSize: t.buttonSmall,
|
|
2350
2457
|
color: C.text,
|
|
2351
2458
|
onMouseDown: () => this.rotateStreamKey()
|
|
2352
2459
|
}
|
|
2353
2460
|
), !this.streamData.isLive && /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2354
2461
|
import_react_ecs4.Button,
|
|
2355
2462
|
{
|
|
2356
|
-
uiTransform: { width:
|
|
2463
|
+
uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2357
2464
|
uiBackground: { color: this.channelDeleting ? C.btn : C.red },
|
|
2358
2465
|
value: this.channelDeleting ? "..." : "Delete",
|
|
2359
|
-
fontSize:
|
|
2466
|
+
fontSize: t.buttonSmall,
|
|
2360
2467
|
color: C.text,
|
|
2361
2468
|
onMouseDown: () => this.deleteChannel()
|
|
2362
2469
|
}
|
|
2363
|
-
)), this.streamControlStatus === "started" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Stream started - begin broadcasting in OBS", fontSize:
|
|
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(
|
|
2364
2471
|
import_react_ecs4.Input,
|
|
2365
2472
|
{
|
|
2366
|
-
uiTransform: { width:
|
|
2473
|
+
uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
|
|
2367
2474
|
uiBackground: { color: import_math5.Color4.create(0.15, 0.15, 0.2, 1) },
|
|
2368
2475
|
placeholder: "Video URL...",
|
|
2369
2476
|
placeholderColor: C.textDim,
|
|
2370
2477
|
color: C.text,
|
|
2371
|
-
fontSize:
|
|
2478
|
+
fontSize: t.input,
|
|
2372
2479
|
value: this.customVideoUrl,
|
|
2373
2480
|
onChange: (val) => {
|
|
2374
2481
|
this.customVideoUrl = val;
|
|
@@ -2377,10 +2484,10 @@ var AdminPanelUIModule = class {
|
|
|
2377
2484
|
), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2378
2485
|
import_react_ecs4.Button,
|
|
2379
2486
|
{
|
|
2380
|
-
uiTransform: { width:
|
|
2487
|
+
uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
|
|
2381
2488
|
uiBackground: { color: C.green },
|
|
2382
2489
|
value: "Play",
|
|
2383
|
-
fontSize:
|
|
2490
|
+
fontSize: t.button,
|
|
2384
2491
|
color: C.text,
|
|
2385
2492
|
onMouseDown: () => {
|
|
2386
2493
|
if (this.customVideoUrl) this.config.onVideoPlay?.(this.customVideoUrl);
|
|
@@ -2389,211 +2496,218 @@ var AdminPanelUIModule = class {
|
|
|
2389
2496
|
), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2390
2497
|
import_react_ecs4.Button,
|
|
2391
2498
|
{
|
|
2392
|
-
uiTransform: { width:
|
|
2499
|
+
uiTransform: { width: this.s(65), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 6 } },
|
|
2393
2500
|
uiBackground: { color: C.btn },
|
|
2394
2501
|
value: "Clear",
|
|
2395
|
-
fontSize:
|
|
2502
|
+
fontSize: t.button,
|
|
2396
2503
|
color: C.text,
|
|
2397
2504
|
onMouseDown: () => {
|
|
2398
2505
|
this.customVideoUrl = "";
|
|
2399
2506
|
}
|
|
2400
2507
|
}
|
|
2401
|
-
)), /* @__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:
|
|
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(
|
|
2402
2509
|
import_react_ecs4.Button,
|
|
2403
2510
|
{
|
|
2404
|
-
uiTransform: { width:
|
|
2511
|
+
uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2405
2512
|
uiBackground: { color: C.green },
|
|
2406
2513
|
value: "Play",
|
|
2407
|
-
fontSize:
|
|
2514
|
+
fontSize: t.button,
|
|
2408
2515
|
color: C.text,
|
|
2409
2516
|
onMouseDown: () => this.config.onCommand?.("videoPlay", { playing: true })
|
|
2410
2517
|
}
|
|
2411
2518
|
), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2412
2519
|
import_react_ecs4.Button,
|
|
2413
2520
|
{
|
|
2414
|
-
uiTransform: { width:
|
|
2521
|
+
uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2415
2522
|
uiBackground: { color: C.red },
|
|
2416
2523
|
value: "Stop",
|
|
2417
|
-
fontSize:
|
|
2524
|
+
fontSize: t.button,
|
|
2418
2525
|
color: C.text,
|
|
2419
2526
|
onMouseDown: () => this.config.onVideoStop?.()
|
|
2420
2527
|
}
|
|
2421
2528
|
), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2422
2529
|
import_react_ecs4.Button,
|
|
2423
2530
|
{
|
|
2424
|
-
uiTransform: { width:
|
|
2531
|
+
uiTransform: { width: this.s(100), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2425
2532
|
uiBackground: { color: C.btn },
|
|
2426
2533
|
value: "Reset Default",
|
|
2427
|
-
fontSize:
|
|
2534
|
+
fontSize: t.buttonSmall,
|
|
2428
2535
|
color: C.text,
|
|
2429
2536
|
onMouseDown: () => this.config.onCommand?.("videoClear", {})
|
|
2430
2537
|
}
|
|
2431
|
-
)), /* @__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:
|
|
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(
|
|
2432
2539
|
import_react_ecs4.Button,
|
|
2433
2540
|
{
|
|
2434
|
-
uiTransform: { height:
|
|
2541
|
+
uiTransform: { height: this.s(24) },
|
|
2435
2542
|
uiBackground: { color: import_math5.Color4.create(0, 0, 0, 0) },
|
|
2436
|
-
value: "Edit slots at thestatic.tv
|
|
2437
|
-
fontSize:
|
|
2543
|
+
value: "Edit slots at thestatic.tv",
|
|
2544
|
+
fontSize: t.labelSmall,
|
|
2438
2545
|
color: C.cyan,
|
|
2439
2546
|
onMouseDown: () => (0, import_RestrictedActions3.openExternalUrl)({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
|
|
2440
2547
|
}
|
|
2441
2548
|
));
|
|
2442
2549
|
};
|
|
2443
|
-
this.ModTab = () =>
|
|
2444
|
-
|
|
2445
|
-
{
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
this.broadcastText
|
|
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
|
+
}
|
|
2455
2565
|
}
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
import_react_ecs4.Button,
|
|
2459
|
-
{
|
|
2460
|
-
uiTransform: { width: 55, height: 32, margin: { left: 6 } },
|
|
2461
|
-
uiBackground: { color: C.orange },
|
|
2462
|
-
value: "Send",
|
|
2463
|
-
fontSize: 11,
|
|
2464
|
-
color: C.text,
|
|
2465
|
-
onMouseDown: () => this.sendBroadcast()
|
|
2466
|
-
}
|
|
2467
|
-
)), /* @__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: 12 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2468
|
-
import_react_ecs4.Button,
|
|
2469
|
-
{
|
|
2470
|
-
uiTransform: { width: 120, height: 32, margin: 3 },
|
|
2471
|
-
uiBackground: { color: C.red },
|
|
2472
|
-
value: "KICK ALL",
|
|
2473
|
-
fontSize: 11,
|
|
2474
|
-
color: C.text,
|
|
2475
|
-
onMouseDown: () => this.config.onCommand?.("kickAll", {})
|
|
2476
|
-
}
|
|
2477
|
-
)), /* @__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: 4 } } }, this.sceneAdmins.length === 0 && this.modStatus !== "loading" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "No scene admins", fontSize: 10, color: C.textDim }), this.sceneAdmins.map((wallet, i) => /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2478
|
-
import_react_ecs4.UiEntity,
|
|
2479
|
-
{
|
|
2480
|
-
key: `admin-${i}`,
|
|
2481
|
-
uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 3 }, width: "100%" }
|
|
2482
|
-
},
|
|
2483
|
-
/* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2484
|
-
import_react_ecs4.Label,
|
|
2566
|
+
), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2567
|
+
import_react_ecs4.Button,
|
|
2485
2568
|
{
|
|
2486
|
-
|
|
2487
|
-
|
|
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,
|
|
2488
2573
|
color: C.text,
|
|
2489
|
-
|
|
2574
|
+
onMouseDown: () => this.sendBroadcast()
|
|
2490
2575
|
}
|
|
2491
|
-
),
|
|
2492
|
-
/* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
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(
|
|
2493
2577
|
import_react_ecs4.Button,
|
|
2494
2578
|
{
|
|
2495
|
-
uiTransform: { width:
|
|
2496
|
-
uiBackground: { color: C.
|
|
2497
|
-
value: "
|
|
2498
|
-
fontSize:
|
|
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,
|
|
2499
2583
|
color: C.text,
|
|
2500
|
-
onMouseDown: () =>
|
|
2584
|
+
onMouseDown: () => {
|
|
2585
|
+
this.log("KICK ALL clicked");
|
|
2586
|
+
this.config.onCommand?.("kickAll", {});
|
|
2587
|
+
}
|
|
2501
2588
|
}
|
|
2502
|
-
)
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
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
|
+
}
|
|
2515
2628
|
}
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
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
|
+
}
|
|
2527
2640
|
}
|
|
2528
|
-
}
|
|
2529
|
-
|
|
2530
|
-
import_react_ecs4.UiEntity,
|
|
2531
|
-
{
|
|
2532
|
-
key: `ban-${i}`,
|
|
2533
|
-
uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 3 }, width: "100%" }
|
|
2534
|
-
},
|
|
2535
|
-
/* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2536
|
-
import_react_ecs4.Label,
|
|
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,
|
|
2537
2643
|
{
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
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
|
+
}
|
|
2542
2680
|
}
|
|
2543
|
-
),
|
|
2544
|
-
/* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2681
|
+
), /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2545
2682
|
import_react_ecs4.Button,
|
|
2546
2683
|
{
|
|
2547
|
-
uiTransform: { width:
|
|
2548
|
-
uiBackground: { color: C.
|
|
2549
|
-
value: "
|
|
2550
|
-
fontSize:
|
|
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,
|
|
2551
2688
|
color: C.text,
|
|
2552
|
-
onMouseDown: () =>
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
))), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 8 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2556
|
-
import_react_ecs4.Input,
|
|
2557
|
-
{
|
|
2558
|
-
uiTransform: { width: 200, height: 28 },
|
|
2559
|
-
uiBackground: { color: import_math5.Color4.create(0.15, 0.15, 0.2, 1) },
|
|
2560
|
-
placeholder: "0x... ban wallet",
|
|
2561
|
-
placeholderColor: C.textDim,
|
|
2562
|
-
color: C.text,
|
|
2563
|
-
fontSize: 10,
|
|
2564
|
-
value: this.newBanWallet,
|
|
2565
|
-
onChange: (val) => {
|
|
2566
|
-
this.newBanWallet = val;
|
|
2689
|
+
onMouseDown: () => {
|
|
2690
|
+
if (this.newBanWallet) this.banWallet(this.newBanWallet);
|
|
2691
|
+
}
|
|
2567
2692
|
}
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
onMouseDown: () => {
|
|
2578
|
-
if (this.newBanWallet) this.banWallet(this.newBanWallet);
|
|
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",
|
|
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}` })
|
|
2579
2702
|
}
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
import_react_ecs4.Button,
|
|
2583
|
-
{
|
|
2584
|
-
uiTransform: { height: 20 },
|
|
2585
|
-
uiBackground: { color: import_math5.Color4.create(0, 0, 0, 0) },
|
|
2586
|
-
value: "Manage at thestatic.tv \u2192",
|
|
2587
|
-
fontSize: 9,
|
|
2588
|
-
color: C.cyan,
|
|
2589
|
-
onMouseDown: () => (0, import_RestrictedActions3.openExternalUrl)({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
|
|
2590
|
-
}
|
|
2591
|
-
));
|
|
2703
|
+
));
|
|
2704
|
+
};
|
|
2592
2705
|
/**
|
|
2593
2706
|
* Get the React-ECS component for the admin panel
|
|
2594
2707
|
*/
|
|
2595
2708
|
this.getComponent = () => {
|
|
2596
2709
|
if (!this.isAdmin) return null;
|
|
2710
|
+
const t = this.theme;
|
|
2597
2711
|
const tabs = [];
|
|
2598
2712
|
if (this.config.sceneTabs && this.config.sceneTabs.length > 0) {
|
|
2599
2713
|
this.config.sceneTabs.forEach((tab) => tabs.push({ label: tab.label, id: tab.id }));
|
|
@@ -2604,7 +2718,7 @@ var AdminPanelUIModule = class {
|
|
|
2604
2718
|
if (this.config.showModTab !== false && this.isOwner) {
|
|
2605
2719
|
tabs.push({ label: "MOD", id: "mod" });
|
|
2606
2720
|
}
|
|
2607
|
-
if (!tabs.find((
|
|
2721
|
+
if (!tabs.find((tab) => tab.id === this.activeTab) && tabs.length > 0) {
|
|
2608
2722
|
this.activeTab = tabs[0].id;
|
|
2609
2723
|
}
|
|
2610
2724
|
return /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
@@ -2620,17 +2734,17 @@ var AdminPanelUIModule = class {
|
|
|
2620
2734
|
import_react_ecs4.UiEntity,
|
|
2621
2735
|
{
|
|
2622
2736
|
uiTransform: {
|
|
2623
|
-
position: { right:
|
|
2737
|
+
position: { right: 20 + this.s(100) + 10 + this.s(100) + 10, bottom: 10 },
|
|
2624
2738
|
positionType: "absolute"
|
|
2625
2739
|
}
|
|
2626
2740
|
},
|
|
2627
2741
|
/* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2628
2742
|
import_react_ecs4.Button,
|
|
2629
2743
|
{
|
|
2630
|
-
uiTransform: { width:
|
|
2744
|
+
uiTransform: { width: this.s(100), height: this.s(45) },
|
|
2631
2745
|
uiBackground: { color: this.panelOpen ? C.btn : C.header },
|
|
2632
2746
|
value: this.panelOpen ? "CLOSE" : "ADMIN",
|
|
2633
|
-
fontSize:
|
|
2747
|
+
fontSize: this.s(14),
|
|
2634
2748
|
color: C.text,
|
|
2635
2749
|
onMouseDown: () => this.toggle()
|
|
2636
2750
|
}
|
|
@@ -2639,10 +2753,12 @@ var AdminPanelUIModule = class {
|
|
|
2639
2753
|
this.panelOpen && /* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2640
2754
|
import_react_ecs4.UiEntity,
|
|
2641
2755
|
{
|
|
2756
|
+
key: `admin-panel-${this.client.uiScale}`,
|
|
2642
2757
|
uiTransform: {
|
|
2643
|
-
width:
|
|
2644
|
-
|
|
2645
|
-
|
|
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 },
|
|
2646
2762
|
positionType: "absolute",
|
|
2647
2763
|
flexDirection: "column"
|
|
2648
2764
|
},
|
|
@@ -2653,16 +2769,28 @@ var AdminPanelUIModule = class {
|
|
|
2653
2769
|
{
|
|
2654
2770
|
uiTransform: {
|
|
2655
2771
|
width: "100%",
|
|
2656
|
-
height:
|
|
2657
|
-
justifyContent: "
|
|
2772
|
+
height: this.s(UI_DIMENSIONS.admin.headerHeight),
|
|
2773
|
+
justifyContent: "space-between",
|
|
2658
2774
|
alignItems: "center",
|
|
2659
|
-
flexDirection: "row"
|
|
2775
|
+
flexDirection: "row",
|
|
2776
|
+
padding: { left: 12, right: 8 }
|
|
2660
2777
|
},
|
|
2661
2778
|
uiBackground: { color: C.header }
|
|
2662
2779
|
},
|
|
2663
|
-
/* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: this.config.title || "ADMIN PANEL", fontSize:
|
|
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
|
+
)
|
|
2664
2792
|
),
|
|
2665
|
-
/* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { width: "100%", height:
|
|
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 }))),
|
|
2666
2794
|
/* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2667
2795
|
import_react_ecs4.UiEntity,
|
|
2668
2796
|
{
|
|
@@ -2682,7 +2810,7 @@ var AdminPanelUIModule = class {
|
|
|
2682
2810
|
{
|
|
2683
2811
|
uiTransform: {
|
|
2684
2812
|
width: "100%",
|
|
2685
|
-
height:
|
|
2813
|
+
height: this.s(UI_DIMENSIONS.admin.footerHeight),
|
|
2686
2814
|
justifyContent: "center",
|
|
2687
2815
|
alignItems: "center"
|
|
2688
2816
|
},
|
|
@@ -2691,10 +2819,10 @@ var AdminPanelUIModule = class {
|
|
|
2691
2819
|
/* @__PURE__ */ import_react_ecs4.default.createElement(
|
|
2692
2820
|
import_react_ecs4.Button,
|
|
2693
2821
|
{
|
|
2694
|
-
uiTransform: { height:
|
|
2822
|
+
uiTransform: { height: this.s(24) },
|
|
2695
2823
|
uiBackground: { color: import_math5.Color4.create(0, 0, 0, 0) },
|
|
2696
|
-
value: `thestatic.tv/scene/${this.config.sceneId}
|
|
2697
|
-
fontSize:
|
|
2824
|
+
value: `thestatic.tv/scene/${this.config.sceneId}`,
|
|
2825
|
+
fontSize: t.labelSmall,
|
|
2698
2826
|
color: C.cyan,
|
|
2699
2827
|
onMouseDown: () => (0, import_RestrictedActions3.openExternalUrl)({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
|
|
2700
2828
|
}
|
|
@@ -2704,14 +2832,19 @@ var AdminPanelUIModule = class {
|
|
|
2704
2832
|
);
|
|
2705
2833
|
};
|
|
2706
2834
|
this.client = client;
|
|
2835
|
+
if (!config.sceneId) {
|
|
2836
|
+
throw new Error("[AdminPanel] sceneId is required");
|
|
2837
|
+
}
|
|
2707
2838
|
this.config = {
|
|
2708
2839
|
showVideoTab: true,
|
|
2709
2840
|
showModTab: true,
|
|
2710
2841
|
title: "ADMIN PANEL",
|
|
2711
2842
|
debug: false,
|
|
2712
|
-
...config
|
|
2843
|
+
...config,
|
|
2844
|
+
sceneId: config.sceneId
|
|
2845
|
+
// Ensure sceneId is set
|
|
2713
2846
|
};
|
|
2714
|
-
this.baseUrl = client.
|
|
2847
|
+
this.baseUrl = client.getBaseUrl();
|
|
2715
2848
|
if (config.headerColor) {
|
|
2716
2849
|
C.header = import_math5.Color4.create(
|
|
2717
2850
|
config.headerColor.r,
|
|
@@ -2726,12 +2859,21 @@ var AdminPanelUIModule = class {
|
|
|
2726
2859
|
console.log(`[AdminPanel] ${msg}`, ...args);
|
|
2727
2860
|
}
|
|
2728
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
|
+
}
|
|
2729
2870
|
/**
|
|
2730
|
-
* Initialize the admin panel - checks admin status
|
|
2871
|
+
* Initialize the admin panel - checks admin status and fetches video state
|
|
2731
2872
|
*/
|
|
2732
2873
|
async init() {
|
|
2733
|
-
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
2734
2874
|
await this.checkAdminStatus();
|
|
2875
|
+
await this.fetchVideoState();
|
|
2876
|
+
this.autoPlayDefault();
|
|
2735
2877
|
this.log("Initialized");
|
|
2736
2878
|
}
|
|
2737
2879
|
/**
|
|
@@ -2744,17 +2886,23 @@ var AdminPanelUIModule = class {
|
|
|
2744
2886
|
return;
|
|
2745
2887
|
}
|
|
2746
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
|
+
}
|
|
2747
2895
|
try {
|
|
2748
2896
|
const res = await fetch(
|
|
2749
2897
|
`${this.baseUrl}/scene/${this.config.sceneId}/admin-check?wallet=${player.userId}`
|
|
2750
2898
|
);
|
|
2751
2899
|
if (res.ok) {
|
|
2752
2900
|
const data = await res.json();
|
|
2753
|
-
this.isAdmin = data.hasAccess;
|
|
2754
|
-
this.isOwner = data.isOwner || data.
|
|
2901
|
+
this.isAdmin = data.showButton ?? data.hasAccess;
|
|
2902
|
+
this.isOwner = data.isOwner || data.isSceneAdmin;
|
|
2755
2903
|
if (data.isBanned) {
|
|
2756
2904
|
this.config.onBroadcast?.("You have been banned from this scene.");
|
|
2757
|
-
|
|
2905
|
+
this.banKickPlayer();
|
|
2758
2906
|
this.log("Player is banned - kicking");
|
|
2759
2907
|
}
|
|
2760
2908
|
this.log("Admin status:", this.isAdmin, "Owner:", this.isOwner);
|
|
@@ -2767,13 +2915,31 @@ var AdminPanelUIModule = class {
|
|
|
2767
2915
|
* Toggle the admin panel open/closed
|
|
2768
2916
|
*/
|
|
2769
2917
|
toggle() {
|
|
2770
|
-
this.panelOpen
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
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") {
|
|
2774
2932
|
this.startStreamPolling();
|
|
2775
2933
|
}
|
|
2776
2934
|
}
|
|
2935
|
+
/**
|
|
2936
|
+
* Hide the admin panel
|
|
2937
|
+
*/
|
|
2938
|
+
hide() {
|
|
2939
|
+
if (!this.panelOpen) return;
|
|
2940
|
+
this.panelOpen = false;
|
|
2941
|
+
this.stopStreamPolling();
|
|
2942
|
+
}
|
|
2777
2943
|
/**
|
|
2778
2944
|
* Check if the panel is currently open
|
|
2779
2945
|
*/
|
|
@@ -2787,8 +2953,8 @@ var AdminPanelUIModule = class {
|
|
|
2787
2953
|
return this.isAdmin;
|
|
2788
2954
|
}
|
|
2789
2955
|
/**
|
|
2790
|
-
|
|
2791
|
-
|
|
2956
|
+
* Register a custom scene tab (Pro tier)
|
|
2957
|
+
*/
|
|
2792
2958
|
registerSceneTab(tab) {
|
|
2793
2959
|
if (!this.config.sceneTabs) {
|
|
2794
2960
|
this.config.sceneTabs = [];
|
|
@@ -2799,7 +2965,7 @@ var AdminPanelUIModule = class {
|
|
|
2799
2965
|
// --- Stream Polling ---
|
|
2800
2966
|
startStreamPolling() {
|
|
2801
2967
|
if (this.pollIntervalId !== null) return;
|
|
2802
|
-
this.pollIntervalId =
|
|
2968
|
+
this.pollIntervalId = dclSetInterval(() => {
|
|
2803
2969
|
if (this.activeTab === "video" && this.panelOpen && this.streamData?.hasChannel) {
|
|
2804
2970
|
this.refreshStreamStatus();
|
|
2805
2971
|
}
|
|
@@ -2808,7 +2974,7 @@ var AdminPanelUIModule = class {
|
|
|
2808
2974
|
}
|
|
2809
2975
|
stopStreamPolling() {
|
|
2810
2976
|
if (this.pollIntervalId !== null) {
|
|
2811
|
-
|
|
2977
|
+
dclClearInterval(this.pollIntervalId);
|
|
2812
2978
|
this.pollIntervalId = null;
|
|
2813
2979
|
this.log("Stream polling stopped");
|
|
2814
2980
|
}
|
|
@@ -2841,6 +3007,51 @@ var AdminPanelUIModule = class {
|
|
|
2841
3007
|
} catch (err) {
|
|
2842
3008
|
}
|
|
2843
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
|
+
}
|
|
2844
3055
|
async createChannel() {
|
|
2845
3056
|
if (!this.playerWallet || this.channelCreating) return;
|
|
2846
3057
|
this.channelCreating = true;
|
|
@@ -2957,9 +3168,6 @@ var AdminPanelUIModule = class {
|
|
|
2957
3168
|
this.config.onBroadcast?.("Stream started!");
|
|
2958
3169
|
this.streamFetched = false;
|
|
2959
3170
|
await this.fetchStreamData();
|
|
2960
|
-
setTimeout(() => {
|
|
2961
|
-
this.streamControlStatus = "";
|
|
2962
|
-
}, 3e3);
|
|
2963
3171
|
} else {
|
|
2964
3172
|
this.streamControlStatus = "error";
|
|
2965
3173
|
this.log("Start stream failed:", data.error);
|
|
@@ -2992,9 +3200,6 @@ var AdminPanelUIModule = class {
|
|
|
2992
3200
|
this.config.onBroadcast?.("Stream stopped");
|
|
2993
3201
|
this.streamFetched = false;
|
|
2994
3202
|
await this.fetchStreamData();
|
|
2995
|
-
setTimeout(() => {
|
|
2996
|
-
this.streamControlStatus = "";
|
|
2997
|
-
}, 3e3);
|
|
2998
3203
|
} else {
|
|
2999
3204
|
this.streamControlStatus = "error";
|
|
3000
3205
|
this.log("Stop stream failed:", data.error);
|
|
@@ -3024,9 +3229,6 @@ var AdminPanelUIModule = class {
|
|
|
3024
3229
|
this.keyRotateStatus = "success";
|
|
3025
3230
|
this.log("Key rotated:", data.message);
|
|
3026
3231
|
this.config.onBroadcast?.("Stream key rotated - update OBS settings");
|
|
3027
|
-
setTimeout(() => {
|
|
3028
|
-
this.keyRotateStatus = "";
|
|
3029
|
-
}, 3e3);
|
|
3030
3232
|
} else {
|
|
3031
3233
|
this.keyRotateStatus = "error";
|
|
3032
3234
|
this.log("Key rotation failed:", data.error);
|
|
@@ -3086,9 +3288,6 @@ var AdminPanelUIModule = class {
|
|
|
3086
3288
|
this.sceneAdmins = [...this.sceneAdmins, normalized];
|
|
3087
3289
|
this.newAdminWallet = "";
|
|
3088
3290
|
this.modStatus = "saved";
|
|
3089
|
-
setTimeout(() => {
|
|
3090
|
-
this.modStatus = "";
|
|
3091
|
-
}, 2e3);
|
|
3092
3291
|
this.log("Added scene admin:", normalized);
|
|
3093
3292
|
} else {
|
|
3094
3293
|
this.modStatus = "error";
|
|
@@ -3116,9 +3315,6 @@ var AdminPanelUIModule = class {
|
|
|
3116
3315
|
if (res.ok) {
|
|
3117
3316
|
this.sceneAdmins = this.sceneAdmins.filter((w) => w !== wallet);
|
|
3118
3317
|
this.modStatus = "saved";
|
|
3119
|
-
setTimeout(() => {
|
|
3120
|
-
this.modStatus = "";
|
|
3121
|
-
}, 2e3);
|
|
3122
3318
|
this.log("Removed scene admin:", wallet);
|
|
3123
3319
|
} else {
|
|
3124
3320
|
this.modStatus = "error";
|
|
@@ -3153,9 +3349,6 @@ var AdminPanelUIModule = class {
|
|
|
3153
3349
|
this.bannedWallets = [...this.bannedWallets, normalized];
|
|
3154
3350
|
this.newBanWallet = "";
|
|
3155
3351
|
this.modStatus = "saved";
|
|
3156
|
-
setTimeout(() => {
|
|
3157
|
-
this.modStatus = "";
|
|
3158
|
-
}, 2e3);
|
|
3159
3352
|
this.log("Banned wallet:", normalized);
|
|
3160
3353
|
this.config.onCommand?.("kickBanned", { wallet: normalized });
|
|
3161
3354
|
} else {
|
|
@@ -3184,9 +3377,6 @@ var AdminPanelUIModule = class {
|
|
|
3184
3377
|
if (res.ok) {
|
|
3185
3378
|
this.bannedWallets = this.bannedWallets.filter((w) => w !== wallet);
|
|
3186
3379
|
this.modStatus = "saved";
|
|
3187
|
-
setTimeout(() => {
|
|
3188
|
-
this.modStatus = "";
|
|
3189
|
-
}, 2e3);
|
|
3190
3380
|
this.log("Unbanned wallet:", wallet);
|
|
3191
3381
|
} else {
|
|
3192
3382
|
this.modStatus = "error";
|
|
@@ -3220,7 +3410,89 @@ var AdminPanelUIModule = class {
|
|
|
3220
3410
|
}
|
|
3221
3411
|
};
|
|
3222
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
|
+
|
|
3223
3494
|
// src/StaticTVClient.ts
|
|
3495
|
+
var import_ecs3 = require("@dcl/sdk/ecs");
|
|
3224
3496
|
var DEFAULT_BASE_URL = "https://thestatic.tv/api/v1/dcl";
|
|
3225
3497
|
var KEY_TYPE_CHANNEL = "channel";
|
|
3226
3498
|
var KEY_TYPE_SCENE = "scene";
|
|
@@ -3230,38 +3502,63 @@ var StaticTVClient = class {
|
|
|
3230
3502
|
*
|
|
3231
3503
|
* @param config Configuration options
|
|
3232
3504
|
*
|
|
3233
|
-
* @example
|
|
3505
|
+
* @example Free tier (session tracking only):
|
|
3234
3506
|
* ```typescript
|
|
3235
|
-
*
|
|
3507
|
+
* const staticTV = new StaticTVClient({
|
|
3508
|
+
* apiKey: 'dcls_your_key_here'
|
|
3509
|
+
* })
|
|
3510
|
+
* ```
|
|
3511
|
+
*
|
|
3512
|
+
* @example Standard/Pro with video screen (SDK handles playback):
|
|
3513
|
+
* ```typescript
|
|
3514
|
+
* // Create your video screen
|
|
3515
|
+
* const screen = engine.addEntity()
|
|
3516
|
+
* Transform.create(screen, { position: Vector3.create(8, 3, 14), scale: Vector3.create(8, 4.5, 0.1) })
|
|
3517
|
+
* MeshRenderer.setPlane(screen)
|
|
3518
|
+
*
|
|
3519
|
+
* // SDK handles everything - Guide selection + Pro admin controls just work
|
|
3520
|
+
* const staticTV = new StaticTVClient({
|
|
3521
|
+
* apiKey: 'dcls_your_key_here',
|
|
3522
|
+
* videoScreen: screen // That's it! SDK manages video playback
|
|
3523
|
+
* })
|
|
3236
3524
|
*
|
|
3237
3525
|
* export function main() {
|
|
3238
|
-
*
|
|
3239
|
-
* staticTV = new StaticTVClient({
|
|
3240
|
-
* apiKey: 'dcls_your_key_here'
|
|
3241
|
-
* })
|
|
3242
|
-
* // Session tracking starts automatically!
|
|
3526
|
+
* staticTV.setupUI()
|
|
3243
3527
|
* }
|
|
3244
3528
|
* ```
|
|
3245
3529
|
*/
|
|
3246
3530
|
constructor(config) {
|
|
3247
3531
|
this._keyType = null;
|
|
3532
|
+
this._keyId = null;
|
|
3248
3533
|
this._disabled = false;
|
|
3249
|
-
this.
|
|
3534
|
+
this._tier = "free";
|
|
3535
|
+
this._standardFeaturesEnabled = false;
|
|
3250
3536
|
this._proFeaturesEnabled = false;
|
|
3251
|
-
|
|
3537
|
+
this._pendingProConfig = null;
|
|
3538
|
+
this._featuresResolved = false;
|
|
3539
|
+
/** Guide module - fetch channel lineup (standard/pro tier) */
|
|
3252
3540
|
this.guide = null;
|
|
3253
|
-
/** Session module - track visitor sessions (all
|
|
3541
|
+
/** Session module - track visitor sessions (all tiers, null when disabled) */
|
|
3254
3542
|
this.session = null;
|
|
3255
|
-
/** Heartbeat module - track video watching (
|
|
3543
|
+
/** Heartbeat module - track video watching (standard/pro tier) */
|
|
3256
3544
|
this.heartbeat = null;
|
|
3257
|
-
/** Interactions module - like/follow channels (
|
|
3545
|
+
/** Interactions module - like/follow channels (standard/pro tier) */
|
|
3258
3546
|
this.interactions = null;
|
|
3259
|
-
/** Guide UI module - channel browser UI (
|
|
3547
|
+
/** Guide UI module - channel browser UI (standard/pro tier) */
|
|
3260
3548
|
this.guideUI = null;
|
|
3261
|
-
/** Chat UI module - real-time chat UI (
|
|
3549
|
+
/** Chat UI module - real-time chat UI (standard/pro tier) */
|
|
3262
3550
|
this.chatUI = null;
|
|
3263
|
-
/** Admin Panel module - Video/Mod tabs (pro
|
|
3551
|
+
/** Admin Panel module - Video/Mod tabs (pro tier only) */
|
|
3264
3552
|
this.adminPanel = null;
|
|
3553
|
+
/** UI scale - fixed at 1.0. DCL's UI system auto-scales based on viewport. */
|
|
3554
|
+
this.uiScale = 1;
|
|
3555
|
+
// =============================================================================
|
|
3556
|
+
// --- VIDEO PLAYBACK (Internal handler for videoScreen) ---
|
|
3557
|
+
// =============================================================================
|
|
3558
|
+
this._currentVideoUrl = "";
|
|
3559
|
+
this._featuresReadyPromise = new Promise((resolve) => {
|
|
3560
|
+
this._featuresReadyResolve = resolve;
|
|
3561
|
+
});
|
|
3265
3562
|
this.config = {
|
|
3266
3563
|
autoStartSession: true,
|
|
3267
3564
|
sessionHeartbeatInterval: 3e4,
|
|
@@ -3271,7 +3568,6 @@ var StaticTVClient = class {
|
|
|
3271
3568
|
};
|
|
3272
3569
|
this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
|
|
3273
3570
|
if (!config.apiKey) {
|
|
3274
|
-
console.log("[StaticTV] No apiKey provided - tracking disabled. Scene will load normally.");
|
|
3275
3571
|
this._disabled = true;
|
|
3276
3572
|
this._keyType = null;
|
|
3277
3573
|
this.session = null;
|
|
@@ -3280,6 +3576,7 @@ var StaticTVClient = class {
|
|
|
3280
3576
|
this.interactions = null;
|
|
3281
3577
|
this.guideUI = null;
|
|
3282
3578
|
this.chatUI = null;
|
|
3579
|
+
this._resolveFeaturesReady("free");
|
|
3283
3580
|
return;
|
|
3284
3581
|
}
|
|
3285
3582
|
if (config.apiKey.startsWith("dclk_")) {
|
|
@@ -3287,14 +3584,17 @@ var StaticTVClient = class {
|
|
|
3287
3584
|
} else if (config.apiKey.startsWith("dcls_")) {
|
|
3288
3585
|
this._keyType = KEY_TYPE_SCENE;
|
|
3289
3586
|
} else {
|
|
3290
|
-
console.
|
|
3587
|
+
console.warn("[TheStatic] Invalid API key format - get your key at thestatic.tv/dashboard");
|
|
3291
3588
|
this._disabled = true;
|
|
3292
3589
|
this._keyType = null;
|
|
3590
|
+
this._resolveFeaturesReady("free");
|
|
3293
3591
|
return;
|
|
3294
3592
|
}
|
|
3295
3593
|
this.session = new SessionModule(this);
|
|
3296
3594
|
if (this._keyType === KEY_TYPE_CHANNEL) {
|
|
3297
|
-
this.
|
|
3595
|
+
this._tier = "standard";
|
|
3596
|
+
this._initStandardModules();
|
|
3597
|
+
this._resolveFeaturesReady("standard");
|
|
3298
3598
|
}
|
|
3299
3599
|
if (this.config.autoStartSession) {
|
|
3300
3600
|
fetchUserData().then(() => {
|
|
@@ -3310,6 +3610,10 @@ var StaticTVClient = class {
|
|
|
3310
3610
|
}
|
|
3311
3611
|
this.log(`StaticTVClient initialized (${this._keyType} mode)`);
|
|
3312
3612
|
}
|
|
3613
|
+
/** Get the API base URL (for internal module use) */
|
|
3614
|
+
getBaseUrl() {
|
|
3615
|
+
return this.baseUrl;
|
|
3616
|
+
}
|
|
3313
3617
|
/**
|
|
3314
3618
|
* Get the key type (channel, scene, or null if disabled)
|
|
3315
3619
|
*/
|
|
@@ -3323,11 +3627,52 @@ var StaticTVClient = class {
|
|
|
3323
3627
|
return this._disabled;
|
|
3324
3628
|
}
|
|
3325
3629
|
/**
|
|
3326
|
-
*
|
|
3327
|
-
|
|
3630
|
+
* Get the current SDK tier (free, standard, or pro)
|
|
3631
|
+
*/
|
|
3632
|
+
get tier() {
|
|
3633
|
+
return this._tier;
|
|
3634
|
+
}
|
|
3635
|
+
/**
|
|
3636
|
+
* Check if this is a free tier client (session tracking only)
|
|
3637
|
+
* Returns true until session confirms a higher tier
|
|
3638
|
+
*/
|
|
3639
|
+
get isFree() {
|
|
3640
|
+
return this._tier === "free";
|
|
3641
|
+
}
|
|
3642
|
+
/**
|
|
3643
|
+
* @deprecated Use `isFree` instead. Kept for backward compatibility.
|
|
3328
3644
|
*/
|
|
3329
3645
|
get isLite() {
|
|
3330
|
-
return
|
|
3646
|
+
return this.isFree;
|
|
3647
|
+
}
|
|
3648
|
+
/**
|
|
3649
|
+
* Wait for tier confirmation from the server.
|
|
3650
|
+
* Resolves when the SDK knows which features are available.
|
|
3651
|
+
*
|
|
3652
|
+
* Use this instead of polling `isLite` or `isFree` in a while loop.
|
|
3653
|
+
*
|
|
3654
|
+
* @returns Promise that resolves with the confirmed tier ('free', 'standard', or 'pro')
|
|
3655
|
+
*
|
|
3656
|
+
* @example
|
|
3657
|
+
* ```typescript
|
|
3658
|
+
* const staticTV = new StaticTVClient({ apiKey: 'dcls_...' })
|
|
3659
|
+
*
|
|
3660
|
+
* // Wait for tier to be confirmed
|
|
3661
|
+
* const tier = await staticTV.onFeaturesReady()
|
|
3662
|
+
*
|
|
3663
|
+
* // Now modules are guaranteed to be initialized (or confirmed unavailable)
|
|
3664
|
+
* if (staticTV.guideUI) {
|
|
3665
|
+
* await staticTV.guideUI.init()
|
|
3666
|
+
* console.log('Guide ready!')
|
|
3667
|
+
* }
|
|
3668
|
+
*
|
|
3669
|
+
* if (tier === 'free') {
|
|
3670
|
+
* console.log('Upgrade at thestatic.tv for Guide & Chat!')
|
|
3671
|
+
* }
|
|
3672
|
+
* ```
|
|
3673
|
+
*/
|
|
3674
|
+
async onFeaturesReady() {
|
|
3675
|
+
return this._featuresReadyPromise;
|
|
3331
3676
|
}
|
|
3332
3677
|
/**
|
|
3333
3678
|
* Make an authenticated API request
|
|
@@ -3345,14 +3690,29 @@ var StaticTVClient = class {
|
|
|
3345
3690
|
});
|
|
3346
3691
|
}
|
|
3347
3692
|
/**
|
|
3348
|
-
* Log a message
|
|
3693
|
+
* Log a debug message (only when debug: true)
|
|
3349
3694
|
* @internal
|
|
3350
3695
|
*/
|
|
3351
3696
|
log(message, ...args) {
|
|
3352
3697
|
if (this.config.debug) {
|
|
3353
|
-
console.log(`[
|
|
3698
|
+
console.log(`[TheStatic] ${message}`, ...args);
|
|
3354
3699
|
}
|
|
3355
3700
|
}
|
|
3701
|
+
/**
|
|
3702
|
+
* Log a warning (always shown)
|
|
3703
|
+
* @internal
|
|
3704
|
+
*/
|
|
3705
|
+
warn(message) {
|
|
3706
|
+
console.warn(`[TheStatic] ${message}`);
|
|
3707
|
+
}
|
|
3708
|
+
/**
|
|
3709
|
+
* Log an error (always shown, user-friendly format)
|
|
3710
|
+
* @internal
|
|
3711
|
+
*/
|
|
3712
|
+
error(message, err) {
|
|
3713
|
+
const errorDetail = err instanceof Error ? err.message : String(err || "");
|
|
3714
|
+
console.error(`[TheStatic] ${message}${errorDetail ? `: ${errorDetail}` : ""}`);
|
|
3715
|
+
}
|
|
3356
3716
|
/**
|
|
3357
3717
|
* Get the current configuration
|
|
3358
3718
|
* @internal
|
|
@@ -3361,35 +3721,190 @@ var StaticTVClient = class {
|
|
|
3361
3721
|
return this.config;
|
|
3362
3722
|
}
|
|
3363
3723
|
/**
|
|
3364
|
-
*
|
|
3724
|
+
* Play a video on the configured videoScreen entity.
|
|
3725
|
+
* Called by Guide UI and Admin Panel.
|
|
3726
|
+
*
|
|
3727
|
+
* @param url Video URL to play
|
|
3728
|
+
*/
|
|
3729
|
+
playVideo(url) {
|
|
3730
|
+
const screen = this.config.videoScreen;
|
|
3731
|
+
if (screen !== void 0) {
|
|
3732
|
+
this.log(`Playing video: ${url}`);
|
|
3733
|
+
this._currentVideoUrl = url;
|
|
3734
|
+
if (import_ecs3.VideoPlayer.has(screen)) {
|
|
3735
|
+
import_ecs3.VideoPlayer.deleteFrom(screen);
|
|
3736
|
+
}
|
|
3737
|
+
import_ecs3.VideoPlayer.create(screen, {
|
|
3738
|
+
src: url,
|
|
3739
|
+
playing: true,
|
|
3740
|
+
volume: 1
|
|
3741
|
+
});
|
|
3742
|
+
import_ecs3.Material.setBasicMaterial(screen, {
|
|
3743
|
+
texture: import_ecs3.Material.Texture.Video({ videoPlayerEntity: screen })
|
|
3744
|
+
});
|
|
3745
|
+
if (this.guideUI) {
|
|
3746
|
+
const videos = this.guideUI.getVideos();
|
|
3747
|
+
const video = videos.find((v) => v.src === url);
|
|
3748
|
+
if (video) {
|
|
3749
|
+
this.guideUI.currentVideoId = video.id;
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
if (this.config.onVideoPlay) {
|
|
3754
|
+
this.config.onVideoPlay(url);
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
/**
|
|
3758
|
+
* Stop video playback on the configured videoScreen entity.
|
|
3759
|
+
* Called by Admin Panel.
|
|
3760
|
+
*/
|
|
3761
|
+
stopVideo() {
|
|
3762
|
+
const screen = this.config.videoScreen;
|
|
3763
|
+
if (screen !== void 0 && import_ecs3.VideoPlayer.has(screen)) {
|
|
3764
|
+
this.log("Stopping video");
|
|
3765
|
+
import_ecs3.VideoPlayer.getMutable(screen).playing = false;
|
|
3766
|
+
this._currentVideoUrl = "";
|
|
3767
|
+
if (this.guideUI) {
|
|
3768
|
+
this.guideUI.currentVideoId = null;
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
if (this.config.onVideoStop) {
|
|
3772
|
+
this.config.onVideoStop();
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
/**
|
|
3776
|
+
* Get the currently playing video URL
|
|
3777
|
+
*/
|
|
3778
|
+
get currentVideoUrl() {
|
|
3779
|
+
return this._currentVideoUrl;
|
|
3780
|
+
}
|
|
3781
|
+
/**
|
|
3782
|
+
* Internal handler for Guide video selection
|
|
3783
|
+
* @internal
|
|
3784
|
+
*/
|
|
3785
|
+
_handleGuideVideoSelect(video) {
|
|
3786
|
+
if (video.src) {
|
|
3787
|
+
this.playVideo(video.src);
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
/**
|
|
3791
|
+
* Internal handler for broadcast messages
|
|
3792
|
+
* @internal
|
|
3793
|
+
*/
|
|
3794
|
+
_handleBroadcast(text) {
|
|
3795
|
+
if (this.config.onBroadcast) {
|
|
3796
|
+
this.config.onBroadcast(text);
|
|
3797
|
+
} else {
|
|
3798
|
+
this.showNotification(text);
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3801
|
+
/**
|
|
3802
|
+
* Initialize standard feature modules (guide, heartbeat, interactions, UI)
|
|
3365
3803
|
* @internal
|
|
3366
3804
|
*/
|
|
3367
|
-
|
|
3368
|
-
if (this.
|
|
3805
|
+
_initStandardModules() {
|
|
3806
|
+
if (this._standardFeaturesEnabled) return;
|
|
3369
3807
|
this.guide = new GuideModule(this);
|
|
3370
3808
|
this.heartbeat = new HeartbeatModule(this);
|
|
3371
3809
|
this.interactions = new InteractionsModule(this);
|
|
3372
|
-
|
|
3810
|
+
const guideConfig = {
|
|
3811
|
+
...this.config.guideUI,
|
|
3812
|
+
onVideoSelect: (video) => {
|
|
3813
|
+
if (this.config.videoScreen !== void 0 || this.config.onVideoPlay) {
|
|
3814
|
+
this._handleGuideVideoSelect(video);
|
|
3815
|
+
}
|
|
3816
|
+
if (this.config.guideUI?.onVideoSelect) {
|
|
3817
|
+
this.config.guideUI.onVideoSelect(video);
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
};
|
|
3821
|
+
this.guideUI = new GuideUIModule(this, guideConfig);
|
|
3373
3822
|
this.chatUI = new ChatUIModule(this, this.config.chatUI);
|
|
3374
|
-
this.
|
|
3823
|
+
this._standardFeaturesEnabled = true;
|
|
3375
3824
|
this.chatUI.init().catch((err) => {
|
|
3376
3825
|
this.log(`Chat init failed: ${err}`);
|
|
3377
3826
|
});
|
|
3378
|
-
this.log("
|
|
3827
|
+
this.log("Standard features enabled (guide, chat, heartbeat, interactions)");
|
|
3828
|
+
}
|
|
3829
|
+
/**
|
|
3830
|
+
* Initialize pro feature modules (admin panel)
|
|
3831
|
+
* @internal
|
|
3832
|
+
*/
|
|
3833
|
+
_initProModules() {
|
|
3834
|
+
if (this._proFeaturesEnabled) return;
|
|
3835
|
+
const sceneId = this._pendingProConfig?.sceneId || this.config.sceneId || this._keyId || void 0;
|
|
3836
|
+
if (!sceneId) {
|
|
3837
|
+
this.log("Pro features: No sceneId and no keyId available - admin panel disabled");
|
|
3838
|
+
return;
|
|
3839
|
+
}
|
|
3840
|
+
const adminConfig = {
|
|
3841
|
+
sceneId,
|
|
3842
|
+
// Use internal handlers that manage videoScreen + call user callbacks
|
|
3843
|
+
onVideoPlay: (url) => this.playVideo(url),
|
|
3844
|
+
onVideoStop: () => this.stopVideo(),
|
|
3845
|
+
onBroadcast: (text) => this._handleBroadcast(text),
|
|
3846
|
+
onCommand: this.config.onCommand || this._pendingProConfig?.onCommand,
|
|
3847
|
+
// Merge other settings from enableProFeatures if provided
|
|
3848
|
+
...this._pendingProConfig
|
|
3849
|
+
};
|
|
3850
|
+
this.adminPanel = new AdminPanelUIModule(this, adminConfig);
|
|
3851
|
+
this._proFeaturesEnabled = true;
|
|
3852
|
+
this.adminPanel.init().catch((err) => {
|
|
3853
|
+
this.log(`Admin panel init failed: ${err}`);
|
|
3854
|
+
});
|
|
3855
|
+
this.log(`Pro features enabled (admin panel) - sceneId: ${sceneId}`);
|
|
3856
|
+
}
|
|
3857
|
+
/**
|
|
3858
|
+
* Resolve the features ready promise (only once)
|
|
3859
|
+
* @internal
|
|
3860
|
+
*/
|
|
3861
|
+
_resolveFeaturesReady(tier) {
|
|
3862
|
+
if (!this._featuresResolved) {
|
|
3863
|
+
this._featuresResolved = true;
|
|
3864
|
+
this._featuresReadyResolve(tier);
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
/**
|
|
3868
|
+
* Called by SessionModule when server returns the tier
|
|
3869
|
+
* Enables modules based on tier level
|
|
3870
|
+
* @internal
|
|
3871
|
+
*/
|
|
3872
|
+
_enableFeaturesForTier(tier, keyId) {
|
|
3873
|
+
this._tier = tier;
|
|
3874
|
+
if (keyId) {
|
|
3875
|
+
this._keyId = keyId;
|
|
3876
|
+
}
|
|
3877
|
+
if (tier === "standard" || tier === "pro") {
|
|
3878
|
+
this._initStandardModules();
|
|
3879
|
+
}
|
|
3880
|
+
if (tier === "pro") {
|
|
3881
|
+
const hasVideoConfig = this.config.videoScreen !== void 0 || this.config.onVideoPlay !== void 0 || this.config.sceneId !== void 0 || this._pendingProConfig !== null;
|
|
3882
|
+
if (hasVideoConfig) {
|
|
3883
|
+
this._initProModules();
|
|
3884
|
+
} else {
|
|
3885
|
+
this.log("Pro tier detected but no video config - call enableProFeatures() or set videoScreen to enable admin panel");
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
this._resolveFeaturesReady(tier);
|
|
3379
3889
|
}
|
|
3380
3890
|
/**
|
|
3381
|
-
*
|
|
3382
|
-
* Enables guide, chat, heartbeat, and interactions modules
|
|
3891
|
+
* @deprecated Use `_enableFeaturesForTier` instead
|
|
3383
3892
|
* @internal
|
|
3384
3893
|
*/
|
|
3385
3894
|
_enableFullFeatures() {
|
|
3386
|
-
this.
|
|
3895
|
+
this._enableFeaturesForTier("standard");
|
|
3896
|
+
}
|
|
3897
|
+
/**
|
|
3898
|
+
* Check if standard features are enabled (guide, chat, etc.)
|
|
3899
|
+
*/
|
|
3900
|
+
get hasStandardFeatures() {
|
|
3901
|
+
return this._standardFeaturesEnabled;
|
|
3387
3902
|
}
|
|
3388
3903
|
/**
|
|
3389
|
-
*
|
|
3904
|
+
* @deprecated Use `hasStandardFeatures` instead
|
|
3390
3905
|
*/
|
|
3391
3906
|
get hasFullFeatures() {
|
|
3392
|
-
return this.
|
|
3907
|
+
return this._standardFeaturesEnabled;
|
|
3393
3908
|
}
|
|
3394
3909
|
/**
|
|
3395
3910
|
* Check if pro features are enabled (admin panel)
|
|
@@ -3398,64 +3913,121 @@ var StaticTVClient = class {
|
|
|
3398
3913
|
return this._proFeaturesEnabled;
|
|
3399
3914
|
}
|
|
3400
3915
|
/**
|
|
3401
|
-
*
|
|
3916
|
+
* @deprecated Use `tier` instead
|
|
3402
3917
|
*/
|
|
3403
3918
|
get sdkType() {
|
|
3404
|
-
return this.
|
|
3919
|
+
return this._tier === "free" ? "lite" : "full";
|
|
3405
3920
|
}
|
|
3406
3921
|
/**
|
|
3407
|
-
*
|
|
3408
|
-
*
|
|
3922
|
+
* Configure Pro features (Admin Panel with Video + Mod tabs)
|
|
3923
|
+
*
|
|
3924
|
+
* **OPTIONAL**: If you set `videoScreen` in the constructor, you don't need
|
|
3925
|
+
* to call this method - the Admin Panel will auto-enable with sensible defaults.
|
|
3926
|
+
*
|
|
3927
|
+
* Use this method only if you need:
|
|
3928
|
+
* - Custom scene tab UI
|
|
3929
|
+
* - Custom panel title/styling
|
|
3930
|
+
* - Advanced command handlers
|
|
3409
3931
|
*
|
|
3410
3932
|
* @param config Admin panel configuration
|
|
3411
3933
|
*
|
|
3412
|
-
* @example
|
|
3934
|
+
* @example Using videoScreen (recommended - no enableProFeatures needed):
|
|
3413
3935
|
* ```typescript
|
|
3414
|
-
* const staticTV = new StaticTVClient({
|
|
3936
|
+
* const staticTV = new StaticTVClient({
|
|
3937
|
+
* apiKey: 'dcls_...',
|
|
3938
|
+
* videoScreen: myScreen // Admin Panel auto-enabled for Pro tier!
|
|
3939
|
+
* })
|
|
3940
|
+
* ```
|
|
3415
3941
|
*
|
|
3416
|
-
*
|
|
3942
|
+
* @example Advanced: Custom scene tabs
|
|
3943
|
+
* ```typescript
|
|
3417
3944
|
* staticTV.enableProFeatures({
|
|
3418
|
-
* sceneId: 'my-scene',
|
|
3419
3945
|
* title: 'MY SCENE ADMIN',
|
|
3420
|
-
*
|
|
3421
|
-
* onVideoStop: () => videoPlayer.stop(),
|
|
3422
|
-
* onBroadcast: (text) => showNotification(text)
|
|
3946
|
+
* sceneTabs: [{ label: 'LIGHTS', id: 'lights', render: () => <LightsTab /> }]
|
|
3423
3947
|
* })
|
|
3424
3948
|
* ```
|
|
3425
3949
|
*/
|
|
3426
|
-
enableProFeatures(config) {
|
|
3950
|
+
enableProFeatures(config = {}) {
|
|
3427
3951
|
if (this._proFeaturesEnabled) {
|
|
3428
3952
|
this.log("Pro features already enabled");
|
|
3429
3953
|
return;
|
|
3430
3954
|
}
|
|
3431
|
-
this.
|
|
3432
|
-
this.
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3955
|
+
this._pendingProConfig = config;
|
|
3956
|
+
if (this._tier === "pro") {
|
|
3957
|
+
this._initProModules();
|
|
3958
|
+
} else {
|
|
3959
|
+
this.log("Pro features configured - will enable when Pro tier is confirmed");
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3962
|
+
/**
|
|
3963
|
+
* Register a custom scene tab for the admin panel (Pro tier)
|
|
3964
|
+
* Must call enableProFeatures() first.
|
|
3965
|
+
*
|
|
3966
|
+
* @param tab The tab definition with label, id, and render function
|
|
3967
|
+
*
|
|
3968
|
+
* @example
|
|
3969
|
+
* ```typescript
|
|
3970
|
+
* staticTV.registerSceneTab({
|
|
3971
|
+
* label: 'LIGHTS',
|
|
3972
|
+
* id: 'lights',
|
|
3973
|
+
* render: () => <MyLightsControls />
|
|
3974
|
+
* })
|
|
3975
|
+
* ```
|
|
3976
|
+
*/
|
|
3977
|
+
registerSceneTab(tab) {
|
|
3978
|
+
if (!this.adminPanel) {
|
|
3979
|
+
this.log("Cannot register scene tab - call enableProFeatures() first");
|
|
3980
|
+
return;
|
|
3981
|
+
}
|
|
3982
|
+
this.adminPanel.registerSceneTab(tab);
|
|
3983
|
+
}
|
|
3984
|
+
/**
|
|
3985
|
+
* Close Admin/Guide panels (they share the same screen space)
|
|
3986
|
+
* Chat is independent and stays open.
|
|
3987
|
+
* @param except The panel that should stay open: 'admin' | 'guide'
|
|
3988
|
+
*/
|
|
3989
|
+
closeOtherPanels(except) {
|
|
3990
|
+
if (except !== "guide" && this.guideUI?.isVisible) {
|
|
3991
|
+
this.guideUI.hide();
|
|
3992
|
+
}
|
|
3993
|
+
if (except !== "admin" && this.adminPanel?.isOpen) {
|
|
3994
|
+
this.adminPanel.hide();
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
/**
|
|
3998
|
+
* Set up the UI renderer for all SDK panels
|
|
3999
|
+
* Call this in your scene's main() function to render Guide, Chat, Admin panels.
|
|
4000
|
+
* No need to create your own ui.tsx - the SDK handles everything.
|
|
4001
|
+
*
|
|
4002
|
+
* @example
|
|
4003
|
+
* ```typescript
|
|
4004
|
+
* const staticTV = new StaticTVClient({ apiKey: 'dcls_...' })
|
|
4005
|
+
*
|
|
4006
|
+
* export function main() {
|
|
4007
|
+
* staticTV.setupUI()
|
|
4008
|
+
* // That's it! All panels will render automatically
|
|
4009
|
+
* }
|
|
4010
|
+
* ```
|
|
4011
|
+
*/
|
|
4012
|
+
setupUI() {
|
|
4013
|
+
setupStaticUI(this);
|
|
4014
|
+
this.log("UI renderer initialized");
|
|
3437
4015
|
}
|
|
3438
4016
|
/**
|
|
3439
|
-
*
|
|
3440
|
-
*
|
|
4017
|
+
* Show a notification message on screen
|
|
4018
|
+
* Works with both SDK-rendered UI and custom UI setups.
|
|
3441
4019
|
*
|
|
3442
|
-
* @param
|
|
4020
|
+
* @param message The message to display
|
|
4021
|
+
* @param durationMs How long to show (default 5000ms)
|
|
3443
4022
|
*
|
|
3444
4023
|
* @example
|
|
3445
4024
|
* ```typescript
|
|
3446
|
-
* staticTV.
|
|
3447
|
-
*
|
|
3448
|
-
* id: 'lights',
|
|
3449
|
-
* render: () => <MyLightsControls />
|
|
3450
|
-
* })
|
|
4025
|
+
* staticTV.showNotification('Stream started!')
|
|
4026
|
+
* staticTV.showNotification('Custom message', 10000) // 10 seconds
|
|
3451
4027
|
* ```
|
|
3452
4028
|
*/
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
this.log("Cannot register scene tab - call enableProFeatures() first");
|
|
3456
|
-
return;
|
|
3457
|
-
}
|
|
3458
|
-
this.adminPanel.registerSceneTab(tab);
|
|
4029
|
+
showNotification(message, durationMs = 5e3) {
|
|
4030
|
+
showNotification(message, durationMs);
|
|
3459
4031
|
}
|
|
3460
4032
|
/**
|
|
3461
4033
|
* Cleanup when done (call before scene unload)
|
|
@@ -3491,5 +4063,7 @@ var StaticTVClient = class {
|
|
|
3491
4063
|
StaticTVClient,
|
|
3492
4064
|
fetchUserData,
|
|
3493
4065
|
getPlayerDisplayName,
|
|
3494
|
-
getPlayerWallet
|
|
4066
|
+
getPlayerWallet,
|
|
4067
|
+
setupStaticUI,
|
|
4068
|
+
showNotification
|
|
3495
4069
|
});
|