mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 23:16:21 +00:00
ESLint --fix: auto-fix brace-style, indent, semi, no-trailing-spaces
Run eslint --fix across src/ to clear ~1900 mechanical lint errors surfaced by the @typescript-eslint v8 + react-hooks v7 + react-compiler upgrade in the React 19 modernization PR. Issues fixed automatically: - brace-style (Allman): try/catch one-liners reformatted to multi-line - indent: tab-vs-space and depth corrections - semi: missing trailing semicolons - no-trailing-spaces No semantic changes. Remaining 701 errors are real-code issues (set-state-in-effect, rules-of-hooks, no-unsafe-* type checks) that need manual per-file review. https://claude.ai/code/session_01GrR87LAqnAEyKG2ZbmQt5Q
This commit is contained in:
@@ -203,7 +203,10 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
|
||||
<ToolbarMeView setMeExpanded={ setMeExpanded } unseenAchievementCount={ getTotalUnseen } useGuideTool={ useGuideTool } />
|
||||
</motion.div> }
|
||||
</AnimatePresence>
|
||||
<motion.div whileHover={ { scale: 1.08 } } whileTap={ { scale: 0.95 } } className="cursor-pointer" onClick={ event => { setMeExpanded(value => !value); event.stopPropagation(); } }>
|
||||
<motion.div whileHover={ { scale: 1.08 } } whileTap={ { scale: 0.95 } } className="cursor-pointer" onClick={ event =>
|
||||
{
|
||||
setMeExpanded(value => !value); event.stopPropagation();
|
||||
} }>
|
||||
<LayoutAvatarImageView headOnly={ true } direction={ 2 } figure={ userFigure } className="tb-icon !h-[44px] !w-[32px] !bg-center !bg-no-repeat" style={ { marginTop: '4px' } } />
|
||||
</motion.div>
|
||||
{ (getTotalUnseen > 0) &&
|
||||
@@ -239,20 +242,20 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
|
||||
exit={ { opacity: 0, x: 10 } }
|
||||
transition={ { type: 'spring', stiffness: 300, damping: 28 } }
|
||||
className={ `fixed bottom-0 z-40 h-[52px] max-w-[calc(50vw-242px)] items-center overflow-visible pr-3 pointer-events-auto ${ desktopFlexClasses } ${ isInRoom ? 'right-0' : 'right-3' }` }>
|
||||
<motion.div variants={ containerVariants } initial="hidden" animate="visible" exit="exit" className="tb-open-shell flex h-[52px] max-w-full items-center gap-3 overflow-visible bg-transparent px-[8px] pt-[10px] pb-[2px]">
|
||||
<motion.div variants={ itemVariants } className="relative">
|
||||
<ToolbarItemView icon="friendall" onClick={ () => CreateLinkEvent('friends/toggle') } className="tb-icon" />
|
||||
{ (requests.length > 0) &&
|
||||
<motion.div variants={ containerVariants } initial="hidden" animate="visible" exit="exit" className="tb-open-shell flex h-[52px] max-w-full items-center gap-3 overflow-visible bg-transparent px-[8px] pt-[10px] pb-[2px]">
|
||||
<motion.div variants={ itemVariants } className="relative">
|
||||
<ToolbarItemView icon="friendall" onClick={ () => CreateLinkEvent('friends/toggle') } className="tb-icon" />
|
||||
{ (requests.length > 0) &&
|
||||
<LayoutItemCountView count={ requests.length } className="absolute -right-2 -top-1" /> }
|
||||
</motion.div>
|
||||
{ ((iconState === MessengerIconState.SHOW) || (iconState === MessengerIconState.UNREAD)) &&
|
||||
</motion.div>
|
||||
{ ((iconState === MessengerIconState.SHOW) || (iconState === MessengerIconState.UNREAD)) &&
|
||||
<motion.div variants={ itemVariants }>
|
||||
<ToolbarItemView className={ `tb-icon ${ iconState === MessengerIconState.UNREAD ? 'is-unseen animate-pulse' : '' }` } icon="message" onClick={ () => OpenMessengerChat() } />
|
||||
</motion.div> }
|
||||
<div className={ `mx-1 h-5 w-[1px] bg-white/20 ${ desktopBlockClasses }` } />
|
||||
<div className={ `h-full shrink-0 ${ desktopBlockClasses }` } id="toolbar-friend-bar-container-desktop" />
|
||||
</motion.div>
|
||||
<div className={ `mx-1 h-5 w-[1px] bg-white/20 ${ desktopBlockClasses }` } />
|
||||
<div className={ `h-full shrink-0 ${ desktopBlockClasses }` } id="toolbar-friend-bar-container-desktop" />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
key="mobile-nav"
|
||||
@@ -297,13 +300,16 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
|
||||
className="pointer-events-auto absolute bottom-[calc(100%+10px)] left-1/2 z-[70] -translate-x-1/2">
|
||||
<ToolbarMeView setMeExpanded={ setMeExpanded } unseenAchievementCount={ getTotalUnseen } useGuideTool={ useGuideTool } />
|
||||
</motion.div> }
|
||||
</AnimatePresence>
|
||||
<motion.div whileHover={ { scale: 1.08 } } whileTap={ { scale: 0.95 } } className="cursor-pointer" onClick={ event => { setMeExpanded(value => !value); event.stopPropagation(); } }>
|
||||
<LayoutAvatarImageView headOnly={ true } direction={ 2 } figure={ userFigure } className="tb-icon !h-[44px] !w-[32px] !bg-center !bg-no-repeat" style={ { marginTop: '4px' } } />
|
||||
</motion.div>
|
||||
{ (getTotalUnseen > 0) &&
|
||||
<LayoutItemCountView count={ getTotalUnseen } className="pointer-events-none absolute -right-1 -top-1 z-10" /> }
|
||||
</AnimatePresence>
|
||||
<motion.div whileHover={ { scale: 1.08 } } whileTap={ { scale: 0.95 } } className="cursor-pointer" onClick={ event =>
|
||||
{
|
||||
setMeExpanded(value => !value); event.stopPropagation();
|
||||
} }>
|
||||
<LayoutAvatarImageView headOnly={ true } direction={ 2 } figure={ userFigure } className="tb-icon !h-[44px] !w-[32px] !bg-center !bg-no-repeat" style={ { marginTop: '4px' } } />
|
||||
</motion.div>
|
||||
{ (getTotalUnseen > 0) &&
|
||||
<LayoutItemCountView count={ getTotalUnseen } className="pointer-events-none absolute -right-1 -top-1 z-10" /> }
|
||||
</motion.div>
|
||||
<motion.div variants={ containerVariants } initial="hidden" animate="visible" exit="exit" className="tb-bar-scroll flex h-full items-center gap-2 overflow-x-auto overflow-y-visible px-1">
|
||||
{ (isInRoom && showToolbarButton) &&
|
||||
<motion.div variants={ itemVariants }>
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
import { ControlYoutubeDisplayPlaybackMessageComposer, YouTubeRoomBroadcastEvent, YouTubeRoomPlayComposer, YouTubeRoomSettingsEvent, YouTubeRoomWatchersEvent, YouTubeRoomWatchingComposer } from "@nitrots/nitro-renderer";
|
||||
import { FC, useEffect, useRef, useState } from "react";
|
||||
import ReactPlayer from "react-player/youtube";
|
||||
import { GetRoomSession, getYoutubeRoomEnabled, GetSessionDataManager, LocalizeText, SendMessageComposer, YoutubeVideoPlaybackStateEnum } from "../../api";
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, LayoutAvatarImageView } from "../../common";
|
||||
import { useFurnitureYoutubeWidget, useMessageEvent } from "../../hooks";
|
||||
import { ControlYoutubeDisplayPlaybackMessageComposer, YouTubeRoomBroadcastEvent, YouTubeRoomPlayComposer, YouTubeRoomSettingsEvent, YouTubeRoomWatchersEvent, YouTubeRoomWatchingComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useRef, useState } from 'react';
|
||||
import ReactPlayer from 'react-player/youtube';
|
||||
import { GetRoomSession, getYoutubeRoomEnabled, GetSessionDataManager, LocalizeText, SendMessageComposer, YoutubeVideoPlaybackStateEnum } from '../../api';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, LayoutAvatarImageView } from '../../common';
|
||||
import { useFurnitureYoutubeWidget, useMessageEvent } from '../../hooks';
|
||||
|
||||
const CONTROL_COMMAND_PREVIOUS_VIDEO = 0;
|
||||
const CONTROL_COMMAND_NEXT_VIDEO = 1;
|
||||
const CONTROL_COMMAND_PAUSE_VIDEO = 2;
|
||||
const CONTROL_COMMAND_CONTINUE_VIDEO = 3;
|
||||
|
||||
const extractVideoId = (input: string): string => {
|
||||
const extractVideoId = (input: string): string =>
|
||||
{
|
||||
const patterns = [
|
||||
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/v\/|youtube\.com\/shorts\/)([a-zA-Z0-9_-]{11})/,
|
||||
/^([a-zA-Z0-9_-]{11})$/,
|
||||
];
|
||||
for (const pattern of patterns) {
|
||||
for (const pattern of patterns)
|
||||
{
|
||||
const match = input.match(pattern);
|
||||
if (match) return match[1];
|
||||
}
|
||||
return input;
|
||||
};
|
||||
|
||||
export const YouTubePlayerView: FC<{}> = () => {
|
||||
export const YouTubePlayerView: FC<{}> = () =>
|
||||
{
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [tab, setTab] = useState< | "player" | "playlist" | "spectators" | "settings" | "history" | "share" >("player");
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const [tab, setTab] = useState< | 'player' | 'playlist' | 'spectators' | 'settings' | 'history' | 'share' >('player');
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [isRoomMode, setIsRoomMode] = useState(false);
|
||||
const [volume, setVolume] = useState(100);
|
||||
const [isMuted, setIsMuted] = useState(false);
|
||||
@@ -38,121 +41,174 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
const playerRef = useRef<ReactPlayer | null>(null);
|
||||
const { objectId: youtubeObjectId, videoId: roomVideoId, currentVideoState, hasControl } = useFurnitureYoutubeWidget();
|
||||
const [spectators, setSpectators] = useState< { id: number; name: string; look: string }[] >([]);
|
||||
const [broadcastVideo, setBroadcastVideo] = useState("");
|
||||
const [broadcastSender, setBroadcastSender] = useState("");
|
||||
const [broadcastVideo, setBroadcastVideo] = useState('');
|
||||
const [broadcastSender, setBroadcastSender] = useState('');
|
||||
const [broadcastPlaylist, setBroadcastPlaylist] = useState<string[]>([]);
|
||||
const [watcherIds, setWatcherIds] = useState<Set<number>>(new Set());
|
||||
const [youtubeEnabled, setYoutubeEnabled] = useState(getYoutubeRoomEnabled());
|
||||
|
||||
useMessageEvent<YouTubeRoomSettingsEvent>(YouTubeRoomSettingsEvent, event => {
|
||||
useMessageEvent<YouTubeRoomSettingsEvent>(YouTubeRoomSettingsEvent, event =>
|
||||
{
|
||||
setYoutubeEnabled(event.getParser().youtubeEnabled);
|
||||
});
|
||||
useMessageEvent<YouTubeRoomBroadcastEvent>(YouTubeRoomBroadcastEvent, event => {
|
||||
useMessageEvent<YouTubeRoomBroadcastEvent>(YouTubeRoomBroadcastEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
setBroadcastVideo(parser.videoId);
|
||||
setBroadcastSender(parser.senderName);
|
||||
setBroadcastPlaylist(parser.playlist);
|
||||
if (parser.videoId) {
|
||||
if (parser.videoId)
|
||||
{
|
||||
setInputValue(parser.videoId);
|
||||
setIsOpen(true);
|
||||
setTab("player");
|
||||
} else {
|
||||
setInputValue("");
|
||||
setBroadcastVideo("");
|
||||
setBroadcastSender("");
|
||||
setTab('player');
|
||||
}
|
||||
else
|
||||
{
|
||||
setInputValue('');
|
||||
setBroadcastVideo('');
|
||||
setBroadcastSender('');
|
||||
setBroadcastPlaylist([]);
|
||||
}
|
||||
});
|
||||
|
||||
useMessageEvent<YouTubeRoomWatchersEvent>(YouTubeRoomWatchersEvent, event => { setWatcherIds(new Set(event.getParser().watcherIds)); loadRoomUsers(); });
|
||||
useMessageEvent<YouTubeRoomWatchersEvent>(YouTubeRoomWatchersEvent, event =>
|
||||
{
|
||||
setWatcherIds(new Set(event.getParser().watcherIds)); loadRoomUsers();
|
||||
});
|
||||
|
||||
const sentWatchingRef = useRef(false);
|
||||
const hasVideo = !!(inputValue && extractVideoId(inputValue));
|
||||
useEffect(() => {
|
||||
if (isOpen && hasVideo && !sentWatchingRef.current) {
|
||||
try { SendMessageComposer(new YouTubeRoomWatchingComposer(true)); } catch(e) {}
|
||||
useEffect(() =>
|
||||
{
|
||||
if (isOpen && hasVideo && !sentWatchingRef.current)
|
||||
{
|
||||
try
|
||||
{
|
||||
SendMessageComposer(new YouTubeRoomWatchingComposer(true));
|
||||
}
|
||||
catch(e)
|
||||
{}
|
||||
sentWatchingRef.current = true;
|
||||
} else if ((!isOpen || !hasVideo) && sentWatchingRef.current) {
|
||||
try { SendMessageComposer(new YouTubeRoomWatchingComposer(false)); } catch(e) {}
|
||||
}
|
||||
else if ((!isOpen || !hasVideo) && sentWatchingRef.current)
|
||||
{
|
||||
try
|
||||
{
|
||||
SendMessageComposer(new YouTubeRoomWatchingComposer(false));
|
||||
}
|
||||
catch(e)
|
||||
{}
|
||||
sentWatchingRef.current = false;
|
||||
}
|
||||
}, [isOpen, hasVideo]);
|
||||
|
||||
const loadRoomUsers = () => {
|
||||
try {
|
||||
const loadRoomUsers = () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const roomSession = GetRoomSession();
|
||||
if (!roomSession) { setSpectators([]); return; }
|
||||
if (!roomSession)
|
||||
{
|
||||
setSpectators([]); return;
|
||||
}
|
||||
const users: { id: number; name: string; look: string }[] = [];
|
||||
const seen = new Set<number>();
|
||||
for (let i = 0; i < 500; i++) {
|
||||
for (let i = 0; i < 500; i++)
|
||||
{
|
||||
const userData = roomSession.userDataManager.getUserDataByIndex(i);
|
||||
if (userData && userData.name && userData.type === 1 && !seen.has(userData.userId)) {
|
||||
if (userData && userData.name && userData.type === 1 && !seen.has(userData.userId))
|
||||
{
|
||||
seen.add(userData.userId);
|
||||
users.push({ id: userData.userId, name: userData.name, look: userData.figure });
|
||||
}
|
||||
}
|
||||
setSpectators(users);
|
||||
} catch (e) {
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
setSpectators([]);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
useEffect(() =>
|
||||
{
|
||||
if (isOpen) loadRoomUsers();
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (youtubeObjectId && youtubeObjectId !== -1) {
|
||||
useEffect(() =>
|
||||
{
|
||||
if (youtubeObjectId && youtubeObjectId !== -1)
|
||||
{
|
||||
setIsRoomMode(true);
|
||||
if (roomVideoId) {
|
||||
if (roomVideoId)
|
||||
{
|
||||
setInputValue(roomVideoId);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
setIsRoomMode(false);
|
||||
}
|
||||
}, [youtubeObjectId, roomVideoId]);
|
||||
|
||||
useEffect(() => {
|
||||
useEffect(() =>
|
||||
{
|
||||
const handler = () => setIsOpen((p) => !p);
|
||||
window.addEventListener("youtube:toggle", handler);
|
||||
return () => window.removeEventListener("youtube:toggle", handler);
|
||||
window.addEventListener('youtube:toggle', handler);
|
||||
return () => window.removeEventListener('youtube:toggle', handler);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const savedHistory = localStorage.getItem("youtube_history");
|
||||
if (savedHistory) {
|
||||
try {
|
||||
useEffect(() =>
|
||||
{
|
||||
const savedHistory = localStorage.getItem('youtube_history');
|
||||
if (savedHistory)
|
||||
{
|
||||
try
|
||||
{
|
||||
const parsed = JSON.parse(savedHistory);
|
||||
if (Array.isArray(parsed)) {
|
||||
setHistory(parsed.map((entry: any) => typeof entry === "string" ? entry : entry?.id).filter(Boolean));
|
||||
if (Array.isArray(parsed))
|
||||
{
|
||||
setHistory(parsed.map((entry: any) => typeof entry === 'string' ? entry : entry?.id).filter(Boolean));
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
catch (e)
|
||||
{}
|
||||
}
|
||||
const savedPlaylist = localStorage.getItem("youtube_playlist");
|
||||
if (savedPlaylist) {
|
||||
try {
|
||||
const savedPlaylist = localStorage.getItem('youtube_playlist');
|
||||
if (savedPlaylist)
|
||||
{
|
||||
try
|
||||
{
|
||||
const parsed = JSON.parse(savedPlaylist);
|
||||
if (Array.isArray(parsed)) {
|
||||
setPlaylist(parsed.map((entry: any) => typeof entry === "string" ? entry : entry?.id).filter(Boolean));
|
||||
if (Array.isArray(parsed))
|
||||
{
|
||||
setPlaylist(parsed.map((entry: any) => typeof entry === 'string' ? entry : entry?.id).filter(Boolean));
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
catch (e)
|
||||
{}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
useEffect(() =>
|
||||
{
|
||||
localStorage.setItem(
|
||||
"youtube_history",
|
||||
'youtube_history',
|
||||
JSON.stringify(history.slice(0, 50)),
|
||||
);
|
||||
}, [history]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem("youtube_playlist", JSON.stringify(playlist));
|
||||
useEffect(() =>
|
||||
{
|
||||
localStorage.setItem('youtube_playlist', JSON.stringify(playlist));
|
||||
}, [playlist]);
|
||||
|
||||
const addToHistory = (id: string) => {
|
||||
const addToHistory = (id: string) =>
|
||||
{
|
||||
if (!id) return;
|
||||
setHistory((prev) => {
|
||||
setHistory((prev) =>
|
||||
{
|
||||
const filtered = prev.filter((v) => v !== id);
|
||||
return [id, ...filtered].slice(0, 50);
|
||||
});
|
||||
@@ -199,9 +255,11 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
),
|
||||
);
|
||||
|
||||
const addToPlaylist = () => {
|
||||
const addToPlaylist = () =>
|
||||
{
|
||||
const id = extractVideoId(inputValue);
|
||||
if (id && !playlist.includes(id)) {
|
||||
if (id && !playlist.includes(id))
|
||||
{
|
||||
setPlaylist((p) => [...p, id]);
|
||||
}
|
||||
};
|
||||
@@ -222,11 +280,12 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
label: string;
|
||||
}) => (
|
||||
<button
|
||||
onClick={() => {
|
||||
onClick={() =>
|
||||
{
|
||||
setVolume(value);
|
||||
setVolumePreset(value);
|
||||
}}
|
||||
className={`px-2 py-1 rounded text-xs ${volumePreset === value ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300"}`}
|
||||
className={`px-2 py-1 rounded text-xs ${volumePreset === value ? 'bg-amber-600 text-white' : 'bg-gray-700 text-gray-300'}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
@@ -234,55 +293,58 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
|
||||
return (
|
||||
<NitroCardView
|
||||
className={`youtube-player-modal ${isFullscreen ? "!fixed inset-0 w-full h-full z-[9999] rounded-none" : "w-[550px]"}`}
|
||||
className={`youtube-player-modal ${isFullscreen ? '!fixed inset-0 w-full h-full z-[9999] rounded-none' : 'w-[550px]'}`}
|
||||
>
|
||||
<NitroCardHeaderView
|
||||
headerText={isRoomMode ? "📺 YouTube TV" : "▶ YouTube"}
|
||||
headerText={isRoomMode ? '📺 YouTube TV' : '▶ YouTube'}
|
||||
onCloseClick={() => setIsOpen(false)}
|
||||
/>
|
||||
<NitroCardContentView>
|
||||
<div className="flex gap-1 mb-3 border-b border-gray-700 pb-2 flex-wrap">
|
||||
<button
|
||||
onClick={() => setTab("player")}
|
||||
className={`px-3 py-1 rounded text-sm ${tab === "player" ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300"}`}
|
||||
onClick={() => setTab('player')}
|
||||
className={`px-3 py-1 rounded text-sm ${tab === 'player' ? 'bg-amber-600 text-white' : 'bg-gray-700 text-gray-300'}`}
|
||||
>
|
||||
▶
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setTab("playlist")}
|
||||
className={`px-3 py-1 rounded text-sm ${tab === "playlist" ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300"}`}
|
||||
onClick={() => setTab('playlist')}
|
||||
className={`px-3 py-1 rounded text-sm ${tab === 'playlist' ? 'bg-amber-600 text-white' : 'bg-gray-700 text-gray-300'}`}
|
||||
>
|
||||
📋 {playlist.length}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setTab("history")}
|
||||
className={`px-3 py-1 rounded text-sm ${tab === "history" ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300"}`}
|
||||
onClick={() => setTab('history')}
|
||||
className={`px-3 py-1 rounded text-sm ${tab === 'history' ? 'bg-amber-600 text-white' : 'bg-gray-700 text-gray-300'}`}
|
||||
>
|
||||
🕐 {history.length}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setTab("share")}
|
||||
className={`px-3 py-1 rounded text-sm ${tab === "share" ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300"}`}
|
||||
onClick={() => setTab('share')}
|
||||
className={`px-3 py-1 rounded text-sm ${tab === 'share' ? 'bg-amber-600 text-white' : 'bg-gray-700 text-gray-300'}`}
|
||||
>
|
||||
📤
|
||||
</button>
|
||||
{watcherIds.size > 0 && (
|
||||
<button
|
||||
onClick={() => { setTab("spectators"); loadRoomUsers(); }}
|
||||
className={`px-3 py-1 rounded text-sm ${tab === "spectators" ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300"}`}
|
||||
onClick={() =>
|
||||
{
|
||||
setTab('spectators'); loadRoomUsers();
|
||||
}}
|
||||
className={`px-3 py-1 rounded text-sm ${tab === 'spectators' ? 'bg-amber-600 text-white' : 'bg-gray-700 text-gray-300'}`}
|
||||
>
|
||||
📺 {watcherIds.size}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setTab("settings")}
|
||||
className={`px-3 py-1 rounded text-sm ${tab === "settings" ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300"}`}
|
||||
onClick={() => setTab('settings')}
|
||||
className={`px-3 py-1 rounded text-sm ${tab === 'settings' ? 'bg-amber-600 text-white' : 'bg-gray-700 text-gray-300'}`}
|
||||
>
|
||||
⚙️
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{tab === "player" && (
|
||||
{tab === 'player' && (
|
||||
<>
|
||||
{isRoomMode && (
|
||||
<div className="mb-2 p-2 bg-blue-900/50 rounded flex justify-between text-sm">
|
||||
@@ -311,10 +373,13 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
|
||||
{videoId ? (
|
||||
<ReactPlayer
|
||||
ref={ref => { playerRef.current = ref; }}
|
||||
ref={ref =>
|
||||
{
|
||||
playerRef.current = ref;
|
||||
}}
|
||||
url={`https://www.youtube.com/watch?v=${videoId}`}
|
||||
width="100%"
|
||||
height={isFullscreen ? "100%" : 280}
|
||||
height={isFullscreen ? '100%' : 280}
|
||||
playing
|
||||
muted={isMuted}
|
||||
loop={isLooping}
|
||||
@@ -347,7 +412,7 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
}
|
||||
className="px-4 py-1 bg-amber-600 rounded text-white font-bold text-sm"
|
||||
>
|
||||
{isPlaying ? "⏸" : "▶"}
|
||||
{isPlaying ? '⏸' : '▶'}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleNext}
|
||||
@@ -363,12 +428,16 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
<span className="text-purple-300">📡 {broadcastSender} broadcasting</span>
|
||||
{isMyRoom && (
|
||||
<button
|
||||
onClick={() => {
|
||||
try {
|
||||
SendMessageComposer(new YouTubeRoomPlayComposer("", []));
|
||||
} catch(e) {}
|
||||
setBroadcastVideo("");
|
||||
setBroadcastSender("");
|
||||
onClick={() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
SendMessageComposer(new YouTubeRoomPlayComposer('', []));
|
||||
}
|
||||
catch(e)
|
||||
{}
|
||||
setBroadcastVideo('');
|
||||
setBroadcastSender('');
|
||||
setBroadcastPlaylist([]);
|
||||
}}
|
||||
className="px-2 py-0.5 bg-red-700 hover:bg-red-600 rounded text-white text-xs"
|
||||
@@ -385,15 +454,19 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
disabled={!!broadcastVideo && !isMyRoom}
|
||||
className={`flex-1 p-2 rounded text-white text-sm ${(!!broadcastVideo && !isMyRoom) ? "bg-gray-800" : "bg-gray-700"}`}
|
||||
className={`flex-1 p-2 rounded text-white text-sm ${(!!broadcastVideo && !isMyRoom) ? 'bg-gray-800' : 'bg-gray-700'}`}
|
||||
placeholder="YouTube URL / video ID"
|
||||
/>
|
||||
{isMyRoom && youtubeEnabled && videoId && (
|
||||
<button
|
||||
onClick={() => {
|
||||
try {
|
||||
onClick={() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
SendMessageComposer(new YouTubeRoomPlayComposer(videoId, playlist));
|
||||
} catch(e) {}
|
||||
}
|
||||
catch(e)
|
||||
{}
|
||||
}}
|
||||
className="px-3 bg-purple-600 rounded text-white text-sm whitespace-nowrap"
|
||||
title="Speel deze video voor iedereen in de kamer"
|
||||
@@ -405,7 +478,7 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{tab === "playlist" && (
|
||||
{tab === 'playlist' && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
@@ -415,7 +488,7 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
placeholder="Add video URL..."
|
||||
className="flex-1 p-2 bg-gray-700 text-white rounded text-sm"
|
||||
onKeyDown={(e) =>
|
||||
e.key === "Enter" && addToPlaylist()
|
||||
e.key === 'Enter' && addToPlaylist()
|
||||
}
|
||||
/>
|
||||
<button
|
||||
@@ -427,7 +500,7 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setInputValue("")}
|
||||
onClick={() => setInputValue('')}
|
||||
className="flex-1 px-3 py-2 bg-gray-700 rounded text-white text-sm"
|
||||
>
|
||||
🔄 New video
|
||||
@@ -449,9 +522,10 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
<div
|
||||
key={i}
|
||||
className="flex items-center gap-2 p-2 bg-gray-800 rounded hover:bg-gray-700 cursor-pointer"
|
||||
onClick={() => {
|
||||
onClick={() =>
|
||||
{
|
||||
setInputValue(id);
|
||||
setTab("player");
|
||||
setTab('player');
|
||||
}}
|
||||
>
|
||||
<span className="text-amber-500 text-sm w-6">
|
||||
@@ -461,7 +535,8 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
{id}
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
setPlaylist((p) =>
|
||||
p.filter((x) => x !== id),
|
||||
@@ -478,7 +553,7 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tab === "history" && (
|
||||
{tab === 'history' && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-gray-400 text-sm">
|
||||
@@ -501,9 +576,10 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
<div
|
||||
key={i}
|
||||
className="flex items-center gap-2 p-2 bg-gray-800 rounded hover:bg-gray-700 cursor-pointer"
|
||||
onClick={() => {
|
||||
onClick={() =>
|
||||
{
|
||||
setInputValue(id);
|
||||
setTab("player");
|
||||
setTab('player');
|
||||
}}
|
||||
>
|
||||
<div className="flex-1 min-w-0 text-white text-sm truncate font-mono">
|
||||
@@ -516,7 +592,7 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tab === "share" && (
|
||||
{tab === 'share' && (
|
||||
<div className="space-y-3">
|
||||
<div className="p-3 bg-gray-800 rounded">
|
||||
<div className="text-gray-400 text-sm mb-2">
|
||||
@@ -532,7 +608,8 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
className="flex-1 p-2 bg-gray-700 text-white rounded text-sm"
|
||||
/>
|
||||
<button
|
||||
onClick={() => {
|
||||
onClick={() =>
|
||||
{
|
||||
navigator.clipboard.writeText(
|
||||
`https://youtube.com/watch?v=${videoId}`,
|
||||
);
|
||||
@@ -555,12 +632,14 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (videoId) {
|
||||
onClick={() =>
|
||||
{
|
||||
if (videoId)
|
||||
{
|
||||
const url = `https://twitter.com/intent/tweet?text=${encodeURIComponent(
|
||||
'Now watching: https://youtube.com/watch?v=${videoId}',
|
||||
)}`;
|
||||
window.open(url, "_blank");
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
}}
|
||||
disabled={!videoId}
|
||||
@@ -573,57 +652,61 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tab === "spectators" && (() => {
|
||||
{tab === 'spectators' && (() =>
|
||||
{
|
||||
const watchers: { id: number; name: string; look: string }[] = [];
|
||||
const rs = GetRoomSession();
|
||||
if (rs) {
|
||||
for (const uid of watcherIds) {
|
||||
if (rs)
|
||||
{
|
||||
for (const uid of watcherIds)
|
||||
{
|
||||
const ud = rs.userDataManager.getUserData(uid);
|
||||
if (ud && ud.name) {
|
||||
if (ud && ud.name)
|
||||
{
|
||||
watchers.push({ id: ud.userId, name: ud.name, look: ud.figure });
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="p-3 bg-gray-800 rounded">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<div className="text-gray-400 text-sm">
|
||||
<div className="p-3 bg-gray-800 rounded">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<div className="text-gray-400 text-sm">
|
||||
📺 {watchers.length} watching
|
||||
</div>
|
||||
<button
|
||||
onClick={loadRoomUsers}
|
||||
className="text-gray-400 hover:text-white text-xs"
|
||||
>
|
||||
</div>
|
||||
<button
|
||||
onClick={loadRoomUsers}
|
||||
className="text-gray-400 hover:text-white text-xs"
|
||||
>
|
||||
🔄
|
||||
</button>
|
||||
</div>
|
||||
{watchers.length === 0 ? (
|
||||
<div className="text-gray-500 text-sm text-center py-4">
|
||||
</button>
|
||||
</div>
|
||||
{watchers.length === 0 ? (
|
||||
<div className="text-gray-500 text-sm text-center py-4">
|
||||
No one is watching
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-h-[200px] overflow-y-auto space-y-1">
|
||||
{watchers.map((user) => (
|
||||
<div
|
||||
key={user.id}
|
||||
className="flex items-center gap-2 p-2 bg-gray-700 rounded"
|
||||
>
|
||||
<div className="shrink-0 overflow-hidden">
|
||||
<LayoutAvatarImageView figure={user.look} headOnly direction={2} scale={1} className="!w-[45px] !h-[65px] -mt-[5px] -ml-[5px]" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-h-[200px] overflow-y-auto space-y-1">
|
||||
{watchers.map((user) => (
|
||||
<div
|
||||
key={user.id}
|
||||
className="flex items-center gap-2 p-2 bg-gray-700 rounded"
|
||||
>
|
||||
<div className="shrink-0 overflow-hidden">
|
||||
<LayoutAvatarImageView figure={user.look} headOnly direction={2} scale={1} className="!w-[45px] !h-[65px] -mt-[5px] -ml-[5px]" />
|
||||
</div>
|
||||
<span className="text-white text-sm flex-1">
|
||||
{user.name}
|
||||
</span>
|
||||
<span className="text-amber-400 text-xs">📺</span>
|
||||
</div>
|
||||
<span className="text-white text-sm flex-1">
|
||||
{user.name}
|
||||
</span>
|
||||
<span className="text-amber-400 text-xs">📺</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{tab === "settings" && (
|
||||
{tab === 'settings' && (
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-1">
|
||||
@@ -636,7 +719,7 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
}
|
||||
className="text-gray-400 text-xs"
|
||||
>
|
||||
{showVolumeSlider ? "▼" : "▲"}
|
||||
{showVolumeSlider ? '▼' : '▲'}
|
||||
</button>
|
||||
</div>
|
||||
{showVolumeSlider && (
|
||||
@@ -645,7 +728,8 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
min="0"
|
||||
max="100"
|
||||
value={volume}
|
||||
onChange={(e) => {
|
||||
onChange={(e) =>
|
||||
{
|
||||
setVolume(parseInt(e.target.value));
|
||||
setVolumePreset(
|
||||
parseInt(e.target.value),
|
||||
@@ -702,19 +786,19 @@ export const YouTubePlayerView: FC<{}> = () => {
|
||||
<div className="p-2 bg-gray-800 rounded text-xs text-gray-400">
|
||||
<div className="font-bold mb-1">ℹ️ Info</div>
|
||||
<div>
|
||||
📡 Broadcast:{" "}
|
||||
📡 Broadcast:{' '}
|
||||
{broadcastVideo
|
||||
? <span className="text-green-400">✓ Active ({broadcastSender} playing)</span>
|
||||
: <span className="text-gray-500">✕ No video</span>}
|
||||
</div>
|
||||
<div>
|
||||
🎮 Controle:{" "}
|
||||
🎮 Controle:{' '}
|
||||
{isMyRoom
|
||||
? <span className="text-green-400">✓ You are the owner</span>
|
||||
: <span className="text-gray-500">✕ Viewing only</span>}
|
||||
</div>
|
||||
<div>
|
||||
👁️ Viewers:{" "}
|
||||
👁️ Viewers:{' '}
|
||||
<span className="text-amber-400">{watcherIds.size}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user