@thestatic-tv/dcl-sdk 2.3.0-dev.0 → 2.3.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 +39 -25
- package/dist/index.d.mts +324 -69
- package/dist/index.d.ts +324 -69
- package/dist/index.js +937 -410
- package/dist/index.mjs +934 -409
- package/package.json +8 -3
package/dist/index.mjs
CHANGED
|
@@ -132,7 +132,7 @@ function ensureTimerSystem() {
|
|
|
132
132
|
try {
|
|
133
133
|
timer.callback();
|
|
134
134
|
} catch (e) {
|
|
135
|
-
console.error("[
|
|
135
|
+
console.error("[TheStatic] Timer error");
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
}
|
|
@@ -173,7 +173,7 @@ function ensureTimeoutSystem() {
|
|
|
173
173
|
try {
|
|
174
174
|
timeout.callback();
|
|
175
175
|
} catch (e) {
|
|
176
|
-
console.error("[
|
|
176
|
+
console.error("[TheStatic] Timeout error");
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
}
|
|
@@ -204,20 +204,41 @@ function dclClearTimeout(timeoutId) {
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
// src/modules/session.ts
|
|
207
|
+
function normalizeTier(tier) {
|
|
208
|
+
if (tier === "lite") return "free";
|
|
209
|
+
if (tier === "full") return "standard";
|
|
210
|
+
if (tier === "free" || tier === "standard" || tier === "pro") return tier;
|
|
211
|
+
return "free";
|
|
212
|
+
}
|
|
207
213
|
var SessionModule = class {
|
|
208
214
|
constructor(client) {
|
|
209
215
|
this.sessionId = null;
|
|
216
|
+
this._keyId = null;
|
|
210
217
|
this.heartbeatTimerId = null;
|
|
211
218
|
this.isActive = false;
|
|
212
|
-
this.
|
|
219
|
+
this._tier = "free";
|
|
213
220
|
this.client = client;
|
|
214
221
|
}
|
|
215
222
|
/**
|
|
216
|
-
* Get the
|
|
217
|
-
|
|
223
|
+
* Get the API key ID (used as default sceneId for Pro users)
|
|
224
|
+
*/
|
|
225
|
+
get keyId() {
|
|
226
|
+
return this._keyId;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get the SDK tier returned by the server
|
|
230
|
+
* - free: Session tracking only
|
|
231
|
+
* - standard: Guide, Chat, Heartbeat, Interactions
|
|
232
|
+
* - pro: Everything + Admin Panel
|
|
233
|
+
*/
|
|
234
|
+
get tier() {
|
|
235
|
+
return this._tier;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* @deprecated Use `tier` instead. Returns mapped value for compatibility.
|
|
218
239
|
*/
|
|
219
240
|
get sdkType() {
|
|
220
|
-
return this.
|
|
241
|
+
return this._tier === "free" ? "lite" : "full";
|
|
221
242
|
}
|
|
222
243
|
/**
|
|
223
244
|
* Get the appropriate session endpoint based on key type
|
|
@@ -264,13 +285,12 @@ var SessionModule = class {
|
|
|
264
285
|
});
|
|
265
286
|
if (response.success && response.sessionId) {
|
|
266
287
|
this.sessionId = response.sessionId;
|
|
288
|
+
this._keyId = response.keyId || null;
|
|
267
289
|
this.isActive = true;
|
|
268
|
-
this.
|
|
290
|
+
this._tier = normalizeTier(response.sdkType);
|
|
269
291
|
this.startHeartbeat();
|
|
270
|
-
this.client.log(`Session started: ${this.sessionId},
|
|
271
|
-
|
|
272
|
-
this.client._enableFullFeatures();
|
|
273
|
-
}
|
|
292
|
+
this.client.log(`Session started: ${this.sessionId}, tier: ${this._tier}`);
|
|
293
|
+
this.client._enableFeaturesForTier(this._tier, this._keyId);
|
|
274
294
|
return this.sessionId;
|
|
275
295
|
}
|
|
276
296
|
return null;
|
|
@@ -558,7 +578,8 @@ var UI_DIMENSIONS = {
|
|
|
558
578
|
// Guide UI - positioned to the left of chat (chat is 380px wide at right:20)
|
|
559
579
|
guide: {
|
|
560
580
|
width: 900,
|
|
561
|
-
height:
|
|
581
|
+
height: 580,
|
|
582
|
+
// Numeric value for scaling (matches chat/admin)
|
|
562
583
|
bottom: 55,
|
|
563
584
|
right: 410,
|
|
564
585
|
// 20 + 380 (chat width) + 10 (gap)
|
|
@@ -577,15 +598,33 @@ var UI_DIMENSIONS = {
|
|
|
577
598
|
padding: 15
|
|
578
599
|
}
|
|
579
600
|
},
|
|
580
|
-
// Chat UI -
|
|
601
|
+
// Chat UI - positioned at right side
|
|
581
602
|
chat: {
|
|
582
603
|
width: 380,
|
|
583
604
|
height: 580,
|
|
584
605
|
bottom: 55,
|
|
585
606
|
right: 20,
|
|
607
|
+
headerHeight: 40,
|
|
586
608
|
messagesPerPage: 5,
|
|
587
609
|
channelsPerPage: 6
|
|
588
610
|
},
|
|
611
|
+
// Admin Panel - positioned left of chat
|
|
612
|
+
admin: {
|
|
613
|
+
width: 400,
|
|
614
|
+
height: 580,
|
|
615
|
+
// Match chat height
|
|
616
|
+
maxHeight: 700,
|
|
617
|
+
bottom: 55,
|
|
618
|
+
right: 410,
|
|
619
|
+
// 20 + 380 (chat width) + 10 (gap)
|
|
620
|
+
headerHeight: 48,
|
|
621
|
+
tabHeight: 40,
|
|
622
|
+
footerHeight: 32,
|
|
623
|
+
sectionHeadHeight: 28,
|
|
624
|
+
buttonHeight: 36,
|
|
625
|
+
buttonHeightSmall: 30,
|
|
626
|
+
inputHeight: 36
|
|
627
|
+
},
|
|
589
628
|
// Shared
|
|
590
629
|
closeButton: {
|
|
591
630
|
size: 40,
|
|
@@ -593,18 +632,18 @@ var UI_DIMENSIONS = {
|
|
|
593
632
|
}
|
|
594
633
|
};
|
|
595
634
|
var DEFAULT_CHAT_THEME = {
|
|
596
|
-
header:
|
|
635
|
+
header: 16,
|
|
597
636
|
channelButton: 14,
|
|
598
637
|
channelDropdown: 14,
|
|
599
|
-
systemMessage:
|
|
600
|
-
chatUsername:
|
|
638
|
+
systemMessage: 13,
|
|
639
|
+
chatUsername: 14,
|
|
601
640
|
chatTimestamp: 11,
|
|
602
|
-
chatMessage:
|
|
603
|
-
input:
|
|
641
|
+
chatMessage: 14,
|
|
642
|
+
input: 14,
|
|
604
643
|
sendButton: 14,
|
|
605
|
-
userInfo:
|
|
644
|
+
userInfo: 13,
|
|
606
645
|
authStatus: 12,
|
|
607
|
-
notification:
|
|
646
|
+
notification: 16,
|
|
608
647
|
closeButton: 16
|
|
609
648
|
};
|
|
610
649
|
function scaleChatTheme(theme, fontScale) {
|
|
@@ -624,6 +663,30 @@ function scaleChatTheme(theme, fontScale) {
|
|
|
624
663
|
closeButton: Math.round(theme.closeButton * fontScale)
|
|
625
664
|
};
|
|
626
665
|
}
|
|
666
|
+
var DEFAULT_ADMIN_THEME = {
|
|
667
|
+
header: 16,
|
|
668
|
+
tabButton: 14,
|
|
669
|
+
sectionHead: 13,
|
|
670
|
+
label: 13,
|
|
671
|
+
labelSmall: 11,
|
|
672
|
+
button: 13,
|
|
673
|
+
buttonSmall: 11,
|
|
674
|
+
input: 13,
|
|
675
|
+
status: 12
|
|
676
|
+
};
|
|
677
|
+
function scaleAdminTheme(theme, fontScale) {
|
|
678
|
+
return {
|
|
679
|
+
header: Math.round(theme.header * fontScale),
|
|
680
|
+
tabButton: Math.round(theme.tabButton * fontScale),
|
|
681
|
+
sectionHead: Math.round(theme.sectionHead * fontScale),
|
|
682
|
+
label: Math.round(theme.label * fontScale),
|
|
683
|
+
labelSmall: Math.round(theme.labelSmall * fontScale),
|
|
684
|
+
button: Math.round(theme.button * fontScale),
|
|
685
|
+
buttonSmall: Math.round(theme.buttonSmall * fontScale),
|
|
686
|
+
input: Math.round(theme.input * fontScale),
|
|
687
|
+
status: Math.round(theme.status * fontScale)
|
|
688
|
+
};
|
|
689
|
+
}
|
|
627
690
|
|
|
628
691
|
// src/ui/components.tsx
|
|
629
692
|
import ReactEcs, { UiEntity, Label, Input } from "@dcl/sdk/react-ecs";
|
|
@@ -679,21 +742,21 @@ var PanelHeader = (props) => {
|
|
|
679
742
|
}),
|
|
680
743
|
ReactEcs.createElement(UiEntity, { key: "pos-spacer", uiTransform: { width: 6 } })
|
|
681
744
|
] : [],
|
|
682
|
-
// Font controls
|
|
745
|
+
// Font controls (with visual disabled state at limits)
|
|
683
746
|
...props.showFontControls ? [
|
|
684
747
|
ReactEcs.createElement(UiEntity, {
|
|
685
748
|
key: "font-down",
|
|
686
749
|
uiTransform: { width: 22, height: 22, justifyContent: "center", alignItems: "center" },
|
|
687
|
-
uiBackground: { color: THEME.colors.buttonBackground },
|
|
688
|
-
onMouseDown: () => props.onFontScaleDown?.(),
|
|
689
|
-
children: [ReactEcs.createElement(UiEntity, { uiText: { value: "\u2212", fontSize: 14, color: THEME.colors.cyan } })]
|
|
750
|
+
uiBackground: { color: props.fontScaleAtMin ? Color42.create(0.1, 0.1, 0.1, 0.3) : THEME.colors.buttonBackground },
|
|
751
|
+
onMouseDown: () => !props.fontScaleAtMin && props.onFontScaleDown?.(),
|
|
752
|
+
children: [ReactEcs.createElement(UiEntity, { uiText: { value: "\u2212", fontSize: 14, color: props.fontScaleAtMin ? THEME.colors.gray : THEME.colors.cyan } })]
|
|
690
753
|
}),
|
|
691
754
|
ReactEcs.createElement(UiEntity, {
|
|
692
755
|
key: "font-up",
|
|
693
756
|
uiTransform: { width: 22, height: 22, justifyContent: "center", alignItems: "center" },
|
|
694
|
-
uiBackground: { color: THEME.colors.buttonBackground },
|
|
695
|
-
onMouseDown: () => props.onFontScaleUp?.(),
|
|
696
|
-
children: [ReactEcs.createElement(UiEntity, { uiText: { value: "+", fontSize: 14, color: THEME.colors.cyan } })]
|
|
757
|
+
uiBackground: { color: props.fontScaleAtMax ? Color42.create(0.1, 0.1, 0.1, 0.3) : THEME.colors.buttonBackground },
|
|
758
|
+
onMouseDown: () => !props.fontScaleAtMax && props.onFontScaleUp?.(),
|
|
759
|
+
children: [ReactEcs.createElement(UiEntity, { uiText: { value: "+", fontSize: 14, color: props.fontScaleAtMax ? THEME.colors.gray : THEME.colors.cyan } })]
|
|
697
760
|
}),
|
|
698
761
|
ReactEcs.createElement(UiEntity, { key: "font-spacer", uiTransform: { width: 6 } })
|
|
699
762
|
] : [],
|
|
@@ -731,7 +794,6 @@ var GuideUIModule = class {
|
|
|
731
794
|
this.currentPage = 0;
|
|
732
795
|
this.itemsPerPage = 6;
|
|
733
796
|
this.searchQuery = "";
|
|
734
|
-
this.uiScale = 1;
|
|
735
797
|
// Current video tracking (for "PLAYING" indicator)
|
|
736
798
|
this._currentVideoId = null;
|
|
737
799
|
// =============================================================================
|
|
@@ -739,34 +801,46 @@ var GuideUIModule = class {
|
|
|
739
801
|
// =============================================================================
|
|
740
802
|
/**
|
|
741
803
|
* Get the guide UI component for rendering
|
|
742
|
-
*
|
|
804
|
+
* Always renders toggle button, plus panel when visible
|
|
743
805
|
*/
|
|
744
806
|
this.getComponent = () => {
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
}
|
|
748
|
-
const windowW = this.s(UI_DIMENSIONS.guide.width);
|
|
807
|
+
const windowW = UI_DIMENSIONS.guide.width;
|
|
808
|
+
const windowH = UI_DIMENSIONS.guide.height;
|
|
749
809
|
return ReactEcs2.createElement(UiEntity2, {
|
|
810
|
+
key: `guide-root-${this.client.uiScale}`,
|
|
750
811
|
uiTransform: {
|
|
751
|
-
width:
|
|
752
|
-
height:
|
|
753
|
-
positionType: "absolute"
|
|
754
|
-
position: { bottom: UI_DIMENSIONS.guide.bottom, right: UI_DIMENSIONS.guide.right },
|
|
755
|
-
flexDirection: "row",
|
|
756
|
-
border: { top: 2, bottom: 2, left: 2, right: 2 },
|
|
757
|
-
borderColor: THEME.colors.panelBorder
|
|
812
|
+
width: "100%",
|
|
813
|
+
height: "100%",
|
|
814
|
+
positionType: "absolute"
|
|
758
815
|
},
|
|
759
|
-
uiBackground: { color: THEME.colors.panel },
|
|
760
816
|
children: [
|
|
761
|
-
|
|
762
|
-
this.
|
|
763
|
-
|
|
817
|
+
// Always render toggle button
|
|
818
|
+
this.renderToggleButton(),
|
|
819
|
+
// Render panel when visible - Guide positioned relative to Chat's scaled width
|
|
820
|
+
// Guide.right = Chat.right + Chat.scaledWidth + gap (10px)
|
|
821
|
+
this._isVisible ? ReactEcs2.createElement(UiEntity2, {
|
|
822
|
+
key: `guide-panel-${this.client.uiScale}`,
|
|
823
|
+
uiTransform: {
|
|
824
|
+
width: windowW,
|
|
825
|
+
height: windowH,
|
|
826
|
+
positionType: "absolute",
|
|
827
|
+
position: { bottom: UI_DIMENSIONS.guide.bottom, right: UI_DIMENSIONS.chat.right + this.s(UI_DIMENSIONS.chat.width) + 10 },
|
|
828
|
+
flexDirection: "row",
|
|
829
|
+
border: { top: 2, bottom: 2, left: 2, right: 2 },
|
|
830
|
+
borderColor: THEME.colors.panelBorder
|
|
831
|
+
},
|
|
832
|
+
uiBackground: { color: THEME.colors.panel },
|
|
833
|
+
children: [
|
|
834
|
+
this.renderLeftPanel(),
|
|
835
|
+
this.renderRightPanel(),
|
|
836
|
+
this.renderCloseButton()
|
|
837
|
+
]
|
|
838
|
+
}) : null
|
|
764
839
|
]
|
|
765
840
|
});
|
|
766
841
|
};
|
|
767
842
|
this.client = client;
|
|
768
843
|
this.config = config;
|
|
769
|
-
this.uiScale = config.uiScale || 1;
|
|
770
844
|
this._currentVideoId = config.currentVideoId || null;
|
|
771
845
|
}
|
|
772
846
|
/**
|
|
@@ -780,6 +854,8 @@ var GuideUIModule = class {
|
|
|
780
854
|
* Show the guide UI
|
|
781
855
|
*/
|
|
782
856
|
show() {
|
|
857
|
+
if (this._isVisible) return;
|
|
858
|
+
this.client.closeOtherPanels("guide");
|
|
783
859
|
this._isVisible = true;
|
|
784
860
|
this.fetchGuideData().catch(() => {
|
|
785
861
|
});
|
|
@@ -915,7 +991,7 @@ var GuideUIModule = class {
|
|
|
915
991
|
// --- UTILITIES ---
|
|
916
992
|
// =============================================================================
|
|
917
993
|
s(value) {
|
|
918
|
-
return Math.round(value * this.uiScale);
|
|
994
|
+
return Math.round(value * this.client.uiScale);
|
|
919
995
|
}
|
|
920
996
|
handleVideoSelect(video) {
|
|
921
997
|
if (this.config.onVideoSelect) {
|
|
@@ -930,7 +1006,7 @@ var GuideUIModule = class {
|
|
|
930
1006
|
renderLeftPanel() {
|
|
931
1007
|
const sidebarW = this.s(UI_DIMENSIONS.guide.sidebar.width);
|
|
932
1008
|
const liveCount = this.liveVideos.length;
|
|
933
|
-
const signalCount = this.videos.filter((v) => !v.isLive).length;
|
|
1009
|
+
const signalCount = this.videos.filter((v) => !v.isLive && !v.isCreator).length;
|
|
934
1010
|
const nodesPlaylist = this.featuredPlaylists.find((p) => p.name === "NODES");
|
|
935
1011
|
const nodesCount = nodesPlaylist ? nodesPlaylist.videos.filter((v) => v.isCreator).length : 0;
|
|
936
1012
|
const displayedPlaylists = this.featuredPlaylists.filter((p) => p.name !== "NODES").slice(0, 6);
|
|
@@ -951,15 +1027,26 @@ var GuideUIModule = class {
|
|
|
951
1027
|
ReactEcs2.createElement(UiEntity2, {
|
|
952
1028
|
uiTransform: { width: "100%", flexDirection: "column", padding: 10 },
|
|
953
1029
|
children: [
|
|
954
|
-
// Title
|
|
1030
|
+
// Title row
|
|
955
1031
|
ReactEcs2.createElement(UiEntity2, {
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1032
|
+
uiTransform: {
|
|
1033
|
+
width: "100%",
|
|
1034
|
+
height: this.s(40),
|
|
1035
|
+
marginBottom: 10,
|
|
1036
|
+
flexDirection: "row",
|
|
1037
|
+
alignItems: "center"
|
|
961
1038
|
},
|
|
962
|
-
|
|
1039
|
+
children: [
|
|
1040
|
+
// Title
|
|
1041
|
+
ReactEcs2.createElement(UiEntity2, {
|
|
1042
|
+
uiText: {
|
|
1043
|
+
value: "THE STATIC TV",
|
|
1044
|
+
fontSize: this.s(UI_DIMENSIONS.guide.sidebar.headerSize),
|
|
1045
|
+
color: THEME.colors.cyan,
|
|
1046
|
+
textAlign: "middle-left"
|
|
1047
|
+
}
|
|
1048
|
+
})
|
|
1049
|
+
]
|
|
963
1050
|
}),
|
|
964
1051
|
// Random Signal button
|
|
965
1052
|
ReactEcs2.createElement(UiEntity2, {
|
|
@@ -1016,7 +1103,7 @@ var GuideUIModule = class {
|
|
|
1016
1103
|
}),
|
|
1017
1104
|
// Menu buttons
|
|
1018
1105
|
this.renderMenuButton("LIVE NOW", liveCount, THEME.colors.red, liveCount > 0 ? "\u25CF" : void 0, "ACTIVE STREAMS"),
|
|
1019
|
-
this.renderMenuButton("SIGNALS", signalCount, THEME.colors.white, void 0, "
|
|
1106
|
+
this.renderMenuButton("SIGNALS", signalCount, THEME.colors.white, void 0, "VODS & RECORDINGS"),
|
|
1020
1107
|
this.renderMenuButton("NODES", nodesCount, THEME.colors.magenta, void 0, "CHANNELS & CREATORS"),
|
|
1021
1108
|
// Divider
|
|
1022
1109
|
ReactEcs2.createElement(UiEntity2, {
|
|
@@ -1098,7 +1185,7 @@ var GuideUIModule = class {
|
|
|
1098
1185
|
let videosToShow = [];
|
|
1099
1186
|
const nodePl = this.featuredPlaylists.find((p) => p.name === "NODES");
|
|
1100
1187
|
if (this.activeTab === "SIGNALS") {
|
|
1101
|
-
videosToShow = this.videos.filter((v) => !v.isLive);
|
|
1188
|
+
videosToShow = this.videos.filter((v) => !v.isLive && !v.isCreator);
|
|
1102
1189
|
} else if (this.activeTab === "NODES") {
|
|
1103
1190
|
videosToShow = nodePl ? nodePl.videos.filter((v) => v.isCreator) : [];
|
|
1104
1191
|
} else if (this.activeTab === "LIVE NOW") {
|
|
@@ -1309,23 +1396,25 @@ var GuideUIModule = class {
|
|
|
1309
1396
|
});
|
|
1310
1397
|
}
|
|
1311
1398
|
renderToggleButton() {
|
|
1399
|
+
const buttonText = this._isVisible ? "CLOSE" : "GUIDE";
|
|
1400
|
+
const buttonColor = this._isVisible ? Color43.create(0.2, 0.2, 0.28, 0.9) : Color43.create(0, 0.5, 0.5, 0.9);
|
|
1401
|
+
const buttonPos = 20 + this.s(100) + 10;
|
|
1312
1402
|
return ReactEcs2.createElement(UiEntity2, {
|
|
1313
1403
|
uiTransform: {
|
|
1314
1404
|
positionType: "absolute",
|
|
1315
|
-
position: { right:
|
|
1316
|
-
// To the left of CHAT button
|
|
1405
|
+
position: { right: buttonPos, bottom: 10 },
|
|
1317
1406
|
width: this.s(100),
|
|
1318
1407
|
height: this.s(45),
|
|
1319
1408
|
justifyContent: "center",
|
|
1320
1409
|
alignItems: "center"
|
|
1321
1410
|
},
|
|
1322
|
-
uiBackground: { color:
|
|
1323
|
-
onMouseDown: () => this.show(),
|
|
1411
|
+
uiBackground: { color: buttonColor },
|
|
1412
|
+
onMouseDown: () => this._isVisible ? this.hide() : this.show(),
|
|
1324
1413
|
children: [
|
|
1325
1414
|
ReactEcs2.createElement(UiEntity2, {
|
|
1326
1415
|
uiText: {
|
|
1327
|
-
value:
|
|
1328
|
-
fontSize: this.s(
|
|
1416
|
+
value: buttonText,
|
|
1417
|
+
fontSize: this.s(14),
|
|
1329
1418
|
color: THEME.colors.white,
|
|
1330
1419
|
textAlign: "middle-center"
|
|
1331
1420
|
}
|
|
@@ -1367,7 +1456,6 @@ var ChatUIModule = class {
|
|
|
1367
1456
|
this.chatScrollOffset = 0;
|
|
1368
1457
|
// UI preferences
|
|
1369
1458
|
this.position = "right";
|
|
1370
|
-
this.fontScale = 1;
|
|
1371
1459
|
// Timers
|
|
1372
1460
|
this.chatTimerId = null;
|
|
1373
1461
|
this.playerInfoTimerId = null;
|
|
@@ -1377,43 +1465,52 @@ var ChatUIModule = class {
|
|
|
1377
1465
|
// =============================================================================
|
|
1378
1466
|
/**
|
|
1379
1467
|
* Get the chat UI component for rendering
|
|
1380
|
-
*
|
|
1468
|
+
* Always renders toggle button, plus panel when visible
|
|
1381
1469
|
*/
|
|
1382
1470
|
this.getComponent = () => {
|
|
1383
|
-
|
|
1384
|
-
return this.renderToggleButton();
|
|
1385
|
-
}
|
|
1386
|
-
const scaledTheme = scaleChatTheme(DEFAULT_CHAT_THEME, this.fontScale);
|
|
1471
|
+
const scaledTheme = scaleChatTheme(DEFAULT_CHAT_THEME, this.client.uiScale);
|
|
1387
1472
|
const positionStyle = this.getPositionStyle();
|
|
1388
1473
|
return ReactEcs3.createElement(UiEntity3, {
|
|
1389
1474
|
uiTransform: {
|
|
1390
|
-
width:
|
|
1391
|
-
height:
|
|
1392
|
-
positionType: "absolute"
|
|
1393
|
-
position: positionStyle,
|
|
1394
|
-
// Must be nested object, not spread!
|
|
1395
|
-
flexDirection: "column",
|
|
1396
|
-
border: { top: 2, bottom: 2, left: 2, right: 2 },
|
|
1397
|
-
borderColor: THEME.colors.panelBorder
|
|
1475
|
+
width: "100%",
|
|
1476
|
+
height: "100%",
|
|
1477
|
+
positionType: "absolute"
|
|
1398
1478
|
},
|
|
1399
|
-
uiBackground: { color: THEME.colors.panel },
|
|
1400
1479
|
children: [
|
|
1401
|
-
|
|
1402
|
-
this.
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1480
|
+
// Always render toggle button
|
|
1481
|
+
this.renderToggleButton(),
|
|
1482
|
+
// Render panel when visible - scaled sizes, fixed position
|
|
1483
|
+
// Key includes uiScale to force re-render when scale changes from other panels
|
|
1484
|
+
this._isVisible ? ReactEcs3.createElement(UiEntity3, {
|
|
1485
|
+
key: `chat-panel-${this.client.uiScale}`,
|
|
1486
|
+
uiTransform: {
|
|
1487
|
+
width: this.s(UI_DIMENSIONS.chat.width),
|
|
1488
|
+
height: this.s(UI_DIMENSIONS.chat.height),
|
|
1489
|
+
positionType: "absolute",
|
|
1490
|
+
position: { bottom: UI_DIMENSIONS.chat.bottom, right: UI_DIMENSIONS.chat.right },
|
|
1491
|
+
flexDirection: "column",
|
|
1492
|
+
border: { top: 2, bottom: 2, left: 2, right: 2 },
|
|
1493
|
+
borderColor: THEME.colors.panelBorder
|
|
1494
|
+
},
|
|
1495
|
+
uiBackground: { color: THEME.colors.panel },
|
|
1496
|
+
children: [
|
|
1497
|
+
this.renderHeader(),
|
|
1498
|
+
this.renderChannelButton(scaledTheme),
|
|
1499
|
+
this.renderMessagesArea(scaledTheme),
|
|
1500
|
+
ReactEcs3.createElement(UiEntity3, {
|
|
1501
|
+
uiTransform: { width: "100%", height: 1, flexShrink: 0 },
|
|
1502
|
+
uiBackground: { color: THEME.colors.panelBorder }
|
|
1503
|
+
}),
|
|
1504
|
+
this.renderUserInfoBar(scaledTheme),
|
|
1505
|
+
this.renderInputArea(scaledTheme),
|
|
1506
|
+
this.renderChannelDropdown(scaledTheme)
|
|
1507
|
+
]
|
|
1508
|
+
}) : null
|
|
1411
1509
|
]
|
|
1412
1510
|
});
|
|
1413
1511
|
};
|
|
1414
1512
|
this.client = client;
|
|
1415
1513
|
this.config = config;
|
|
1416
|
-
this.fontScale = config.fontScale || 1;
|
|
1417
1514
|
}
|
|
1418
1515
|
/**
|
|
1419
1516
|
* Initialize the chat system
|
|
@@ -1432,6 +1529,7 @@ var ChatUIModule = class {
|
|
|
1432
1529
|
* Show the chat UI
|
|
1433
1530
|
*/
|
|
1434
1531
|
show() {
|
|
1532
|
+
if (this._isVisible) return;
|
|
1435
1533
|
this._isVisible = true;
|
|
1436
1534
|
this._unreadCount = 0;
|
|
1437
1535
|
this.chatScrollOffset = 0;
|
|
@@ -1693,6 +1791,10 @@ var ChatUIModule = class {
|
|
|
1693
1791
|
const d = new Date(input);
|
|
1694
1792
|
if (!isNaN(d.getTime())) return d.getTime();
|
|
1695
1793
|
}
|
|
1794
|
+
if (input instanceof Date) return input.getTime();
|
|
1795
|
+
if (typeof input === "object" && "seconds" in input) {
|
|
1796
|
+
return input.seconds * 1e3;
|
|
1797
|
+
}
|
|
1696
1798
|
return 0;
|
|
1697
1799
|
}
|
|
1698
1800
|
formatTime(isoString) {
|
|
@@ -1705,6 +1807,10 @@ var ChatUIModule = class {
|
|
|
1705
1807
|
return "";
|
|
1706
1808
|
}
|
|
1707
1809
|
}
|
|
1810
|
+
/** Scale a dimension by shared uiScale */
|
|
1811
|
+
s(value) {
|
|
1812
|
+
return Math.round(value * this.client.uiScale);
|
|
1813
|
+
}
|
|
1708
1814
|
getPositionStyle() {
|
|
1709
1815
|
return { bottom: 55, right: 20 };
|
|
1710
1816
|
}
|
|
@@ -1712,15 +1818,9 @@ var ChatUIModule = class {
|
|
|
1712
1818
|
return PanelHeader({
|
|
1713
1819
|
title: "LIVE CHAT",
|
|
1714
1820
|
fontSize: 14,
|
|
1715
|
-
fontScale:
|
|
1821
|
+
fontScale: 1,
|
|
1716
1822
|
showPositionControls: false,
|
|
1717
|
-
showFontControls:
|
|
1718
|
-
onFontScaleUp: () => {
|
|
1719
|
-
this.fontScale = Math.min(1.4, this.fontScale + 0.1);
|
|
1720
|
-
},
|
|
1721
|
-
onFontScaleDown: () => {
|
|
1722
|
-
this.fontScale = Math.max(0.7, this.fontScale - 0.1);
|
|
1723
|
-
},
|
|
1823
|
+
showFontControls: false,
|
|
1724
1824
|
onClose: () => this.hide()
|
|
1725
1825
|
});
|
|
1726
1826
|
}
|
|
@@ -2095,23 +2195,24 @@ var ChatUIModule = class {
|
|
|
2095
2195
|
}
|
|
2096
2196
|
renderToggleButton() {
|
|
2097
2197
|
const unreadBadge = this._unreadCount > 0 ? ` (${this._unreadCount})` : "";
|
|
2198
|
+
const buttonText = this._isVisible ? "CLOSE" : `CHAT${unreadBadge}`;
|
|
2199
|
+
const buttonColor = this._isVisible ? Color44.create(0.2, 0.2, 0.28, 0.9) : Color44.create(0.6, 0, 0.5, 0.9);
|
|
2098
2200
|
return ReactEcs3.createElement(UiEntity3, {
|
|
2099
2201
|
uiTransform: {
|
|
2100
2202
|
positionType: "absolute",
|
|
2101
2203
|
position: { right: 20, bottom: 10 },
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
height: 45,
|
|
2204
|
+
width: this.s(100),
|
|
2205
|
+
height: this.s(45),
|
|
2105
2206
|
justifyContent: "center",
|
|
2106
2207
|
alignItems: "center"
|
|
2107
2208
|
},
|
|
2108
|
-
uiBackground: { color:
|
|
2109
|
-
onMouseDown: () => this.show(),
|
|
2209
|
+
uiBackground: { color: buttonColor },
|
|
2210
|
+
onMouseDown: () => this._isVisible ? this.hide() : this.show(),
|
|
2110
2211
|
children: [
|
|
2111
2212
|
ReactEcs3.createElement(UiEntity3, {
|
|
2112
2213
|
uiText: {
|
|
2113
|
-
value:
|
|
2114
|
-
fontSize:
|
|
2214
|
+
value: buttonText,
|
|
2215
|
+
fontSize: this.s(14),
|
|
2115
2216
|
color: THEME.colors.white,
|
|
2116
2217
|
textAlign: "middle-center"
|
|
2117
2218
|
}
|
|
@@ -2145,6 +2246,7 @@ var C = {
|
|
|
2145
2246
|
textDim: Color45.create(0.6, 0.6, 0.7, 1)
|
|
2146
2247
|
};
|
|
2147
2248
|
var AdminPanelUIModule = class {
|
|
2249
|
+
// UI scaling - uses shared client.uiScale
|
|
2148
2250
|
constructor(client, config) {
|
|
2149
2251
|
// State
|
|
2150
2252
|
this.isAdmin = false;
|
|
@@ -2156,6 +2258,8 @@ var AdminPanelUIModule = class {
|
|
|
2156
2258
|
this.customVideoUrl = "";
|
|
2157
2259
|
this.streamData = null;
|
|
2158
2260
|
this.streamFetched = false;
|
|
2261
|
+
this.videoState = null;
|
|
2262
|
+
this.videoStateFetched = false;
|
|
2159
2263
|
this.channelCreating = false;
|
|
2160
2264
|
this.channelCreateError = "";
|
|
2161
2265
|
this.channelDeleting = false;
|
|
@@ -2179,18 +2283,18 @@ var AdminPanelUIModule = class {
|
|
|
2179
2283
|
this.SectionHead = ({ label, color }) => /* @__PURE__ */ ReactEcs4.createElement(
|
|
2180
2284
|
UiEntity4,
|
|
2181
2285
|
{
|
|
2182
|
-
uiTransform: { width: "100%", height:
|
|
2286
|
+
uiTransform: { width: "100%", height: this.s(UI_DIMENSIONS.admin.sectionHeadHeight), margin: { bottom: 8 }, padding: { left: 10 }, alignItems: "center" },
|
|
2183
2287
|
uiBackground: { color: Color45.create(color.r * 0.3, color.g * 0.3, color.b * 0.3, 0.5) }
|
|
2184
2288
|
},
|
|
2185
|
-
/* @__PURE__ */ ReactEcs4.createElement(Label4, { value: label, fontSize:
|
|
2289
|
+
/* @__PURE__ */ ReactEcs4.createElement(Label4, { value: label, fontSize: this.theme.sectionHead, color })
|
|
2186
2290
|
);
|
|
2187
2291
|
this.TabBtn = ({ label, tab }) => /* @__PURE__ */ ReactEcs4.createElement(
|
|
2188
2292
|
Button3,
|
|
2189
2293
|
{
|
|
2190
|
-
uiTransform: { flexGrow: 1, height:
|
|
2294
|
+
uiTransform: { flexGrow: 1, height: this.s(UI_DIMENSIONS.admin.tabHeight), justifyContent: "center", alignItems: "center" },
|
|
2191
2295
|
uiBackground: { color: this.activeTab === tab ? C.tabActive : C.tabInactive },
|
|
2192
2296
|
value: label,
|
|
2193
|
-
fontSize:
|
|
2297
|
+
fontSize: this.theme.tabButton,
|
|
2194
2298
|
color: this.activeTab === tab ? Color45.Black() : C.text,
|
|
2195
2299
|
textAlign: "middle-center",
|
|
2196
2300
|
onMouseDown: () => this.setActiveTab(tab)
|
|
@@ -2200,134 +2304,135 @@ var AdminPanelUIModule = class {
|
|
|
2200
2304
|
if (!this.streamFetched) {
|
|
2201
2305
|
this.fetchStreamData();
|
|
2202
2306
|
}
|
|
2203
|
-
|
|
2307
|
+
const t = this.theme;
|
|
2308
|
+
return /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", width: "100%", padding: 10 } }, !this.streamData?.hasChannel && /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "LIVE STREAM", color: C.btn }), /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "No streaming channel linked", fontSize: t.labelSmall, color: C.textDim, uiTransform: { margin: { bottom: 8 } } }), this.isOwner && /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column" } }, this.streamData?.trialAvailable && /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 10 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2204
2309
|
Button3,
|
|
2205
2310
|
{
|
|
2206
|
-
uiTransform: { width:
|
|
2311
|
+
uiTransform: { width: this.s(200), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: { bottom: 6 } },
|
|
2207
2312
|
uiBackground: { color: this.trialClaiming ? C.btn : C.green },
|
|
2208
|
-
value: this.trialClaiming ? "Claiming..." : "
|
|
2209
|
-
fontSize:
|
|
2313
|
+
value: this.trialClaiming ? "Claiming..." : "Start Free 4-Hour Trial",
|
|
2314
|
+
fontSize: t.button,
|
|
2210
2315
|
color: C.text,
|
|
2211
2316
|
onMouseDown: () => this.claimTrial()
|
|
2212
2317
|
}
|
|
2213
|
-
), this.trialClaimError && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: this.trialClaimError, fontSize:
|
|
2318
|
+
), this.trialClaimError && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: this.trialClaimError, fontSize: t.status, color: C.red }), /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "One-time trial \u2022 4 hours of streaming", fontSize: t.labelSmall, color: C.textDim, uiTransform: { margin: { top: 4 } } })), !this.streamData?.trialAvailable && /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column" } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2214
2319
|
Button3,
|
|
2215
2320
|
{
|
|
2216
|
-
uiTransform: { width:
|
|
2321
|
+
uiTransform: { width: this.s(170), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: { bottom: 6 } },
|
|
2217
2322
|
uiBackground: { color: this.channelCreating ? C.btn : C.cyan },
|
|
2218
2323
|
value: this.channelCreating ? "Creating..." : "+ Create Channel",
|
|
2219
|
-
fontSize:
|
|
2324
|
+
fontSize: t.button,
|
|
2220
2325
|
color: C.text,
|
|
2221
2326
|
onMouseDown: () => this.createChannel()
|
|
2222
2327
|
}
|
|
2223
|
-
), this.channelCreateError && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: this.channelCreateError, fontSize:
|
|
2328
|
+
), this.channelCreateError && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: this.channelCreateError, fontSize: t.status, color: C.red }), /* @__PURE__ */ ReactEcs4.createElement(Label4, { 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__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: this.streamData.isLive ? "LIVE STREAM" : "STREAM SETTINGS", color: this.streamData.isLive ? C.red : C.yellow }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 8 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2224
2329
|
Label4,
|
|
2225
2330
|
{
|
|
2226
2331
|
value: this.streamData.isLive ? `LIVE \u2022 ${this.streamData.currentViewers || 0} viewers` : "OFFLINE",
|
|
2227
|
-
fontSize:
|
|
2332
|
+
fontSize: t.label,
|
|
2228
2333
|
color: this.streamData.isLive ? C.red : C.textDim
|
|
2229
2334
|
}
|
|
2230
|
-
), /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: ` \u2022 ${this.streamData.tier?.toUpperCase() || "RELAY"}`, fontSize:
|
|
2335
|
+
), /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: ` \u2022 ${this.streamData.tier?.toUpperCase() || "RELAY"}`, fontSize: t.labelSmall, color: C.cyan }), /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: ` \u2022 ${this.streamData.sparksBalance || 0} Sparks`, fontSize: t.labelSmall, color: C.textDim })), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2231
2336
|
Label4,
|
|
2232
2337
|
{
|
|
2233
2338
|
value: `Channel: ${this.streamData.channelName || this.streamData.channelId}`,
|
|
2234
|
-
fontSize:
|
|
2339
|
+
fontSize: t.labelSmall,
|
|
2235
2340
|
color: C.textDim,
|
|
2236
|
-
uiTransform: { margin: { bottom:
|
|
2341
|
+
uiTransform: { margin: { bottom: 10 } }
|
|
2237
2342
|
}
|
|
2238
|
-
), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom:
|
|
2343
|
+
), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "RTMP Server:", fontSize: t.labelSmall, color: C.textDim }), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2239
2344
|
Label4,
|
|
2240
2345
|
{
|
|
2241
2346
|
value: this.streamData.rtmpUrl || "Loading...",
|
|
2242
|
-
fontSize:
|
|
2347
|
+
fontSize: t.label,
|
|
2243
2348
|
color: this.streamData.rtmpUrl ? C.text : C.textDim,
|
|
2244
|
-
uiTransform: { margin: { left:
|
|
2349
|
+
uiTransform: { margin: { left: 6 } }
|
|
2245
2350
|
}
|
|
2246
|
-
)), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom:
|
|
2351
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Stream Key:", fontSize: t.labelSmall, color: C.textDim }), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2247
2352
|
Label4,
|
|
2248
2353
|
{
|
|
2249
2354
|
value: this.streamData.streamKey || "Loading...",
|
|
2250
|
-
fontSize:
|
|
2355
|
+
fontSize: t.label,
|
|
2251
2356
|
color: this.streamData.streamKey ? C.cyan : C.textDim,
|
|
2252
|
-
uiTransform: { margin: { left:
|
|
2357
|
+
uiTransform: { margin: { left: 6 } }
|
|
2253
2358
|
}
|
|
2254
|
-
)), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom:
|
|
2359
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 10 } } }, /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "HLS Playback:", fontSize: t.labelSmall, color: C.textDim }), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2255
2360
|
Label4,
|
|
2256
2361
|
{
|
|
2257
2362
|
value: this.streamData.hlsUrl || "Not available",
|
|
2258
|
-
fontSize:
|
|
2363
|
+
fontSize: t.label,
|
|
2259
2364
|
color: this.streamData.hlsUrl ? C.green : C.textDim,
|
|
2260
|
-
uiTransform: { margin: { left:
|
|
2365
|
+
uiTransform: { margin: { left: 6 } }
|
|
2261
2366
|
}
|
|
2262
|
-
)), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom:
|
|
2367
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 6 } } }, !this.streamData.isLive && /* @__PURE__ */ ReactEcs4.createElement(
|
|
2263
2368
|
Button3,
|
|
2264
2369
|
{
|
|
2265
|
-
uiTransform: { width:
|
|
2370
|
+
uiTransform: { width: this.s(110), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2266
2371
|
uiBackground: { color: this.streamControlling ? C.btn : C.green },
|
|
2267
2372
|
value: this.streamControlling ? "Starting..." : "Start Stream",
|
|
2268
|
-
fontSize:
|
|
2373
|
+
fontSize: t.buttonSmall,
|
|
2269
2374
|
color: C.text,
|
|
2270
2375
|
onMouseDown: () => this.startStream()
|
|
2271
2376
|
}
|
|
2272
2377
|
), this.streamData.isLive && /* @__PURE__ */ ReactEcs4.createElement(
|
|
2273
2378
|
Button3,
|
|
2274
2379
|
{
|
|
2275
|
-
uiTransform: { width:
|
|
2380
|
+
uiTransform: { width: this.s(110), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2276
2381
|
uiBackground: { color: this.streamControlling ? C.btn : C.red },
|
|
2277
2382
|
value: this.streamControlling ? "Stopping..." : "Stop Stream",
|
|
2278
|
-
fontSize:
|
|
2383
|
+
fontSize: t.buttonSmall,
|
|
2279
2384
|
color: C.text,
|
|
2280
2385
|
onMouseDown: () => this.stopStream()
|
|
2281
2386
|
}
|
|
2282
2387
|
), this.streamData.isLive && this.streamData.hlsUrl && /* @__PURE__ */ ReactEcs4.createElement(
|
|
2283
2388
|
Button3,
|
|
2284
2389
|
{
|
|
2285
|
-
uiTransform: { width:
|
|
2390
|
+
uiTransform: { width: this.s(100), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2286
2391
|
uiBackground: { color: C.cyan },
|
|
2287
2392
|
value: "Play on Screen",
|
|
2288
|
-
fontSize:
|
|
2393
|
+
fontSize: t.buttonSmall,
|
|
2289
2394
|
color: C.text,
|
|
2290
2395
|
onMouseDown: () => this.config.onVideoPlay?.(this.streamData.hlsUrl)
|
|
2291
2396
|
}
|
|
2292
|
-
)), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom:
|
|
2397
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 6 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2293
2398
|
Button3,
|
|
2294
2399
|
{
|
|
2295
|
-
uiTransform: { width:
|
|
2400
|
+
uiTransform: { width: this.s(80), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2296
2401
|
uiBackground: { color: C.btn },
|
|
2297
2402
|
value: "Refresh",
|
|
2298
|
-
fontSize:
|
|
2403
|
+
fontSize: t.buttonSmall,
|
|
2299
2404
|
color: C.text,
|
|
2300
2405
|
onMouseDown: () => this.refreshStreamStatus()
|
|
2301
2406
|
}
|
|
2302
2407
|
), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2303
2408
|
Button3,
|
|
2304
2409
|
{
|
|
2305
|
-
uiTransform: { width:
|
|
2410
|
+
uiTransform: { width: this.s(95), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2306
2411
|
uiBackground: { color: this.keyRotating ? C.btn : C.yellow },
|
|
2307
2412
|
value: this.keyRotating ? "..." : "Rotate Key",
|
|
2308
|
-
fontSize:
|
|
2413
|
+
fontSize: t.buttonSmall,
|
|
2309
2414
|
color: C.text,
|
|
2310
2415
|
onMouseDown: () => this.rotateStreamKey()
|
|
2311
2416
|
}
|
|
2312
2417
|
), !this.streamData.isLive && /* @__PURE__ */ ReactEcs4.createElement(
|
|
2313
2418
|
Button3,
|
|
2314
2419
|
{
|
|
2315
|
-
uiTransform: { width:
|
|
2420
|
+
uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2316
2421
|
uiBackground: { color: this.channelDeleting ? C.btn : C.red },
|
|
2317
2422
|
value: this.channelDeleting ? "..." : "Delete",
|
|
2318
|
-
fontSize:
|
|
2423
|
+
fontSize: t.buttonSmall,
|
|
2319
2424
|
color: C.text,
|
|
2320
2425
|
onMouseDown: () => this.deleteChannel()
|
|
2321
2426
|
}
|
|
2322
|
-
)), this.streamControlStatus === "started" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Stream started - begin broadcasting in OBS", fontSize:
|
|
2427
|
+
)), this.streamControlStatus === "started" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Stream started - begin broadcasting in OBS", fontSize: t.status, color: C.green }), this.streamControlStatus === "stopped" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Stream stopped", fontSize: t.status, color: C.textDim }), this.keyRotateStatus === "success" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Key rotated! Update OBS", fontSize: t.status, color: C.green }), this.keyRotateStatus === "error" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Failed to rotate key", fontSize: t.status, color: C.red }), this.channelDeleteError && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: this.channelDeleteError, fontSize: t.status, color: C.red }), this.streamControlStatus === "error" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Stream control failed", fontSize: t.status, color: C.red })), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "PLAY NOW", color: C.orange }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2323
2428
|
Input4,
|
|
2324
2429
|
{
|
|
2325
|
-
uiTransform: { width:
|
|
2430
|
+
uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
|
|
2326
2431
|
uiBackground: { color: Color45.create(0.15, 0.15, 0.2, 1) },
|
|
2327
2432
|
placeholder: "Video URL...",
|
|
2328
2433
|
placeholderColor: C.textDim,
|
|
2329
2434
|
color: C.text,
|
|
2330
|
-
fontSize:
|
|
2435
|
+
fontSize: t.input,
|
|
2331
2436
|
value: this.customVideoUrl,
|
|
2332
2437
|
onChange: (val) => {
|
|
2333
2438
|
this.customVideoUrl = val;
|
|
@@ -2336,10 +2441,10 @@ var AdminPanelUIModule = class {
|
|
|
2336
2441
|
), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2337
2442
|
Button3,
|
|
2338
2443
|
{
|
|
2339
|
-
uiTransform: { width:
|
|
2444
|
+
uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
|
|
2340
2445
|
uiBackground: { color: C.green },
|
|
2341
2446
|
value: "Play",
|
|
2342
|
-
fontSize:
|
|
2447
|
+
fontSize: t.button,
|
|
2343
2448
|
color: C.text,
|
|
2344
2449
|
onMouseDown: () => {
|
|
2345
2450
|
if (this.customVideoUrl) this.config.onVideoPlay?.(this.customVideoUrl);
|
|
@@ -2348,211 +2453,218 @@ var AdminPanelUIModule = class {
|
|
|
2348
2453
|
), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2349
2454
|
Button3,
|
|
2350
2455
|
{
|
|
2351
|
-
uiTransform: { width:
|
|
2456
|
+
uiTransform: { width: this.s(65), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 6 } },
|
|
2352
2457
|
uiBackground: { color: C.btn },
|
|
2353
2458
|
value: "Clear",
|
|
2354
|
-
fontSize:
|
|
2459
|
+
fontSize: t.button,
|
|
2355
2460
|
color: C.text,
|
|
2356
2461
|
onMouseDown: () => {
|
|
2357
2462
|
this.customVideoUrl = "";
|
|
2358
2463
|
}
|
|
2359
2464
|
}
|
|
2360
|
-
)), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "PLAYBACK", color: C.green }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom:
|
|
2465
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "PLAYBACK", color: C.green }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2361
2466
|
Button3,
|
|
2362
2467
|
{
|
|
2363
|
-
uiTransform: { width:
|
|
2468
|
+
uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2364
2469
|
uiBackground: { color: C.green },
|
|
2365
2470
|
value: "Play",
|
|
2366
|
-
fontSize:
|
|
2471
|
+
fontSize: t.button,
|
|
2367
2472
|
color: C.text,
|
|
2368
2473
|
onMouseDown: () => this.config.onCommand?.("videoPlay", { playing: true })
|
|
2369
2474
|
}
|
|
2370
2475
|
), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2371
2476
|
Button3,
|
|
2372
2477
|
{
|
|
2373
|
-
uiTransform: { width:
|
|
2478
|
+
uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2374
2479
|
uiBackground: { color: C.red },
|
|
2375
2480
|
value: "Stop",
|
|
2376
|
-
fontSize:
|
|
2481
|
+
fontSize: t.button,
|
|
2377
2482
|
color: C.text,
|
|
2378
2483
|
onMouseDown: () => this.config.onVideoStop?.()
|
|
2379
2484
|
}
|
|
2380
2485
|
), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2381
2486
|
Button3,
|
|
2382
2487
|
{
|
|
2383
|
-
uiTransform: { width:
|
|
2488
|
+
uiTransform: { width: this.s(100), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
|
|
2384
2489
|
uiBackground: { color: C.btn },
|
|
2385
2490
|
value: "Reset Default",
|
|
2386
|
-
fontSize:
|
|
2491
|
+
fontSize: t.buttonSmall,
|
|
2387
2492
|
color: C.text,
|
|
2388
2493
|
onMouseDown: () => this.config.onCommand?.("videoClear", {})
|
|
2389
2494
|
}
|
|
2390
|
-
)), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "VIDEO SLOTS", color: C.cyan }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom:
|
|
2495
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "VIDEO SLOTS", color: C.cyan }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 10 } } }, /* @__PURE__ */ ReactEcs4.createElement(Button3, { 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__ */ ReactEcs4.createElement(Button3, { 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__ */ ReactEcs4.createElement(Button3, { 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__ */ ReactEcs4.createElement(Button3, { 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__ */ ReactEcs4.createElement(Button3, { 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__ */ ReactEcs4.createElement(
|
|
2391
2496
|
Button3,
|
|
2392
2497
|
{
|
|
2393
|
-
uiTransform: { height:
|
|
2498
|
+
uiTransform: { height: this.s(24) },
|
|
2394
2499
|
uiBackground: { color: Color45.create(0, 0, 0, 0) },
|
|
2395
2500
|
value: "Edit slots at thestatic.tv \u2192",
|
|
2396
|
-
fontSize:
|
|
2501
|
+
fontSize: t.labelSmall,
|
|
2397
2502
|
color: C.cyan,
|
|
2398
2503
|
onMouseDown: () => openExternalUrl3({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
|
|
2399
2504
|
}
|
|
2400
2505
|
));
|
|
2401
2506
|
};
|
|
2402
|
-
this.ModTab = () =>
|
|
2403
|
-
|
|
2404
|
-
{
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
this.broadcastText
|
|
2507
|
+
this.ModTab = () => {
|
|
2508
|
+
const t = this.theme;
|
|
2509
|
+
return /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", width: "100%", padding: 10 } }, this.modStatus === "loading" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Loading...", fontSize: t.status, color: C.yellow }), this.modStatus === "saved" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Saved!", fontSize: t.status, color: C.green }), this.modStatus === "error" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Error - check input", fontSize: t.status, color: C.red }), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "BROADCAST", color: C.orange }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2510
|
+
Input4,
|
|
2511
|
+
{
|
|
2512
|
+
uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
|
|
2513
|
+
uiBackground: { color: Color45.create(0.15, 0.15, 0.2, 1) },
|
|
2514
|
+
placeholder: "Message to all players...",
|
|
2515
|
+
placeholderColor: C.textDim,
|
|
2516
|
+
color: C.text,
|
|
2517
|
+
fontSize: t.input,
|
|
2518
|
+
value: this.broadcastText,
|
|
2519
|
+
onChange: (val) => {
|
|
2520
|
+
this.broadcastText = val;
|
|
2521
|
+
}
|
|
2414
2522
|
}
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
Button3,
|
|
2418
|
-
{
|
|
2419
|
-
uiTransform: { width: 55, height: 32, margin: { left: 6 } },
|
|
2420
|
-
uiBackground: { color: C.orange },
|
|
2421
|
-
value: "Send",
|
|
2422
|
-
fontSize: 11,
|
|
2423
|
-
color: C.text,
|
|
2424
|
-
onMouseDown: () => this.sendBroadcast()
|
|
2425
|
-
}
|
|
2426
|
-
)), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "CHAOS MODE", color: C.red }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 12 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2427
|
-
Button3,
|
|
2428
|
-
{
|
|
2429
|
-
uiTransform: { width: 120, height: 32, margin: 3 },
|
|
2430
|
-
uiBackground: { color: C.red },
|
|
2431
|
-
value: "KICK ALL",
|
|
2432
|
-
fontSize: 11,
|
|
2433
|
-
color: C.text,
|
|
2434
|
-
onMouseDown: () => this.config.onCommand?.("kickAll", {})
|
|
2435
|
-
}
|
|
2436
|
-
)), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "SCENE ADMINS", color: C.purple }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 4 } } }, this.sceneAdmins.length === 0 && this.modStatus !== "loading" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "No scene admins", fontSize: 10, color: C.textDim }), this.sceneAdmins.map((wallet, i) => /* @__PURE__ */ ReactEcs4.createElement(
|
|
2437
|
-
UiEntity4,
|
|
2438
|
-
{
|
|
2439
|
-
key: `admin-${i}`,
|
|
2440
|
-
uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 3 }, width: "100%" }
|
|
2441
|
-
},
|
|
2442
|
-
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2443
|
-
Label4,
|
|
2523
|
+
), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2524
|
+
Button3,
|
|
2444
2525
|
{
|
|
2445
|
-
|
|
2446
|
-
|
|
2526
|
+
uiTransform: { width: this.s(65), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
|
|
2527
|
+
uiBackground: { color: C.orange },
|
|
2528
|
+
value: "Send",
|
|
2529
|
+
fontSize: t.button,
|
|
2447
2530
|
color: C.text,
|
|
2448
|
-
|
|
2531
|
+
onMouseDown: () => this.sendBroadcast()
|
|
2449
2532
|
}
|
|
2450
|
-
),
|
|
2451
|
-
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2533
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "CHAOS MODE", color: C.red }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2452
2534
|
Button3,
|
|
2453
2535
|
{
|
|
2454
|
-
uiTransform: { width:
|
|
2455
|
-
uiBackground: { color: C.
|
|
2456
|
-
value: "
|
|
2457
|
-
fontSize:
|
|
2536
|
+
uiTransform: { width: this.s(130), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: 4 },
|
|
2537
|
+
uiBackground: { color: C.red },
|
|
2538
|
+
value: "KICK ALL",
|
|
2539
|
+
fontSize: t.button,
|
|
2458
2540
|
color: C.text,
|
|
2459
|
-
onMouseDown: () =>
|
|
2541
|
+
onMouseDown: () => {
|
|
2542
|
+
this.log("KICK ALL clicked");
|
|
2543
|
+
this.config.onCommand?.("kickAll", {});
|
|
2544
|
+
}
|
|
2460
2545
|
}
|
|
2461
|
-
)
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2546
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "SCENE ADMINS", color: C.purple }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, this.sceneAdmins.length === 0 && this.modStatus !== "loading" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "No scene admins", fontSize: t.labelSmall, color: C.textDim }), this.sceneAdmins.map((wallet, i) => /* @__PURE__ */ ReactEcs4.createElement(
|
|
2547
|
+
UiEntity4,
|
|
2548
|
+
{
|
|
2549
|
+
key: `admin-${i}`,
|
|
2550
|
+
uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 5 }, width: "100%" }
|
|
2551
|
+
},
|
|
2552
|
+
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2553
|
+
Label4,
|
|
2554
|
+
{
|
|
2555
|
+
value: `${wallet.slice(0, 6)}...${wallet.slice(-4)}`,
|
|
2556
|
+
fontSize: t.label,
|
|
2557
|
+
color: C.text,
|
|
2558
|
+
uiTransform: { width: this.s(130) }
|
|
2559
|
+
}
|
|
2560
|
+
),
|
|
2561
|
+
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2562
|
+
Button3,
|
|
2563
|
+
{
|
|
2564
|
+
uiTransform: { width: this.s(70), height: this.s(28), margin: { left: 8 } },
|
|
2565
|
+
uiBackground: { color: C.btn },
|
|
2566
|
+
value: "Remove",
|
|
2567
|
+
fontSize: t.buttonSmall,
|
|
2568
|
+
color: C.text,
|
|
2569
|
+
onMouseDown: () => this.removeSceneAdmin(wallet)
|
|
2570
|
+
}
|
|
2571
|
+
)
|
|
2572
|
+
))), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2573
|
+
Input4,
|
|
2574
|
+
{
|
|
2575
|
+
uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
|
|
2576
|
+
uiBackground: { color: Color45.create(0.15, 0.15, 0.2, 1) },
|
|
2577
|
+
placeholder: "0x... add admin",
|
|
2578
|
+
placeholderColor: C.textDim,
|
|
2579
|
+
color: C.text,
|
|
2580
|
+
fontSize: t.input,
|
|
2581
|
+
value: this.newAdminWallet,
|
|
2582
|
+
onChange: (val) => {
|
|
2583
|
+
this.newAdminWallet = val;
|
|
2584
|
+
}
|
|
2474
2585
|
}
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2586
|
+
), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2587
|
+
Button3,
|
|
2588
|
+
{
|
|
2589
|
+
uiTransform: { width: this.s(60), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
|
|
2590
|
+
uiBackground: { color: C.purple },
|
|
2591
|
+
value: "Add",
|
|
2592
|
+
fontSize: t.button,
|
|
2593
|
+
color: C.text,
|
|
2594
|
+
onMouseDown: () => {
|
|
2595
|
+
if (this.newAdminWallet) this.addSceneAdmin(this.newAdminWallet);
|
|
2596
|
+
}
|
|
2486
2597
|
}
|
|
2487
|
-
}
|
|
2488
|
-
|
|
2489
|
-
UiEntity4,
|
|
2490
|
-
{
|
|
2491
|
-
key: `ban-${i}`,
|
|
2492
|
-
uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 3 }, width: "100%" }
|
|
2493
|
-
},
|
|
2494
|
-
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2495
|
-
Label4,
|
|
2598
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "BANNED WALLETS", color: C.red }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, this.bannedWallets.length === 0 && this.modStatus !== "loading" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "No banned wallets", fontSize: t.labelSmall, color: C.textDim }), this.bannedWallets.map((wallet, i) => /* @__PURE__ */ ReactEcs4.createElement(
|
|
2599
|
+
UiEntity4,
|
|
2496
2600
|
{
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2601
|
+
key: `ban-${i}`,
|
|
2602
|
+
uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 5 }, width: "100%" }
|
|
2603
|
+
},
|
|
2604
|
+
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2605
|
+
Label4,
|
|
2606
|
+
{
|
|
2607
|
+
value: `${wallet.slice(0, 6)}...${wallet.slice(-4)}`,
|
|
2608
|
+
fontSize: t.label,
|
|
2609
|
+
color: C.red,
|
|
2610
|
+
uiTransform: { width: this.s(130) }
|
|
2611
|
+
}
|
|
2612
|
+
),
|
|
2613
|
+
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2614
|
+
Button3,
|
|
2615
|
+
{
|
|
2616
|
+
uiTransform: { width: this.s(70), height: this.s(28), margin: { left: 8 } },
|
|
2617
|
+
uiBackground: { color: C.green },
|
|
2618
|
+
value: "Unban",
|
|
2619
|
+
fontSize: t.buttonSmall,
|
|
2620
|
+
color: C.text,
|
|
2621
|
+
onMouseDown: () => this.unbanWallet(wallet)
|
|
2622
|
+
}
|
|
2623
|
+
)
|
|
2624
|
+
))), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 10 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2625
|
+
Input4,
|
|
2626
|
+
{
|
|
2627
|
+
uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
|
|
2628
|
+
uiBackground: { color: Color45.create(0.15, 0.15, 0.2, 1) },
|
|
2629
|
+
placeholder: "0x... ban wallet",
|
|
2630
|
+
placeholderColor: C.textDim,
|
|
2631
|
+
color: C.text,
|
|
2632
|
+
fontSize: t.input,
|
|
2633
|
+
value: this.newBanWallet,
|
|
2634
|
+
onChange: (val) => {
|
|
2635
|
+
this.newBanWallet = val;
|
|
2636
|
+
}
|
|
2501
2637
|
}
|
|
2502
|
-
),
|
|
2503
|
-
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2638
|
+
), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2504
2639
|
Button3,
|
|
2505
2640
|
{
|
|
2506
|
-
uiTransform: { width:
|
|
2507
|
-
uiBackground: { color: C.
|
|
2508
|
-
value: "
|
|
2509
|
-
fontSize:
|
|
2641
|
+
uiTransform: { width: this.s(60), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
|
|
2642
|
+
uiBackground: { color: C.red },
|
|
2643
|
+
value: "Ban",
|
|
2644
|
+
fontSize: t.button,
|
|
2510
2645
|
color: C.text,
|
|
2511
|
-
onMouseDown: () =>
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
))), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 8 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2515
|
-
Input4,
|
|
2516
|
-
{
|
|
2517
|
-
uiTransform: { width: 200, height: 28 },
|
|
2518
|
-
uiBackground: { color: Color45.create(0.15, 0.15, 0.2, 1) },
|
|
2519
|
-
placeholder: "0x... ban wallet",
|
|
2520
|
-
placeholderColor: C.textDim,
|
|
2521
|
-
color: C.text,
|
|
2522
|
-
fontSize: 10,
|
|
2523
|
-
value: this.newBanWallet,
|
|
2524
|
-
onChange: (val) => {
|
|
2525
|
-
this.newBanWallet = val;
|
|
2646
|
+
onMouseDown: () => {
|
|
2647
|
+
if (this.newBanWallet) this.banWallet(this.newBanWallet);
|
|
2648
|
+
}
|
|
2526
2649
|
}
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
onMouseDown: () => {
|
|
2537
|
-
if (this.newBanWallet) this.banWallet(this.newBanWallet);
|
|
2650
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2651
|
+
Button3,
|
|
2652
|
+
{
|
|
2653
|
+
uiTransform: { height: this.s(24) },
|
|
2654
|
+
uiBackground: { color: Color45.create(0, 0, 0, 0) },
|
|
2655
|
+
value: "Manage at thestatic.tv \u2192",
|
|
2656
|
+
fontSize: t.labelSmall,
|
|
2657
|
+
color: C.cyan,
|
|
2658
|
+
onMouseDown: () => openExternalUrl3({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
|
|
2538
2659
|
}
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
Button3,
|
|
2542
|
-
{
|
|
2543
|
-
uiTransform: { height: 20 },
|
|
2544
|
-
uiBackground: { color: Color45.create(0, 0, 0, 0) },
|
|
2545
|
-
value: "Manage at thestatic.tv \u2192",
|
|
2546
|
-
fontSize: 9,
|
|
2547
|
-
color: C.cyan,
|
|
2548
|
-
onMouseDown: () => openExternalUrl3({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
|
|
2549
|
-
}
|
|
2550
|
-
));
|
|
2660
|
+
));
|
|
2661
|
+
};
|
|
2551
2662
|
/**
|
|
2552
2663
|
* Get the React-ECS component for the admin panel
|
|
2553
2664
|
*/
|
|
2554
2665
|
this.getComponent = () => {
|
|
2555
2666
|
if (!this.isAdmin) return null;
|
|
2667
|
+
const t = this.theme;
|
|
2556
2668
|
const tabs = [];
|
|
2557
2669
|
if (this.config.sceneTabs && this.config.sceneTabs.length > 0) {
|
|
2558
2670
|
this.config.sceneTabs.forEach((tab) => tabs.push({ label: tab.label, id: tab.id }));
|
|
@@ -2563,7 +2675,7 @@ var AdminPanelUIModule = class {
|
|
|
2563
2675
|
if (this.config.showModTab !== false && this.isOwner) {
|
|
2564
2676
|
tabs.push({ label: "MOD", id: "mod" });
|
|
2565
2677
|
}
|
|
2566
|
-
if (!tabs.find((
|
|
2678
|
+
if (!tabs.find((tab) => tab.id === this.activeTab) && tabs.length > 0) {
|
|
2567
2679
|
this.activeTab = tabs[0].id;
|
|
2568
2680
|
}
|
|
2569
2681
|
return /* @__PURE__ */ ReactEcs4.createElement(
|
|
@@ -2579,17 +2691,17 @@ var AdminPanelUIModule = class {
|
|
|
2579
2691
|
UiEntity4,
|
|
2580
2692
|
{
|
|
2581
2693
|
uiTransform: {
|
|
2582
|
-
position: { right:
|
|
2694
|
+
position: { right: 20 + this.s(100) + 10 + this.s(100) + 10, bottom: 10 },
|
|
2583
2695
|
positionType: "absolute"
|
|
2584
2696
|
}
|
|
2585
2697
|
},
|
|
2586
2698
|
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2587
2699
|
Button3,
|
|
2588
2700
|
{
|
|
2589
|
-
uiTransform: { width:
|
|
2701
|
+
uiTransform: { width: this.s(100), height: this.s(45) },
|
|
2590
2702
|
uiBackground: { color: this.panelOpen ? C.btn : C.header },
|
|
2591
2703
|
value: this.panelOpen ? "CLOSE" : "ADMIN",
|
|
2592
|
-
fontSize:
|
|
2704
|
+
fontSize: this.s(14),
|
|
2593
2705
|
color: C.text,
|
|
2594
2706
|
onMouseDown: () => this.toggle()
|
|
2595
2707
|
}
|
|
@@ -2598,10 +2710,12 @@ var AdminPanelUIModule = class {
|
|
|
2598
2710
|
this.panelOpen && /* @__PURE__ */ ReactEcs4.createElement(
|
|
2599
2711
|
UiEntity4,
|
|
2600
2712
|
{
|
|
2713
|
+
key: `admin-panel-${this.client.uiScale}`,
|
|
2601
2714
|
uiTransform: {
|
|
2602
|
-
width:
|
|
2603
|
-
|
|
2604
|
-
|
|
2715
|
+
width: this.s(UI_DIMENSIONS.admin.width),
|
|
2716
|
+
height: this.s(UI_DIMENSIONS.admin.height),
|
|
2717
|
+
maxHeight: this.s(UI_DIMENSIONS.admin.maxHeight),
|
|
2718
|
+
position: { right: UI_DIMENSIONS.chat.right + this.s(UI_DIMENSIONS.chat.width) + 10, bottom: UI_DIMENSIONS.admin.bottom },
|
|
2605
2719
|
positionType: "absolute",
|
|
2606
2720
|
flexDirection: "column"
|
|
2607
2721
|
},
|
|
@@ -2612,16 +2726,28 @@ var AdminPanelUIModule = class {
|
|
|
2612
2726
|
{
|
|
2613
2727
|
uiTransform: {
|
|
2614
2728
|
width: "100%",
|
|
2615
|
-
height:
|
|
2616
|
-
justifyContent: "
|
|
2729
|
+
height: this.s(UI_DIMENSIONS.admin.headerHeight),
|
|
2730
|
+
justifyContent: "space-between",
|
|
2617
2731
|
alignItems: "center",
|
|
2618
|
-
flexDirection: "row"
|
|
2732
|
+
flexDirection: "row",
|
|
2733
|
+
padding: { left: 12, right: 8 }
|
|
2619
2734
|
},
|
|
2620
2735
|
uiBackground: { color: C.header }
|
|
2621
2736
|
},
|
|
2622
|
-
/* @__PURE__ */ ReactEcs4.createElement(Label4, { value: this.config.title || "ADMIN PANEL", fontSize:
|
|
2737
|
+
/* @__PURE__ */ ReactEcs4.createElement(Label4, { value: this.config.title || "ADMIN PANEL", fontSize: t.header, color: C.text }),
|
|
2738
|
+
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2739
|
+
Button3,
|
|
2740
|
+
{
|
|
2741
|
+
uiTransform: { width: this.s(28), height: this.s(28) },
|
|
2742
|
+
uiBackground: { color: Color45.create(0, 0, 0, 0.3) },
|
|
2743
|
+
value: "X",
|
|
2744
|
+
fontSize: t.header,
|
|
2745
|
+
color: THEME.colors.red,
|
|
2746
|
+
onMouseDown: () => this.hide()
|
|
2747
|
+
}
|
|
2748
|
+
)
|
|
2623
2749
|
),
|
|
2624
|
-
/* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { width: "100%", height:
|
|
2750
|
+
/* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { width: "100%", height: this.s(UI_DIMENSIONS.admin.tabHeight), flexDirection: "row" } }, tabs.map((tab) => /* @__PURE__ */ ReactEcs4.createElement(this.TabBtn, { label: tab.label, tab: tab.id }))),
|
|
2625
2751
|
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2626
2752
|
UiEntity4,
|
|
2627
2753
|
{
|
|
@@ -2641,7 +2767,7 @@ var AdminPanelUIModule = class {
|
|
|
2641
2767
|
{
|
|
2642
2768
|
uiTransform: {
|
|
2643
2769
|
width: "100%",
|
|
2644
|
-
height:
|
|
2770
|
+
height: this.s(UI_DIMENSIONS.admin.footerHeight),
|
|
2645
2771
|
justifyContent: "center",
|
|
2646
2772
|
alignItems: "center"
|
|
2647
2773
|
},
|
|
@@ -2650,10 +2776,10 @@ var AdminPanelUIModule = class {
|
|
|
2650
2776
|
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2651
2777
|
Button3,
|
|
2652
2778
|
{
|
|
2653
|
-
uiTransform: { height:
|
|
2779
|
+
uiTransform: { height: this.s(24) },
|
|
2654
2780
|
uiBackground: { color: Color45.create(0, 0, 0, 0) },
|
|
2655
2781
|
value: `thestatic.tv/scene/${this.config.sceneId} \u2192`,
|
|
2656
|
-
fontSize:
|
|
2782
|
+
fontSize: t.labelSmall,
|
|
2657
2783
|
color: C.cyan,
|
|
2658
2784
|
onMouseDown: () => openExternalUrl3({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
|
|
2659
2785
|
}
|
|
@@ -2663,14 +2789,19 @@ var AdminPanelUIModule = class {
|
|
|
2663
2789
|
);
|
|
2664
2790
|
};
|
|
2665
2791
|
this.client = client;
|
|
2792
|
+
if (!config.sceneId) {
|
|
2793
|
+
throw new Error("[AdminPanel] sceneId is required");
|
|
2794
|
+
}
|
|
2666
2795
|
this.config = {
|
|
2667
2796
|
showVideoTab: true,
|
|
2668
2797
|
showModTab: true,
|
|
2669
2798
|
title: "ADMIN PANEL",
|
|
2670
2799
|
debug: false,
|
|
2671
|
-
...config
|
|
2800
|
+
...config,
|
|
2801
|
+
sceneId: config.sceneId
|
|
2802
|
+
// Ensure sceneId is set
|
|
2672
2803
|
};
|
|
2673
|
-
this.baseUrl = client.
|
|
2804
|
+
this.baseUrl = client.getBaseUrl();
|
|
2674
2805
|
if (config.headerColor) {
|
|
2675
2806
|
C.header = Color45.create(
|
|
2676
2807
|
config.headerColor.r,
|
|
@@ -2685,12 +2816,21 @@ var AdminPanelUIModule = class {
|
|
|
2685
2816
|
console.log(`[AdminPanel] ${msg}`, ...args);
|
|
2686
2817
|
}
|
|
2687
2818
|
}
|
|
2819
|
+
/** Scale a dimension by shared uiScale */
|
|
2820
|
+
s(value) {
|
|
2821
|
+
return Math.round(value * this.client.uiScale);
|
|
2822
|
+
}
|
|
2823
|
+
/** Get scaled theme */
|
|
2824
|
+
get theme() {
|
|
2825
|
+
return scaleAdminTheme(DEFAULT_ADMIN_THEME, this.client.uiScale);
|
|
2826
|
+
}
|
|
2688
2827
|
/**
|
|
2689
|
-
* Initialize the admin panel - checks admin status
|
|
2828
|
+
* Initialize the admin panel - checks admin status and fetches video state
|
|
2690
2829
|
*/
|
|
2691
2830
|
async init() {
|
|
2692
|
-
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
2693
2831
|
await this.checkAdminStatus();
|
|
2832
|
+
await this.fetchVideoState();
|
|
2833
|
+
this.autoPlayDefault();
|
|
2694
2834
|
this.log("Initialized");
|
|
2695
2835
|
}
|
|
2696
2836
|
/**
|
|
@@ -2703,17 +2843,23 @@ var AdminPanelUIModule = class {
|
|
|
2703
2843
|
return;
|
|
2704
2844
|
}
|
|
2705
2845
|
this.playerWallet = player.userId;
|
|
2846
|
+
if (this.config.forceAdmin) {
|
|
2847
|
+
this.isAdmin = true;
|
|
2848
|
+
this.isOwner = true;
|
|
2849
|
+
this.log("Admin status FORCED (forceAdmin: true)");
|
|
2850
|
+
return;
|
|
2851
|
+
}
|
|
2706
2852
|
try {
|
|
2707
2853
|
const res = await fetch(
|
|
2708
2854
|
`${this.baseUrl}/scene/${this.config.sceneId}/admin-check?wallet=${player.userId}`
|
|
2709
2855
|
);
|
|
2710
2856
|
if (res.ok) {
|
|
2711
2857
|
const data = await res.json();
|
|
2712
|
-
this.isAdmin = data.hasAccess;
|
|
2713
|
-
this.isOwner = data.isOwner || data.
|
|
2858
|
+
this.isAdmin = data.showButton ?? data.hasAccess;
|
|
2859
|
+
this.isOwner = data.isOwner || data.isSceneAdmin;
|
|
2714
2860
|
if (data.isBanned) {
|
|
2715
2861
|
this.config.onBroadcast?.("You have been banned from this scene.");
|
|
2716
|
-
|
|
2862
|
+
this.banKickPlayer();
|
|
2717
2863
|
this.log("Player is banned - kicking");
|
|
2718
2864
|
}
|
|
2719
2865
|
this.log("Admin status:", this.isAdmin, "Owner:", this.isOwner);
|
|
@@ -2726,13 +2872,31 @@ var AdminPanelUIModule = class {
|
|
|
2726
2872
|
* Toggle the admin panel open/closed
|
|
2727
2873
|
*/
|
|
2728
2874
|
toggle() {
|
|
2729
|
-
this.panelOpen
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2875
|
+
if (this.panelOpen) {
|
|
2876
|
+
this.hide();
|
|
2877
|
+
} else {
|
|
2878
|
+
this.show();
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
/**
|
|
2882
|
+
* Show the admin panel
|
|
2883
|
+
*/
|
|
2884
|
+
show() {
|
|
2885
|
+
if (this.panelOpen) return;
|
|
2886
|
+
this.client.closeOtherPanels("admin");
|
|
2887
|
+
this.panelOpen = true;
|
|
2888
|
+
if (this.activeTab === "video") {
|
|
2733
2889
|
this.startStreamPolling();
|
|
2734
2890
|
}
|
|
2735
2891
|
}
|
|
2892
|
+
/**
|
|
2893
|
+
* Hide the admin panel
|
|
2894
|
+
*/
|
|
2895
|
+
hide() {
|
|
2896
|
+
if (!this.panelOpen) return;
|
|
2897
|
+
this.panelOpen = false;
|
|
2898
|
+
this.stopStreamPolling();
|
|
2899
|
+
}
|
|
2736
2900
|
/**
|
|
2737
2901
|
* Check if the panel is currently open
|
|
2738
2902
|
*/
|
|
@@ -2746,8 +2910,8 @@ var AdminPanelUIModule = class {
|
|
|
2746
2910
|
return this.isAdmin;
|
|
2747
2911
|
}
|
|
2748
2912
|
/**
|
|
2749
|
-
|
|
2750
|
-
|
|
2913
|
+
* Register a custom scene tab (Pro tier)
|
|
2914
|
+
*/
|
|
2751
2915
|
registerSceneTab(tab) {
|
|
2752
2916
|
if (!this.config.sceneTabs) {
|
|
2753
2917
|
this.config.sceneTabs = [];
|
|
@@ -2758,7 +2922,7 @@ var AdminPanelUIModule = class {
|
|
|
2758
2922
|
// --- Stream Polling ---
|
|
2759
2923
|
startStreamPolling() {
|
|
2760
2924
|
if (this.pollIntervalId !== null) return;
|
|
2761
|
-
this.pollIntervalId =
|
|
2925
|
+
this.pollIntervalId = dclSetInterval(() => {
|
|
2762
2926
|
if (this.activeTab === "video" && this.panelOpen && this.streamData?.hasChannel) {
|
|
2763
2927
|
this.refreshStreamStatus();
|
|
2764
2928
|
}
|
|
@@ -2767,7 +2931,7 @@ var AdminPanelUIModule = class {
|
|
|
2767
2931
|
}
|
|
2768
2932
|
stopStreamPolling() {
|
|
2769
2933
|
if (this.pollIntervalId !== null) {
|
|
2770
|
-
|
|
2934
|
+
dclClearInterval(this.pollIntervalId);
|
|
2771
2935
|
this.pollIntervalId = null;
|
|
2772
2936
|
this.log("Stream polling stopped");
|
|
2773
2937
|
}
|
|
@@ -2800,6 +2964,51 @@ var AdminPanelUIModule = class {
|
|
|
2800
2964
|
} catch (err) {
|
|
2801
2965
|
}
|
|
2802
2966
|
}
|
|
2967
|
+
// --- Video State (Slots) ---
|
|
2968
|
+
async fetchVideoState() {
|
|
2969
|
+
if (this.videoStateFetched) return;
|
|
2970
|
+
try {
|
|
2971
|
+
const res = await fetch(
|
|
2972
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/video-state`
|
|
2973
|
+
);
|
|
2974
|
+
if (res.ok) {
|
|
2975
|
+
const data = await res.json();
|
|
2976
|
+
this.videoState = {
|
|
2977
|
+
defaultSlot: data.defaultSlot || null,
|
|
2978
|
+
videoSlots: data.videoSlots || {}
|
|
2979
|
+
};
|
|
2980
|
+
this.videoStateFetched = true;
|
|
2981
|
+
this.log("Video state fetched:", this.videoState.defaultSlot || "no default");
|
|
2982
|
+
}
|
|
2983
|
+
} catch (err) {
|
|
2984
|
+
this.log("Video state fetch error:", err);
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
/**
|
|
2988
|
+
* Play a video slot by ID - looks up URL and calls onVideoPlay
|
|
2989
|
+
*/
|
|
2990
|
+
playSlot(slotId) {
|
|
2991
|
+
const slot = this.videoState?.videoSlots?.[slotId];
|
|
2992
|
+
if (slot?.url) {
|
|
2993
|
+
this.log("Playing slot:", slotId, slot.url);
|
|
2994
|
+
this.config.onVideoPlay?.(slot.url);
|
|
2995
|
+
} else {
|
|
2996
|
+
this.log("Slot has no URL:", slotId);
|
|
2997
|
+
this.config.onVideoSlotPlay?.(slotId);
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
/**
|
|
3001
|
+
* Auto-play the default slot if configured
|
|
3002
|
+
*/
|
|
3003
|
+
autoPlayDefault() {
|
|
3004
|
+
if (!this.videoState?.defaultSlot) return;
|
|
3005
|
+
const defaultSlot = this.videoState.defaultSlot;
|
|
3006
|
+
const slot = this.videoState.videoSlots?.[defaultSlot];
|
|
3007
|
+
if (slot?.url) {
|
|
3008
|
+
this.log("Auto-playing default slot:", defaultSlot);
|
|
3009
|
+
this.config.onVideoPlay?.(slot.url);
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
2803
3012
|
async createChannel() {
|
|
2804
3013
|
if (!this.playerWallet || this.channelCreating) return;
|
|
2805
3014
|
this.channelCreating = true;
|
|
@@ -2916,9 +3125,6 @@ var AdminPanelUIModule = class {
|
|
|
2916
3125
|
this.config.onBroadcast?.("Stream started!");
|
|
2917
3126
|
this.streamFetched = false;
|
|
2918
3127
|
await this.fetchStreamData();
|
|
2919
|
-
setTimeout(() => {
|
|
2920
|
-
this.streamControlStatus = "";
|
|
2921
|
-
}, 3e3);
|
|
2922
3128
|
} else {
|
|
2923
3129
|
this.streamControlStatus = "error";
|
|
2924
3130
|
this.log("Start stream failed:", data.error);
|
|
@@ -2951,9 +3157,6 @@ var AdminPanelUIModule = class {
|
|
|
2951
3157
|
this.config.onBroadcast?.("Stream stopped");
|
|
2952
3158
|
this.streamFetched = false;
|
|
2953
3159
|
await this.fetchStreamData();
|
|
2954
|
-
setTimeout(() => {
|
|
2955
|
-
this.streamControlStatus = "";
|
|
2956
|
-
}, 3e3);
|
|
2957
3160
|
} else {
|
|
2958
3161
|
this.streamControlStatus = "error";
|
|
2959
3162
|
this.log("Stop stream failed:", data.error);
|
|
@@ -2983,9 +3186,6 @@ var AdminPanelUIModule = class {
|
|
|
2983
3186
|
this.keyRotateStatus = "success";
|
|
2984
3187
|
this.log("Key rotated:", data.message);
|
|
2985
3188
|
this.config.onBroadcast?.("Stream key rotated - update OBS settings");
|
|
2986
|
-
setTimeout(() => {
|
|
2987
|
-
this.keyRotateStatus = "";
|
|
2988
|
-
}, 3e3);
|
|
2989
3189
|
} else {
|
|
2990
3190
|
this.keyRotateStatus = "error";
|
|
2991
3191
|
this.log("Key rotation failed:", data.error);
|
|
@@ -3045,9 +3245,6 @@ var AdminPanelUIModule = class {
|
|
|
3045
3245
|
this.sceneAdmins = [...this.sceneAdmins, normalized];
|
|
3046
3246
|
this.newAdminWallet = "";
|
|
3047
3247
|
this.modStatus = "saved";
|
|
3048
|
-
setTimeout(() => {
|
|
3049
|
-
this.modStatus = "";
|
|
3050
|
-
}, 2e3);
|
|
3051
3248
|
this.log("Added scene admin:", normalized);
|
|
3052
3249
|
} else {
|
|
3053
3250
|
this.modStatus = "error";
|
|
@@ -3075,9 +3272,6 @@ var AdminPanelUIModule = class {
|
|
|
3075
3272
|
if (res.ok) {
|
|
3076
3273
|
this.sceneAdmins = this.sceneAdmins.filter((w) => w !== wallet);
|
|
3077
3274
|
this.modStatus = "saved";
|
|
3078
|
-
setTimeout(() => {
|
|
3079
|
-
this.modStatus = "";
|
|
3080
|
-
}, 2e3);
|
|
3081
3275
|
this.log("Removed scene admin:", wallet);
|
|
3082
3276
|
} else {
|
|
3083
3277
|
this.modStatus = "error";
|
|
@@ -3112,9 +3306,6 @@ var AdminPanelUIModule = class {
|
|
|
3112
3306
|
this.bannedWallets = [...this.bannedWallets, normalized];
|
|
3113
3307
|
this.newBanWallet = "";
|
|
3114
3308
|
this.modStatus = "saved";
|
|
3115
|
-
setTimeout(() => {
|
|
3116
|
-
this.modStatus = "";
|
|
3117
|
-
}, 2e3);
|
|
3118
3309
|
this.log("Banned wallet:", normalized);
|
|
3119
3310
|
this.config.onCommand?.("kickBanned", { wallet: normalized });
|
|
3120
3311
|
} else {
|
|
@@ -3143,9 +3334,6 @@ var AdminPanelUIModule = class {
|
|
|
3143
3334
|
if (res.ok) {
|
|
3144
3335
|
this.bannedWallets = this.bannedWallets.filter((w) => w !== wallet);
|
|
3145
3336
|
this.modStatus = "saved";
|
|
3146
|
-
setTimeout(() => {
|
|
3147
|
-
this.modStatus = "";
|
|
3148
|
-
}, 2e3);
|
|
3149
3337
|
this.log("Unbanned wallet:", wallet);
|
|
3150
3338
|
} else {
|
|
3151
3339
|
this.modStatus = "error";
|
|
@@ -3179,7 +3367,89 @@ var AdminPanelUIModule = class {
|
|
|
3179
3367
|
}
|
|
3180
3368
|
};
|
|
3181
3369
|
|
|
3370
|
+
// src/ui/ui-renderer.tsx
|
|
3371
|
+
import ReactEcs5, { ReactEcsRenderer, UiEntity as UiEntity5, Label as Label5 } from "@dcl/sdk/react-ecs";
|
|
3372
|
+
import { Color4 as Color46 } from "@dcl/sdk/math";
|
|
3373
|
+
import { engine as engine2 } from "@dcl/sdk/ecs";
|
|
3374
|
+
var notificationText = "";
|
|
3375
|
+
var notificationVisible = false;
|
|
3376
|
+
var notificationEndTime = 0;
|
|
3377
|
+
var notificationInitialized = false;
|
|
3378
|
+
function showNotification(message, durationMs = 5e3) {
|
|
3379
|
+
notificationText = message;
|
|
3380
|
+
notificationVisible = true;
|
|
3381
|
+
notificationEndTime = Date.now() + durationMs;
|
|
3382
|
+
}
|
|
3383
|
+
function initNotificationSystem() {
|
|
3384
|
+
if (notificationInitialized) return;
|
|
3385
|
+
notificationInitialized = true;
|
|
3386
|
+
engine2.addSystem(() => {
|
|
3387
|
+
if (notificationVisible && Date.now() > notificationEndTime) {
|
|
3388
|
+
notificationVisible = false;
|
|
3389
|
+
}
|
|
3390
|
+
});
|
|
3391
|
+
}
|
|
3392
|
+
function NotificationBanner() {
|
|
3393
|
+
if (!notificationVisible) return null;
|
|
3394
|
+
return /* @__PURE__ */ ReactEcs5.createElement(
|
|
3395
|
+
UiEntity5,
|
|
3396
|
+
{
|
|
3397
|
+
uiTransform: {
|
|
3398
|
+
positionType: "absolute",
|
|
3399
|
+
position: { top: 80 },
|
|
3400
|
+
width: 500,
|
|
3401
|
+
height: 60,
|
|
3402
|
+
padding: 16,
|
|
3403
|
+
alignSelf: "center",
|
|
3404
|
+
justifyContent: "center",
|
|
3405
|
+
alignItems: "center"
|
|
3406
|
+
},
|
|
3407
|
+
uiBackground: { color: Color46.create(0.1, 0.1, 0.15, 0.95) }
|
|
3408
|
+
},
|
|
3409
|
+
/* @__PURE__ */ ReactEcs5.createElement(
|
|
3410
|
+
Label5,
|
|
3411
|
+
{
|
|
3412
|
+
value: notificationText,
|
|
3413
|
+
fontSize: 18,
|
|
3414
|
+
color: Color46.create(0, 1, 1, 1),
|
|
3415
|
+
textAlign: "middle-center"
|
|
3416
|
+
}
|
|
3417
|
+
)
|
|
3418
|
+
);
|
|
3419
|
+
}
|
|
3420
|
+
function createStaticUI(client) {
|
|
3421
|
+
initNotificationSystem();
|
|
3422
|
+
return function StaticUI() {
|
|
3423
|
+
const currentScale = client.uiScale;
|
|
3424
|
+
const guideComponent = client.guideUI?.getComponent() ?? null;
|
|
3425
|
+
const chatComponent = client.chatUI?.getComponent() ?? null;
|
|
3426
|
+
const adminComponent = client.adminPanel?.getComponent() ?? null;
|
|
3427
|
+
return /* @__PURE__ */ ReactEcs5.createElement(
|
|
3428
|
+
UiEntity5,
|
|
3429
|
+
{
|
|
3430
|
+
key: `static-ui-root-${currentScale}`,
|
|
3431
|
+
uiTransform: {
|
|
3432
|
+
width: "100%",
|
|
3433
|
+
height: "100%",
|
|
3434
|
+
positionType: "absolute",
|
|
3435
|
+
flexDirection: "column",
|
|
3436
|
+
alignItems: "center"
|
|
3437
|
+
}
|
|
3438
|
+
},
|
|
3439
|
+
/* @__PURE__ */ ReactEcs5.createElement(NotificationBanner, null),
|
|
3440
|
+
guideComponent,
|
|
3441
|
+
chatComponent,
|
|
3442
|
+
adminComponent
|
|
3443
|
+
);
|
|
3444
|
+
};
|
|
3445
|
+
}
|
|
3446
|
+
function setupStaticUI(client) {
|
|
3447
|
+
const StaticUI = createStaticUI(client);
|
|
3448
|
+
ReactEcsRenderer.setUiRenderer(StaticUI);
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3182
3451
|
// src/StaticTVClient.ts
|
|
3452
|
+
import { VideoPlayer, Material } from "@dcl/sdk/ecs";
|
|
3183
3453
|
var DEFAULT_BASE_URL = "https://thestatic.tv/api/v1/dcl";
|
|
3184
3454
|
var KEY_TYPE_CHANNEL = "channel";
|
|
3185
3455
|
var KEY_TYPE_SCENE = "scene";
|
|
@@ -3189,38 +3459,59 @@ var StaticTVClient = class {
|
|
|
3189
3459
|
*
|
|
3190
3460
|
* @param config Configuration options
|
|
3191
3461
|
*
|
|
3192
|
-
* @example
|
|
3462
|
+
* @example Free tier (session tracking only):
|
|
3193
3463
|
* ```typescript
|
|
3194
|
-
*
|
|
3464
|
+
* const staticTV = new StaticTVClient({
|
|
3465
|
+
* apiKey: 'dcls_your_key_here'
|
|
3466
|
+
* })
|
|
3467
|
+
* ```
|
|
3468
|
+
*
|
|
3469
|
+
* @example Standard/Pro with video screen (SDK handles playback):
|
|
3470
|
+
* ```typescript
|
|
3471
|
+
* // Create your video screen
|
|
3472
|
+
* const screen = engine.addEntity()
|
|
3473
|
+
* Transform.create(screen, { position: Vector3.create(8, 3, 14), scale: Vector3.create(8, 4.5, 0.1) })
|
|
3474
|
+
* MeshRenderer.setPlane(screen)
|
|
3475
|
+
*
|
|
3476
|
+
* // SDK handles everything - Guide selection + Pro admin controls just work
|
|
3477
|
+
* const staticTV = new StaticTVClient({
|
|
3478
|
+
* apiKey: 'dcls_your_key_here',
|
|
3479
|
+
* videoScreen: screen // That's it! SDK manages video playback
|
|
3480
|
+
* })
|
|
3195
3481
|
*
|
|
3196
3482
|
* export function main() {
|
|
3197
|
-
*
|
|
3198
|
-
* staticTV = new StaticTVClient({
|
|
3199
|
-
* apiKey: 'dcls_your_key_here'
|
|
3200
|
-
* })
|
|
3201
|
-
* // Session tracking starts automatically!
|
|
3483
|
+
* staticTV.setupUI()
|
|
3202
3484
|
* }
|
|
3203
3485
|
* ```
|
|
3204
3486
|
*/
|
|
3205
3487
|
constructor(config) {
|
|
3206
3488
|
this._keyType = null;
|
|
3489
|
+
this._keyId = null;
|
|
3207
3490
|
this._disabled = false;
|
|
3208
|
-
this.
|
|
3491
|
+
this._tier = "free";
|
|
3492
|
+
this._standardFeaturesEnabled = false;
|
|
3209
3493
|
this._proFeaturesEnabled = false;
|
|
3210
|
-
|
|
3494
|
+
this._pendingProConfig = null;
|
|
3495
|
+
/** Guide module - fetch channel lineup (standard/pro tier) */
|
|
3211
3496
|
this.guide = null;
|
|
3212
|
-
/** Session module - track visitor sessions (all
|
|
3497
|
+
/** Session module - track visitor sessions (all tiers, null when disabled) */
|
|
3213
3498
|
this.session = null;
|
|
3214
|
-
/** Heartbeat module - track video watching (
|
|
3499
|
+
/** Heartbeat module - track video watching (standard/pro tier) */
|
|
3215
3500
|
this.heartbeat = null;
|
|
3216
|
-
/** Interactions module - like/follow channels (
|
|
3501
|
+
/** Interactions module - like/follow channels (standard/pro tier) */
|
|
3217
3502
|
this.interactions = null;
|
|
3218
|
-
/** Guide UI module - channel browser UI (
|
|
3503
|
+
/** Guide UI module - channel browser UI (standard/pro tier) */
|
|
3219
3504
|
this.guideUI = null;
|
|
3220
|
-
/** Chat UI module - real-time chat UI (
|
|
3505
|
+
/** Chat UI module - real-time chat UI (standard/pro tier) */
|
|
3221
3506
|
this.chatUI = null;
|
|
3222
|
-
/** Admin Panel module - Video/Mod tabs (pro
|
|
3507
|
+
/** Admin Panel module - Video/Mod tabs (pro tier only) */
|
|
3223
3508
|
this.adminPanel = null;
|
|
3509
|
+
/** UI scale - fixed at 1.0. DCL's UI system auto-scales based on viewport. */
|
|
3510
|
+
this.uiScale = 1;
|
|
3511
|
+
// =============================================================================
|
|
3512
|
+
// --- VIDEO PLAYBACK (Internal handler for videoScreen) ---
|
|
3513
|
+
// =============================================================================
|
|
3514
|
+
this._currentVideoUrl = "";
|
|
3224
3515
|
this.config = {
|
|
3225
3516
|
autoStartSession: true,
|
|
3226
3517
|
sessionHeartbeatInterval: 3e4,
|
|
@@ -3230,7 +3521,6 @@ var StaticTVClient = class {
|
|
|
3230
3521
|
};
|
|
3231
3522
|
this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
|
|
3232
3523
|
if (!config.apiKey) {
|
|
3233
|
-
console.log("[StaticTV] No apiKey provided - tracking disabled. Scene will load normally.");
|
|
3234
3524
|
this._disabled = true;
|
|
3235
3525
|
this._keyType = null;
|
|
3236
3526
|
this.session = null;
|
|
@@ -3246,14 +3536,15 @@ var StaticTVClient = class {
|
|
|
3246
3536
|
} else if (config.apiKey.startsWith("dcls_")) {
|
|
3247
3537
|
this._keyType = KEY_TYPE_SCENE;
|
|
3248
3538
|
} else {
|
|
3249
|
-
console.
|
|
3539
|
+
console.warn("[TheStatic] Invalid API key format - get your key at thestatic.tv/dashboard");
|
|
3250
3540
|
this._disabled = true;
|
|
3251
3541
|
this._keyType = null;
|
|
3252
3542
|
return;
|
|
3253
3543
|
}
|
|
3254
3544
|
this.session = new SessionModule(this);
|
|
3255
3545
|
if (this._keyType === KEY_TYPE_CHANNEL) {
|
|
3256
|
-
this.
|
|
3546
|
+
this._tier = "standard";
|
|
3547
|
+
this._initStandardModules();
|
|
3257
3548
|
}
|
|
3258
3549
|
if (this.config.autoStartSession) {
|
|
3259
3550
|
fetchUserData().then(() => {
|
|
@@ -3269,6 +3560,10 @@ var StaticTVClient = class {
|
|
|
3269
3560
|
}
|
|
3270
3561
|
this.log(`StaticTVClient initialized (${this._keyType} mode)`);
|
|
3271
3562
|
}
|
|
3563
|
+
/** Get the API base URL (for internal module use) */
|
|
3564
|
+
getBaseUrl() {
|
|
3565
|
+
return this.baseUrl;
|
|
3566
|
+
}
|
|
3272
3567
|
/**
|
|
3273
3568
|
* Get the key type (channel, scene, or null if disabled)
|
|
3274
3569
|
*/
|
|
@@ -3282,11 +3577,23 @@ var StaticTVClient = class {
|
|
|
3282
3577
|
return this._disabled;
|
|
3283
3578
|
}
|
|
3284
3579
|
/**
|
|
3285
|
-
*
|
|
3286
|
-
|
|
3580
|
+
* Get the current SDK tier (free, standard, or pro)
|
|
3581
|
+
*/
|
|
3582
|
+
get tier() {
|
|
3583
|
+
return this._tier;
|
|
3584
|
+
}
|
|
3585
|
+
/**
|
|
3586
|
+
* Check if this is a free tier client (session tracking only)
|
|
3587
|
+
* Returns true until session confirms a higher tier
|
|
3588
|
+
*/
|
|
3589
|
+
get isFree() {
|
|
3590
|
+
return this._tier === "free";
|
|
3591
|
+
}
|
|
3592
|
+
/**
|
|
3593
|
+
* @deprecated Use `isFree` instead. Kept for backward compatibility.
|
|
3287
3594
|
*/
|
|
3288
3595
|
get isLite() {
|
|
3289
|
-
return
|
|
3596
|
+
return this.isFree;
|
|
3290
3597
|
}
|
|
3291
3598
|
/**
|
|
3292
3599
|
* Make an authenticated API request
|
|
@@ -3304,14 +3611,29 @@ var StaticTVClient = class {
|
|
|
3304
3611
|
});
|
|
3305
3612
|
}
|
|
3306
3613
|
/**
|
|
3307
|
-
* Log a message
|
|
3614
|
+
* Log a debug message (only when debug: true)
|
|
3308
3615
|
* @internal
|
|
3309
3616
|
*/
|
|
3310
3617
|
log(message, ...args) {
|
|
3311
3618
|
if (this.config.debug) {
|
|
3312
|
-
console.log(`[
|
|
3619
|
+
console.log(`[TheStatic] ${message}`, ...args);
|
|
3313
3620
|
}
|
|
3314
3621
|
}
|
|
3622
|
+
/**
|
|
3623
|
+
* Log a warning (always shown)
|
|
3624
|
+
* @internal
|
|
3625
|
+
*/
|
|
3626
|
+
warn(message) {
|
|
3627
|
+
console.warn(`[TheStatic] ${message}`);
|
|
3628
|
+
}
|
|
3629
|
+
/**
|
|
3630
|
+
* Log an error (always shown, user-friendly format)
|
|
3631
|
+
* @internal
|
|
3632
|
+
*/
|
|
3633
|
+
error(message, err) {
|
|
3634
|
+
const errorDetail = err instanceof Error ? err.message : String(err || "");
|
|
3635
|
+
console.error(`[TheStatic] ${message}${errorDetail ? `: ${errorDetail}` : ""}`);
|
|
3636
|
+
}
|
|
3315
3637
|
/**
|
|
3316
3638
|
* Get the current configuration
|
|
3317
3639
|
* @internal
|
|
@@ -3320,35 +3642,179 @@ var StaticTVClient = class {
|
|
|
3320
3642
|
return this.config;
|
|
3321
3643
|
}
|
|
3322
3644
|
/**
|
|
3323
|
-
*
|
|
3645
|
+
* Play a video on the configured videoScreen entity.
|
|
3646
|
+
* Called by Guide UI and Admin Panel.
|
|
3647
|
+
*
|
|
3648
|
+
* @param url Video URL to play
|
|
3649
|
+
*/
|
|
3650
|
+
playVideo(url) {
|
|
3651
|
+
const screen = this.config.videoScreen;
|
|
3652
|
+
if (screen !== void 0) {
|
|
3653
|
+
this.log(`Playing video: ${url}`);
|
|
3654
|
+
this._currentVideoUrl = url;
|
|
3655
|
+
if (VideoPlayer.has(screen)) {
|
|
3656
|
+
VideoPlayer.deleteFrom(screen);
|
|
3657
|
+
}
|
|
3658
|
+
VideoPlayer.create(screen, {
|
|
3659
|
+
src: url,
|
|
3660
|
+
playing: true,
|
|
3661
|
+
volume: 1
|
|
3662
|
+
});
|
|
3663
|
+
Material.setBasicMaterial(screen, {
|
|
3664
|
+
texture: Material.Texture.Video({ videoPlayerEntity: screen })
|
|
3665
|
+
});
|
|
3666
|
+
if (this.guideUI) {
|
|
3667
|
+
const videos = this.guideUI.getVideos();
|
|
3668
|
+
const video = videos.find((v) => v.src === url);
|
|
3669
|
+
if (video) {
|
|
3670
|
+
this.guideUI.currentVideoId = video.id;
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
if (this.config.onVideoPlay) {
|
|
3675
|
+
this.config.onVideoPlay(url);
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
/**
|
|
3679
|
+
* Stop video playback on the configured videoScreen entity.
|
|
3680
|
+
* Called by Admin Panel.
|
|
3681
|
+
*/
|
|
3682
|
+
stopVideo() {
|
|
3683
|
+
const screen = this.config.videoScreen;
|
|
3684
|
+
if (screen !== void 0 && VideoPlayer.has(screen)) {
|
|
3685
|
+
this.log("Stopping video");
|
|
3686
|
+
VideoPlayer.getMutable(screen).playing = false;
|
|
3687
|
+
this._currentVideoUrl = "";
|
|
3688
|
+
if (this.guideUI) {
|
|
3689
|
+
this.guideUI.currentVideoId = null;
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
if (this.config.onVideoStop) {
|
|
3693
|
+
this.config.onVideoStop();
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
/**
|
|
3697
|
+
* Get the currently playing video URL
|
|
3698
|
+
*/
|
|
3699
|
+
get currentVideoUrl() {
|
|
3700
|
+
return this._currentVideoUrl;
|
|
3701
|
+
}
|
|
3702
|
+
/**
|
|
3703
|
+
* Internal handler for Guide video selection
|
|
3324
3704
|
* @internal
|
|
3325
3705
|
*/
|
|
3326
|
-
|
|
3327
|
-
if (
|
|
3706
|
+
_handleGuideVideoSelect(video) {
|
|
3707
|
+
if (video.src) {
|
|
3708
|
+
this.playVideo(video.src);
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
/**
|
|
3712
|
+
* Internal handler for broadcast messages
|
|
3713
|
+
* @internal
|
|
3714
|
+
*/
|
|
3715
|
+
_handleBroadcast(text) {
|
|
3716
|
+
if (this.config.onBroadcast) {
|
|
3717
|
+
this.config.onBroadcast(text);
|
|
3718
|
+
} else {
|
|
3719
|
+
this.showNotification(text);
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
/**
|
|
3723
|
+
* Initialize standard feature modules (guide, heartbeat, interactions, UI)
|
|
3724
|
+
* @internal
|
|
3725
|
+
*/
|
|
3726
|
+
_initStandardModules() {
|
|
3727
|
+
if (this._standardFeaturesEnabled) return;
|
|
3328
3728
|
this.guide = new GuideModule(this);
|
|
3329
3729
|
this.heartbeat = new HeartbeatModule(this);
|
|
3330
3730
|
this.interactions = new InteractionsModule(this);
|
|
3331
|
-
|
|
3731
|
+
const guideConfig = {
|
|
3732
|
+
...this.config.guideUI,
|
|
3733
|
+
onVideoSelect: (video) => {
|
|
3734
|
+
if (this.config.videoScreen !== void 0 || this.config.onVideoPlay) {
|
|
3735
|
+
this._handleGuideVideoSelect(video);
|
|
3736
|
+
}
|
|
3737
|
+
if (this.config.guideUI?.onVideoSelect) {
|
|
3738
|
+
this.config.guideUI.onVideoSelect(video);
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3741
|
+
};
|
|
3742
|
+
this.guideUI = new GuideUIModule(this, guideConfig);
|
|
3332
3743
|
this.chatUI = new ChatUIModule(this, this.config.chatUI);
|
|
3333
|
-
this.
|
|
3744
|
+
this._standardFeaturesEnabled = true;
|
|
3334
3745
|
this.chatUI.init().catch((err) => {
|
|
3335
3746
|
this.log(`Chat init failed: ${err}`);
|
|
3336
3747
|
});
|
|
3337
|
-
this.log("
|
|
3748
|
+
this.log("Standard features enabled (guide, chat, heartbeat, interactions)");
|
|
3749
|
+
}
|
|
3750
|
+
/**
|
|
3751
|
+
* Initialize pro feature modules (admin panel)
|
|
3752
|
+
* @internal
|
|
3753
|
+
*/
|
|
3754
|
+
_initProModules() {
|
|
3755
|
+
if (this._proFeaturesEnabled) return;
|
|
3756
|
+
const sceneId = this._pendingProConfig?.sceneId || this.config.sceneId || this._keyId || void 0;
|
|
3757
|
+
if (!sceneId) {
|
|
3758
|
+
this.log("Pro features: No sceneId and no keyId available - admin panel disabled");
|
|
3759
|
+
return;
|
|
3760
|
+
}
|
|
3761
|
+
const adminConfig = {
|
|
3762
|
+
sceneId,
|
|
3763
|
+
// Use internal handlers that manage videoScreen + call user callbacks
|
|
3764
|
+
onVideoPlay: (url) => this.playVideo(url),
|
|
3765
|
+
onVideoStop: () => this.stopVideo(),
|
|
3766
|
+
onBroadcast: (text) => this._handleBroadcast(text),
|
|
3767
|
+
onCommand: this.config.onCommand || this._pendingProConfig?.onCommand,
|
|
3768
|
+
// Merge other settings from enableProFeatures if provided
|
|
3769
|
+
...this._pendingProConfig
|
|
3770
|
+
};
|
|
3771
|
+
this.adminPanel = new AdminPanelUIModule(this, adminConfig);
|
|
3772
|
+
this._proFeaturesEnabled = true;
|
|
3773
|
+
this.adminPanel.init().catch((err) => {
|
|
3774
|
+
this.log(`Admin panel init failed: ${err}`);
|
|
3775
|
+
});
|
|
3776
|
+
this.log(`Pro features enabled (admin panel) - sceneId: ${sceneId}`);
|
|
3777
|
+
}
|
|
3778
|
+
/**
|
|
3779
|
+
* Called by SessionModule when server returns the tier
|
|
3780
|
+
* Enables modules based on tier level
|
|
3781
|
+
* @internal
|
|
3782
|
+
*/
|
|
3783
|
+
_enableFeaturesForTier(tier, keyId) {
|
|
3784
|
+
this._tier = tier;
|
|
3785
|
+
if (keyId) {
|
|
3786
|
+
this._keyId = keyId;
|
|
3787
|
+
}
|
|
3788
|
+
if (tier === "standard" || tier === "pro") {
|
|
3789
|
+
this._initStandardModules();
|
|
3790
|
+
}
|
|
3791
|
+
if (tier === "pro") {
|
|
3792
|
+
const hasVideoConfig = this.config.videoScreen !== void 0 || this.config.onVideoPlay !== void 0 || this.config.sceneId !== void 0 || this._pendingProConfig !== null;
|
|
3793
|
+
if (hasVideoConfig) {
|
|
3794
|
+
this._initProModules();
|
|
3795
|
+
} else {
|
|
3796
|
+
this.log("Pro tier detected but no video config - call enableProFeatures() or set videoScreen to enable admin panel");
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3338
3799
|
}
|
|
3339
3800
|
/**
|
|
3340
|
-
*
|
|
3341
|
-
* Enables guide, chat, heartbeat, and interactions modules
|
|
3801
|
+
* @deprecated Use `_enableFeaturesForTier` instead
|
|
3342
3802
|
* @internal
|
|
3343
3803
|
*/
|
|
3344
3804
|
_enableFullFeatures() {
|
|
3345
|
-
this.
|
|
3805
|
+
this._enableFeaturesForTier("standard");
|
|
3346
3806
|
}
|
|
3347
3807
|
/**
|
|
3348
|
-
* Check if
|
|
3808
|
+
* Check if standard features are enabled (guide, chat, etc.)
|
|
3809
|
+
*/
|
|
3810
|
+
get hasStandardFeatures() {
|
|
3811
|
+
return this._standardFeaturesEnabled;
|
|
3812
|
+
}
|
|
3813
|
+
/**
|
|
3814
|
+
* @deprecated Use `hasStandardFeatures` instead
|
|
3349
3815
|
*/
|
|
3350
3816
|
get hasFullFeatures() {
|
|
3351
|
-
return this.
|
|
3817
|
+
return this._standardFeaturesEnabled;
|
|
3352
3818
|
}
|
|
3353
3819
|
/**
|
|
3354
3820
|
* Check if pro features are enabled (admin panel)
|
|
@@ -3357,64 +3823,121 @@ var StaticTVClient = class {
|
|
|
3357
3823
|
return this._proFeaturesEnabled;
|
|
3358
3824
|
}
|
|
3359
3825
|
/**
|
|
3360
|
-
*
|
|
3826
|
+
* @deprecated Use `tier` instead
|
|
3361
3827
|
*/
|
|
3362
3828
|
get sdkType() {
|
|
3363
|
-
return this.
|
|
3829
|
+
return this._tier === "free" ? "lite" : "full";
|
|
3364
3830
|
}
|
|
3365
3831
|
/**
|
|
3366
|
-
*
|
|
3367
|
-
*
|
|
3832
|
+
* Configure Pro features (Admin Panel with Video + Mod tabs)
|
|
3833
|
+
*
|
|
3834
|
+
* **OPTIONAL**: If you set `videoScreen` in the constructor, you don't need
|
|
3835
|
+
* to call this method - the Admin Panel will auto-enable with sensible defaults.
|
|
3836
|
+
*
|
|
3837
|
+
* Use this method only if you need:
|
|
3838
|
+
* - Custom scene tab UI
|
|
3839
|
+
* - Custom panel title/styling
|
|
3840
|
+
* - Advanced command handlers
|
|
3368
3841
|
*
|
|
3369
3842
|
* @param config Admin panel configuration
|
|
3370
3843
|
*
|
|
3371
|
-
* @example
|
|
3844
|
+
* @example Using videoScreen (recommended - no enableProFeatures needed):
|
|
3372
3845
|
* ```typescript
|
|
3373
|
-
* const staticTV = new StaticTVClient({
|
|
3846
|
+
* const staticTV = new StaticTVClient({
|
|
3847
|
+
* apiKey: 'dcls_...',
|
|
3848
|
+
* videoScreen: myScreen // Admin Panel auto-enabled for Pro tier!
|
|
3849
|
+
* })
|
|
3850
|
+
* ```
|
|
3374
3851
|
*
|
|
3375
|
-
*
|
|
3852
|
+
* @example Advanced: Custom scene tabs
|
|
3853
|
+
* ```typescript
|
|
3376
3854
|
* staticTV.enableProFeatures({
|
|
3377
|
-
* sceneId: 'my-scene',
|
|
3378
3855
|
* title: 'MY SCENE ADMIN',
|
|
3379
|
-
*
|
|
3380
|
-
* onVideoStop: () => videoPlayer.stop(),
|
|
3381
|
-
* onBroadcast: (text) => showNotification(text)
|
|
3856
|
+
* sceneTabs: [{ label: 'LIGHTS', id: 'lights', render: () => <LightsTab /> }]
|
|
3382
3857
|
* })
|
|
3383
3858
|
* ```
|
|
3384
3859
|
*/
|
|
3385
|
-
enableProFeatures(config) {
|
|
3860
|
+
enableProFeatures(config = {}) {
|
|
3386
3861
|
if (this._proFeaturesEnabled) {
|
|
3387
3862
|
this.log("Pro features already enabled");
|
|
3388
3863
|
return;
|
|
3389
3864
|
}
|
|
3390
|
-
this.
|
|
3391
|
-
this.
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3865
|
+
this._pendingProConfig = config;
|
|
3866
|
+
if (this._tier === "pro") {
|
|
3867
|
+
this._initProModules();
|
|
3868
|
+
} else {
|
|
3869
|
+
this.log("Pro features configured - will enable when Pro tier is confirmed");
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
/**
|
|
3873
|
+
* Register a custom scene tab for the admin panel (Pro tier)
|
|
3874
|
+
* Must call enableProFeatures() first.
|
|
3875
|
+
*
|
|
3876
|
+
* @param tab The tab definition with label, id, and render function
|
|
3877
|
+
*
|
|
3878
|
+
* @example
|
|
3879
|
+
* ```typescript
|
|
3880
|
+
* staticTV.registerSceneTab({
|
|
3881
|
+
* label: 'LIGHTS',
|
|
3882
|
+
* id: 'lights',
|
|
3883
|
+
* render: () => <MyLightsControls />
|
|
3884
|
+
* })
|
|
3885
|
+
* ```
|
|
3886
|
+
*/
|
|
3887
|
+
registerSceneTab(tab) {
|
|
3888
|
+
if (!this.adminPanel) {
|
|
3889
|
+
this.log("Cannot register scene tab - call enableProFeatures() first");
|
|
3890
|
+
return;
|
|
3891
|
+
}
|
|
3892
|
+
this.adminPanel.registerSceneTab(tab);
|
|
3396
3893
|
}
|
|
3397
3894
|
/**
|
|
3398
|
-
*
|
|
3399
|
-
*
|
|
3895
|
+
* Close Admin/Guide panels (they share the same screen space)
|
|
3896
|
+
* Chat is independent and stays open.
|
|
3897
|
+
* @param except The panel that should stay open: 'admin' | 'guide'
|
|
3898
|
+
*/
|
|
3899
|
+
closeOtherPanels(except) {
|
|
3900
|
+
if (except !== "guide" && this.guideUI?.isVisible) {
|
|
3901
|
+
this.guideUI.hide();
|
|
3902
|
+
}
|
|
3903
|
+
if (except !== "admin" && this.adminPanel?.isOpen) {
|
|
3904
|
+
this.adminPanel.hide();
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
/**
|
|
3908
|
+
* Set up the UI renderer for all SDK panels
|
|
3909
|
+
* Call this in your scene's main() function to render Guide, Chat, Admin panels.
|
|
3910
|
+
* No need to create your own ui.tsx - the SDK handles everything.
|
|
3911
|
+
*
|
|
3912
|
+
* @example
|
|
3913
|
+
* ```typescript
|
|
3914
|
+
* const staticTV = new StaticTVClient({ apiKey: 'dcls_...' })
|
|
3915
|
+
*
|
|
3916
|
+
* export function main() {
|
|
3917
|
+
* staticTV.setupUI()
|
|
3918
|
+
* // That's it! All panels will render automatically
|
|
3919
|
+
* }
|
|
3920
|
+
* ```
|
|
3921
|
+
*/
|
|
3922
|
+
setupUI() {
|
|
3923
|
+
setupStaticUI(this);
|
|
3924
|
+
this.log("UI renderer initialized");
|
|
3925
|
+
}
|
|
3926
|
+
/**
|
|
3927
|
+
* Show a notification message on screen
|
|
3928
|
+
* Works with both SDK-rendered UI and custom UI setups.
|
|
3400
3929
|
*
|
|
3401
|
-
* @param
|
|
3930
|
+
* @param message The message to display
|
|
3931
|
+
* @param durationMs How long to show (default 5000ms)
|
|
3402
3932
|
*
|
|
3403
3933
|
* @example
|
|
3404
3934
|
* ```typescript
|
|
3405
|
-
* staticTV.
|
|
3406
|
-
*
|
|
3407
|
-
* id: 'lights',
|
|
3408
|
-
* render: () => <MyLightsControls />
|
|
3409
|
-
* })
|
|
3935
|
+
* staticTV.showNotification('Stream started!')
|
|
3936
|
+
* staticTV.showNotification('Custom message', 10000) // 10 seconds
|
|
3410
3937
|
* ```
|
|
3411
3938
|
*/
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
this.log("Cannot register scene tab - call enableProFeatures() first");
|
|
3415
|
-
return;
|
|
3416
|
-
}
|
|
3417
|
-
this.adminPanel.registerSceneTab(tab);
|
|
3939
|
+
showNotification(message, durationMs = 5e3) {
|
|
3940
|
+
showNotification(message, durationMs);
|
|
3418
3941
|
}
|
|
3419
3942
|
/**
|
|
3420
3943
|
* Cleanup when done (call before scene unload)
|
|
@@ -3449,5 +3972,7 @@ export {
|
|
|
3449
3972
|
StaticTVClient,
|
|
3450
3973
|
fetchUserData,
|
|
3451
3974
|
getPlayerDisplayName,
|
|
3452
|
-
getPlayerWallet
|
|
3975
|
+
getPlayerWallet,
|
|
3976
|
+
setupStaticUI,
|
|
3977
|
+
showNotification
|
|
3453
3978
|
};
|