From 92e9bb19cd9dd814062f8ecf4b49a7b97122e2fd Mon Sep 17 00:00:00 2001 From: DuckieTM Date: Sun, 3 May 2026 17:54:10 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=86=99=20Updates=20thanks=20to=20Life?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit react-slider → @radix-ui/react-slider react-tiny-popover → @radix-ui/react-popover react-youtube → react-player (more formats, better maintained) --- package.json | 7 +- src/common/Slider.tsx | 141 ++++++++++++++++-- .../NavigatorSearchResultItemInfoView.tsx | 58 ++++--- .../chat-input/ChatInputEmojiSelectorView.tsx | 25 ++-- .../chat-input/ChatInputStyleSelectorView.tsx | 43 +++--- .../furniture/FurnitureYoutubeDisplayView.tsx | 91 ++++------- src/components/toolbar/YouTubePlayerView.tsx | 26 ++-- 7 files changed, 229 insertions(+), 162 deletions(-) diff --git a/package.json b/package.json index b504e77..6619dea 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "@babel/runtime": "^7.29.2", "@emoji-mart/data": "^1.2.1", "@emoji-mart/react": "^1.1.1", + "@radix-ui/react-popover": "^1.1.6", + "@radix-ui/react-slider": "^1.2.4", "@tanstack/react-virtual": "3.13.24", "dompurify": "^3.4.1", "emoji-mart": "^5.6.0", @@ -21,9 +23,7 @@ "react": "^19.2.5", "react-dom": "^19.2.5", "react-icons": "^5.5.0", - "react-slider": "^2.0.6", - "react-tiny-popover": "^8.1.6", - "react-youtube": "^10.1.0", + "react-player": "^2.16.0", "use-between": "^1.4.0" }, "devDependencies": { @@ -32,7 +32,6 @@ "@types/node": "^25.6.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "@types/react-slider": "^1.3.6", "@typescript-eslint/eslint-plugin": "^8.59.1", "@typescript-eslint/parser": "^8.59.1", "@vitejs/plugin-react": "^6.0.1", diff --git a/src/common/Slider.tsx b/src/common/Slider.tsx index 72fb74c..8cd7490 100644 --- a/src/common/Slider.tsx +++ b/src/common/Slider.tsx @@ -1,35 +1,148 @@ -import { FC } from 'react'; -import ReactSlider, { ReactSliderProps } from 'react-slider'; +import * as RadixSlider from '@radix-ui/react-slider'; +import { CSSProperties, FC, HTMLProps, ReactElement } from 'react'; +import { FaAngleLeft, FaAngleRight } from 'react-icons/fa'; import { Button } from './Button'; import { Flex } from './Flex'; -import { FaAngleLeft, FaAngleRight } from 'react-icons/fa'; -export interface SliderProps extends ReactSliderProps +export interface SliderThumbState { - disabledButton?: boolean; + index: number; + value: number | number[]; + valueNow: number; } +export interface SliderProps +{ + min?: number; + max?: number; + step?: number; + value?: number | number[]; + defaultValue?: number | number[]; + onChange?: (value: any, thumbIndex: number) => void; + disabled?: boolean; + disabledButton?: boolean; + invert?: boolean; + className?: string; + style?: CSSProperties; + trackClassName?: string; + thumbClassName?: string; + renderThumb?: (props: HTMLProps, state: SliderThumbState) => ReactElement; +} + +const toArray = (value: number | number[] | undefined): number[] => +{ + if(Array.isArray(value)) return value; + if(typeof value === 'number') return [ value ]; + + return [ 0 ]; +}; + +const cn = (...parts: (string | undefined | false)[]) => parts.filter(Boolean).join(' '); + export const Slider: FC = props => { - const { disabledButton, max, min, step, value, onChange, ...rest } = props; - const currentValue = Array.isArray(value) ? value[0] : ((typeof value === 'number') ? value : 0); + const { + disabledButton, + disabled, + max = 100, + min = 0, + step = 1, + value, + defaultValue, + onChange, + invert, + className, + style, + trackClassName, + thumbClassName, + renderThumb + } = props; + + const valueArr = toArray(value); + const currentValue = valueArr[0] ?? 0; const minimum = (typeof min === 'number') ? min : 0; const maximum = (typeof max === 'number') ? max : 0; const buttonStep = ((typeof step === 'number') && (step > 0)) ? step : 1; + const isRange = valueArr.length > 1; const roundToStep = (nextValue: number) => { - if(typeof buttonStep !== 'number') return nextValue; - const decimalStep = buttonStep.toString(); const precision = decimalStep.includes('.') ? (decimalStep.length - decimalStep.indexOf('.') - 1) : 0; return parseFloat(nextValue.toFixed(precision)); }; - return - { !disabledButton && } - - { !disabledButton && } - ; + const emit = (next: number[]) => + { + if(!onChange) return; + + if(isRange) onChange(next, 0); + else onChange(next[0], 0); + }; + + const stepDown = () => + { + const next = roundToStep(minimum < currentValue ? currentValue - buttonStep : minimum); + + emit([ next, ...valueArr.slice(1) ]); + }; + + const stepUp = () => + { + const next = roundToStep(maximum > currentValue ? currentValue + buttonStep : maximum); + + emit([ next, ...valueArr.slice(1) ]); + }; + + const renderThumbElement = (i: number) => + { + const baseProps: HTMLProps = { + key: i, + className: cn('thumb', `thumb-${ i }`, thumbClassName) + }; + + const state: SliderThumbState = { + index: i, + value: isRange ? valueArr : currentValue, + valueNow: valueArr[i] ?? 0 + }; + + return ( + + { renderThumb ? renderThumb(baseProps, state) :
} + + ); + }; + + return ( + + { !disabledButton && ( + + ) } + + + + + { valueArr.map((_, i) => renderThumbElement(i)) } + + { !disabledButton && ( + + ) } + + ); } diff --git a/src/components/navigator/views/search/NavigatorSearchResultItemInfoView.tsx b/src/components/navigator/views/search/NavigatorSearchResultItemInfoView.tsx index 645e5c9..40cc451 100644 --- a/src/components/navigator/views/search/NavigatorSearchResultItemInfoView.tsx +++ b/src/components/navigator/views/search/NavigatorSearchResultItemInfoView.tsx @@ -1,7 +1,7 @@ import { RoomDataParser, RoomSettingsComposer, UpdateHomeRoomMessageComposer } from '@nitrots/nitro-renderer'; +import * as Popover from '@radix-ui/react-popover'; import React, { FC, useRef, useState } from 'react'; import { FaUser } from 'react-icons/fa'; -import { ArrowContainer, Popover } from 'react-tiny-popover'; import { GetGroupInformation, GetSessionDataManager, GetUserProfile, LocalizeText, ReportType, SendMessageComposer, ToggleFavoriteRoom } from '../../../../api'; import { Column, Flex, LayoutBadgeImageView, LayoutRoomThumbnailView, NitroCardContentView, Text, UserProfileIconView } from '../../../../common'; import { useHelp, useNavigator } from '../../../../hooks'; @@ -26,6 +26,12 @@ export const NavigatorSearchResultItemInfoView: FC + { + if(!isControlled) setInternalVisible(open); + if(!open && setIsPopoverActive) setIsPopoverActive(false); + }; + const getUserCounterColor = () => { const num: number = (100 * (roomData.userCount / roomData.maxUserCount)); @@ -88,17 +94,22 @@ export const NavigatorSearchResultItemInfoView: FC ( - + + +
{ if(!isControlled) setInternalVisible(true); } } + onMouseLeave={ () => { if(!isControlled) setInternalVisible(false); } } + /> + + + e.stopPropagation() }> @@ -173,24 +184,9 @@ export const NavigatorSearchResultItemInfoView: FC } - - ) } - isOpen={ popoverOpen } - onClickOutside={ () => - { - if(!isControlled) setInternalVisible(false); - if(setIsPopoverActive) setIsPopoverActive(false); - } } - padding={ 10 } - positions={ [ 'right', 'left', 'top', 'bottom' ] } - > -
{ if(!isControlled) setInternalVisible(true); } } - onMouseLeave={ () => { if(!isControlled) setInternalVisible(false); } } - /> - + + + + ); }; diff --git a/src/components/room/widgets/chat-input/ChatInputEmojiSelectorView.tsx b/src/components/room/widgets/chat-input/ChatInputEmojiSelectorView.tsx index f547e46..bd2dcae 100644 --- a/src/components/room/widgets/chat-input/ChatInputEmojiSelectorView.tsx +++ b/src/components/room/widgets/chat-input/ChatInputEmojiSelectorView.tsx @@ -1,7 +1,7 @@ import data from '@emoji-mart/data'; import Picker from '@emoji-mart/react'; +import * as Popover from '@radix-ui/react-popover'; import { FC, useState } from 'react'; -import { Popover } from 'react-tiny-popover'; interface ChatInputEmojiSelectorViewProps { @@ -19,19 +19,16 @@ export const ChatInputEmojiSelectorView: FC = p setSelectorVisible(false); }; - const toggleSelector = () => setSelectorVisible(prev => !prev); - return ( -
- } - isOpen={ selectorVisible } - positions={ [ 'top' ] } - onClickOutside={ () => setSelectorVisible(false) } - > -
🙂
-
-
+ + +
🙂
+
+ + + + + +
); }; diff --git a/src/components/room/widgets/chat-input/ChatInputStyleSelectorView.tsx b/src/components/room/widgets/chat-input/ChatInputStyleSelectorView.tsx index 2a6d165..ec089c3 100644 --- a/src/components/room/widgets/chat-input/ChatInputStyleSelectorView.tsx +++ b/src/components/room/widgets/chat-input/ChatInputStyleSelectorView.tsx @@ -1,5 +1,5 @@ +import * as Popover from '@radix-ui/react-popover'; import { FC, useState } from 'react'; -import { ArrowContainer, Popover } from 'react-tiny-popover'; import { Flex, Grid, NitroCardContentView } from '../../../../common'; interface ChatInputStyleSelectorViewProps @@ -21,20 +21,17 @@ export const ChatInputStyleSelectorView: FC = p }; return ( - ( - + +
+
+
+ + + @@ -47,15 +44,9 @@ export const ChatInputStyleSelectorView: FC = p ))} - - )} - > -
setSelectorVisible(v => !v)} - > -
-
- + + + + ); -}; \ No newline at end of file +}; diff --git a/src/components/room/widgets/furniture/FurnitureYoutubeDisplayView.tsx b/src/components/room/widgets/furniture/FurnitureYoutubeDisplayView.tsx index f2375ca..82a4b8c 100644 --- a/src/components/room/widgets/furniture/FurnitureYoutubeDisplayView.tsx +++ b/src/components/room/widgets/furniture/FurnitureYoutubeDisplayView.tsx @@ -1,6 +1,5 @@ -import { FC, useEffect, useState } from 'react'; -import YouTube, { Options } from 'react-youtube'; -import { YouTubePlayer } from 'youtube-player/dist/types'; +import { FC, useRef } from 'react'; +import ReactPlayer from 'react-player/youtube'; import { LocalizeText, YoutubeVideoPlaybackStateEnum } from '../../../../api'; import { AutoGrid, AutoGridProps, LayoutGridItem, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; import { useFurnitureYoutubeWidget } from '../../../../hooks'; @@ -12,71 +11,24 @@ interface FurnitureYoutubeDisplayViewProps extends AutoGridProps export const FurnitureYoutubeDisplayView: FC<{}> = FurnitureYoutubeDisplayViewProps => { - const [ player, setPlayer ] = useState(null); const { objectId = -1, videoId = null, videoStart = 0, videoEnd = 0, currentVideoState = null, selectedVideo = null, playlists = [], onClose = null, previous = null, next = null, pause = null, play = null, selectVideo = null } = useFurnitureYoutubeWidget(); + const playerRef = useRef(null); - const onStateChange = (event: { target: YouTubePlayer; data: number }) => + const handlePlay = () => { - try - { - setPlayer(event.target); - - if(objectId === -1) return; - - switch(event.target.getPlayerState()) - { - case -1: - case 1: - if(currentVideoState !== 1) play(); - return; - case 2: - if(currentVideoState !== 2) pause(); - } - } - catch(err) {} + if(objectId === -1) return; + if(currentVideoState !== YoutubeVideoPlaybackStateEnum.PLAYING) play(); }; - useEffect(() => + const handlePause = () => { - if((currentVideoState === null) || !player) return; - - try - { - if((currentVideoState === YoutubeVideoPlaybackStateEnum.PLAYING) && (player.getPlayerState() !== YoutubeVideoPlaybackStateEnum.PLAYING)) - { - player.playVideo(); - - return; - } - - if((currentVideoState === YoutubeVideoPlaybackStateEnum.PAUSED) && (player.getPlayerState() !== YoutubeVideoPlaybackStateEnum.PAUSED)) - { - player.pauseVideo(); - - return; - } - } - catch(err) - { - setPlayer(null); - } - }, [ currentVideoState, player ]); + if(objectId === -1) return; + if(currentVideoState !== YoutubeVideoPlaybackStateEnum.PAUSED) pause(); + }; if(objectId === -1) return null; - const youtubeOptions: Options = { - height: '375', - width: '500', - playerVars: { - autoplay: 1, - disablekb: 1, - controls: 0, - origin: window.origin, - modestbranding: 1, - start: videoStart, - end: videoEnd - } - }; + const playing = (currentVideoState === null) ? true : (currentVideoState === YoutubeVideoPlaybackStateEnum.PLAYING); return ( @@ -85,7 +37,26 @@ export const FurnitureYoutubeDisplayView: FC<{}> = FurnitureYoutubeDisplayViewPr
{ (videoId && videoId.length > 0) && - setPlayer(event.target) } onStateChange={ onStateChange } /> + } { (!videoId || videoId.length === 0) &&
{ LocalizeText('widget.furni.video_viewer.no_videos') }
diff --git a/src/components/toolbar/YouTubePlayerView.tsx b/src/components/toolbar/YouTubePlayerView.tsx index 39e8e76..5f1c89c 100644 --- a/src/components/toolbar/YouTubePlayerView.tsx +++ b/src/components/toolbar/YouTubePlayerView.tsx @@ -1,6 +1,6 @@ import { ControlYoutubeDisplayPlaybackMessageComposer, YouTubeRoomBroadcastEvent, YouTubeRoomPlayComposer, YouTubeRoomSettingsEvent, YouTubeRoomWatchersEvent, YouTubeRoomWatchingComposer } from "@nitrots/nitro-renderer"; import { FC, useEffect, useRef, useState } from "react"; -import YouTube from "react-youtube"; +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"; @@ -35,7 +35,7 @@ export const YouTubePlayerView: FC<{}> = () => { const [playlist, setPlaylist] = useState([]); const [history, setHistory] = useState([]); const [showVolumeSlider, setShowVolumeSlider] = useState(true); - const playerRef = useRef(null); + const playerRef = useRef(null); const { objectId: youtubeObjectId, videoId: roomVideoId, currentVideoState, hasControl } = useFurnitureYoutubeWidget(); const [spectators, setSpectators] = useState< { id: number; name: string; look: string }[] >([]); const [broadcastVideo, setBroadcastVideo] = useState(""); @@ -310,22 +310,22 @@ export const YouTubePlayerView: FC<{}> = () => { )} {videoId ? ( - { playerRef.current = ref; }} + url={`https://www.youtube.com/watch?v=${videoId}`} + width="100%" + height={isFullscreen ? "100%" : 280} + playing + muted={isMuted} + loop={isLooping} + volume={Math.max(0, Math.min(1, volume / 100))} + config={{ playerVars: { autoplay: 1, - volume: volume, - muted: isMuted ? 1 : 0, loop: isLooping ? 1 : 0, }, }} - onReady={(e) => { - playerRef.current = e.target; - addToHistory(videoId); - }} + onReady={() => addToHistory(videoId)} /> ) : (