mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 07:26:19 +00:00
Merge remote-tracking branch 'duckie/main' into merge-duckie-main-2026-05-06
# Conflicts: # index.html # public/UITexts.example # public/renderer-config.example # src/App.tsx # src/components/login/LoginView.tsx # src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserView.tsx # src/components/toolbar/ToolbarView.tsx # src/components/user-profile/UserContainerView.tsx
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { FC, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { FaPlus } from 'react-icons/fa';
|
||||
import { GetConfigurationValue, LocalizeText } from '../../../../../api';
|
||||
import { LayoutBadgeImageView } from '../../../../../common';
|
||||
@@ -15,7 +16,8 @@ const BadgeMiniPicker: FC<{
|
||||
onSelect: (badgeCode: string) => void;
|
||||
onClose: () => void;
|
||||
activeBadgeCodes: (string | null)[];
|
||||
}> = ({ onSelect, onClose, activeBadgeCodes }) =>
|
||||
position: { top: number; left: number };
|
||||
}> = ({ onSelect, onClose, activeBadgeCodes, position }) =>
|
||||
{
|
||||
const { badgeCodes = [], requestBadges = null } = useInventoryBadges();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
@@ -43,10 +45,11 @@ const BadgeMiniPicker: FC<{
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, [ onClose ]);
|
||||
|
||||
return (
|
||||
return createPortal(
|
||||
<div
|
||||
ref={ ref }
|
||||
className="absolute right-[calc(100%+8px)] top-0 z-50 bg-[rgba(28,28,32,0.97)] border border-white/20 rounded-md p-2 shadow-lg min-w-[160px]"
|
||||
className="fixed z-[9999] bg-[rgba(28,28,32,0.97)] border border-white/20 rounded-md p-2 shadow-lg min-w-[160px]"
|
||||
style={ { top: position.top, left: position.left } }
|
||||
onClick={ e => e.stopPropagation() }>
|
||||
<input
|
||||
autoFocus
|
||||
@@ -73,7 +76,8 @@ const BadgeMiniPicker: FC<{
|
||||
) }
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
};
|
||||
|
||||
@@ -83,7 +87,9 @@ export const InfoStandBadgeSlotView: FC<InfoStandBadgeSlotProps> = ({ slotIndex,
|
||||
const [ isDragOver, setIsDragOver ] = useState(false);
|
||||
const [ isDragging, setIsDragging ] = useState(false);
|
||||
const [ justDropped, setJustDropped ] = useState(false);
|
||||
const [ showPicker, setShowPicker ] = useState(false);
|
||||
const [ pickerPosition, setPickerPosition ] = useState<{ top: number; left: number } | null>(null);
|
||||
const slotRef = useRef<HTMLDivElement>(null);
|
||||
const showPicker = pickerPosition !== null;
|
||||
|
||||
const hookInitialized = activeBadgeCodes.length > 0;
|
||||
|
||||
@@ -152,9 +158,17 @@ export const InfoStandBadgeSlotView: FC<InfoStandBadgeSlotProps> = ({ slotIndex,
|
||||
|
||||
const handleSlotClick = useCallback(() =>
|
||||
{
|
||||
if(!isOwnUser || badgeCode) return;
|
||||
if(!isOwnUser || badgeCode || !slotRef.current) return;
|
||||
|
||||
setShowPicker(true);
|
||||
const rect = slotRef.current.getBoundingClientRect();
|
||||
const pickerWidth = 180;
|
||||
const gap = 8;
|
||||
let left = rect.right + gap;
|
||||
|
||||
if((left + pickerWidth) > (window.innerWidth - gap)) left = rect.left - pickerWidth - gap;
|
||||
if(left < gap) left = gap;
|
||||
|
||||
setPickerPosition({ top: rect.top, left });
|
||||
}, [ isOwnUser, badgeCode ]);
|
||||
|
||||
const handleDoubleClick = useCallback(() =>
|
||||
@@ -167,12 +181,13 @@ export const InfoStandBadgeSlotView: FC<InfoStandBadgeSlotProps> = ({ slotIndex,
|
||||
const handlePickerSelect = useCallback((code: string) =>
|
||||
{
|
||||
setBadgeAtSlot(code, slotIndex);
|
||||
setShowPicker(false);
|
||||
setPickerPosition(null);
|
||||
}, [ setBadgeAtSlot, slotIndex ]);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div
|
||||
ref={ slotRef }
|
||||
className={ `flex items-center justify-center relative w-[40px] h-[40px] bg-no-repeat bg-center transition-all duration-150
|
||||
${ isOwnUser && badgeCode ? 'cursor-grab active:cursor-grabbing' : '' }
|
||||
${ isOwnUser && !badgeCode ? 'cursor-pointer' : '' }
|
||||
@@ -196,8 +211,9 @@ export const InfoStandBadgeSlotView: FC<InfoStandBadgeSlotProps> = ({ slotIndex,
|
||||
{ showPicker && (
|
||||
<BadgeMiniPicker
|
||||
activeBadgeCodes={ activeBadgeCodes }
|
||||
onClose={ () => setShowPicker(false) }
|
||||
onClose={ () => setPickerPosition(null) }
|
||||
onSelect={ handlePickerSelect }
|
||||
position={ pickerPosition }
|
||||
/>
|
||||
) }
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CrackableDataType, CreateLinkEvent, FurnitureFloorUpdateEvent, GetRoomEngine, GetSoundManager, GroupInformationComposer, GroupInformationEvent, NowPlayingEvent, RoomControllerLevel, RoomObjectCategory, RoomObjectOperationType, RoomObjectVariable, RoomWidgetEnumItemExtradataParameter, RoomWidgetFurniInfoUsagePolicyEnum, SetObjectDataMessageComposer, SongInfoReceivedEvent, StringDataType, UpdateFurniturePositionComposer } from '@nitrots/nitro-renderer';
|
||||
import { CrackableDataType, CreateLinkEvent, FurnitureFloorUpdateEvent, GetRoomEngine, GetSessionDataManager, GetSoundManager, GroupInformationComposer, GroupInformationEvent, NowPlayingEvent, RoomControllerLevel, RoomObjectCategory, RoomObjectOperationType, RoomObjectVariable, RoomWidgetEnumItemExtradataParameter, RoomWidgetFurniInfoUsagePolicyEnum, SetObjectDataMessageComposer, SongInfoReceivedEvent, StringDataType, UpdateFurniturePositionComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { FaCrosshairs, FaRulerVertical, FaTimes } from 'react-icons/fa';
|
||||
import { GrFormNextLink, GrRotateLeft, GrRotateRight } from 'react-icons/gr';
|
||||
@@ -585,19 +585,20 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
||||
onClick={ () => setDropdownOpen(!dropdownOpen) }>
|
||||
{ dropdownOpen ? `${LocalizeText('widget.furni.present.close')} Buildtools` : `${LocalizeText('navigator.roomsettings.doormode.open')} Buildtools` }
|
||||
</button>
|
||||
<button
|
||||
className="w-full text-white text-xs bg-[#1e7295] hover:bg-[#1a617f] border border-[#ffffff33] rounded px-2 py-1 cursor-pointer transition-colors"
|
||||
onClick={ () =>
|
||||
{
|
||||
const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, avatarInfo.id, avatarInfo.isWallItem ? RoomObjectCategory.WALL : RoomObjectCategory.FLOOR);
|
||||
const typeId = roomObject?.model?.getValue(RoomObjectVariable.FURNITURE_TYPE_ID);
|
||||
{ GetSessionDataManager().isModerator &&
|
||||
<button
|
||||
className="w-full text-white text-xs bg-[#1e7295] hover:bg-[#1a617f] border border-[#ffffff33] rounded px-2 py-1 cursor-pointer transition-colors"
|
||||
onClick={ () =>
|
||||
{
|
||||
const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, avatarInfo.id, avatarInfo.isWallItem ? RoomObjectCategory.WALL : RoomObjectCategory.FLOOR);
|
||||
const typeId = roomObject?.model?.getValue(RoomObjectVariable.FURNITURE_TYPE_ID);
|
||||
|
||||
CreateLinkEvent('furni-editor/show');
|
||||
CreateLinkEvent('furni-editor/show');
|
||||
|
||||
if(typeId) window.dispatchEvent(new CustomEvent('furni-editor:open', { detail: { spriteId: typeId } }));
|
||||
} }>
|
||||
Edit Furni
|
||||
</button>
|
||||
if(typeId) window.dispatchEvent(new CustomEvent('furni-editor:open', { detail: { spriteId: typeId } }));
|
||||
} }>
|
||||
Edit Furni
|
||||
</button> }
|
||||
{ dropdownOpen &&
|
||||
<div className="flex gap-[4px] w-full">
|
||||
{ /* Left panel: position + rotation */ }
|
||||
|
||||
@@ -24,12 +24,14 @@ export const InfoStandWidgetUserView: FC<InfoStandWidgetUserViewProps> = props =
|
||||
const [backgroundId, setBackgroundId] = useState<number>(null);
|
||||
const [standId, setStandId] = useState<number>(null);
|
||||
const [overlayId, setOverlayId] = useState<number>(null);
|
||||
const [cardBackgroundId, setCardBackgroundId] = useState<number>(null);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const { roomSession = null } = useRoom();
|
||||
|
||||
const infostandBackgroundClass = `background-${backgroundId ?? 'default'}`;
|
||||
const infostandStandClass = `stand-${standId ?? 'default'}`;
|
||||
const infostandOverlayClass = `overlay-${overlayId ?? 'default'}`;
|
||||
const infostandCardBackgroundClass = cardBackgroundId ? `card-background-${cardBackgroundId}` : '';
|
||||
const handleProfileClick = useCallback(() => { GetUserProfile(avatarInfo.webID); }, [avatarInfo.webID]);
|
||||
|
||||
const handleEditClick = useCallback((event: React.MouseEvent) => { event.stopPropagation(); setIsVisible(prev => !prev); }, []);
|
||||
@@ -96,6 +98,7 @@ export const InfoStandWidgetUserView: FC<InfoStandWidgetUserViewProps> = props =
|
||||
newValue.backgroundId = event.backgroundId;
|
||||
newValue.standId = event.standId;
|
||||
newValue.overlayId = event.overlayId;
|
||||
newValue.cardBackgroundId = event.cardBackgroundId ?? 0;
|
||||
return newValue;
|
||||
});
|
||||
});
|
||||
@@ -130,16 +133,12 @@ export const InfoStandWidgetUserView: FC<InfoStandWidgetUserViewProps> = props =
|
||||
setBackgroundId(avatarInfo.backgroundId);
|
||||
setStandId(avatarInfo.standId);
|
||||
setOverlayId(avatarInfo.overlayId);
|
||||
setCardBackgroundId(avatarInfo.cardBackgroundId ?? 0);
|
||||
|
||||
SendMessageComposer(new UserRelationshipsComposer(avatarInfo.webID));
|
||||
|
||||
return () => {
|
||||
setIsEditingMotto(false);
|
||||
setMotto(null);
|
||||
setRelationships(null);
|
||||
setBackgroundId(null);
|
||||
setStandId(null);
|
||||
setOverlayId(null);
|
||||
};
|
||||
}, [avatarInfo]);
|
||||
|
||||
@@ -147,7 +146,7 @@ export const InfoStandWidgetUserView: FC<InfoStandWidgetUserViewProps> = props =
|
||||
|
||||
return (
|
||||
<>
|
||||
<Column className="relative min-w-[190px] max-w-[190px] z-30 pointer-events-auto bg-[rgba(28,28,32,0.95)] [box-shadow:inset_0_5px_#22222799,inset_0_-4px_#12121599] rounded">
|
||||
<Column className={`relative min-w-[190px] max-w-[190px] z-30 pointer-events-auto ${cardBackgroundId ? '' : 'bg-[rgba(28,28,32,0.95)]'} [box-shadow:inset_0_5px_#22222799,inset_0_-4px_#12121599] rounded overflow-hidden profile-card-background ${infostandCardBackgroundClass}`}>
|
||||
<Column className="h-full p-[8px] overflow-auto" gap={1} overflow="visible">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -292,6 +291,8 @@ export const InfoStandWidgetUserView: FC<InfoStandWidgetUserViewProps> = props =
|
||||
setSelectedStand={setStandId}
|
||||
selectedOverlay={overlayId}
|
||||
setSelectedOverlay={setOverlayId}
|
||||
selectedCardBackground={cardBackgroundId}
|
||||
setSelectedCardBackground={setCardBackgroundId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -55,6 +55,9 @@ export const AvatarInfoWidgetOwnAvatarView: FC<AvatarInfoWidgetOwnAvatarViewProp
|
||||
case 'change_looks':
|
||||
CreateLinkEvent('avatar-editor/show');
|
||||
break;
|
||||
case 'avatar_effect':
|
||||
CreateLinkEvent('avatar-effects/show');
|
||||
break;
|
||||
case 'expressions':
|
||||
hideMenu = false;
|
||||
setMode(MODE_EXPRESSIONS);
|
||||
@@ -137,6 +140,9 @@ export const AvatarInfoWidgetOwnAvatarView: FC<AvatarInfoWidgetOwnAvatarViewProp
|
||||
<ContextMenuListItemView onClick={ event => processAction('change_looks') }>
|
||||
{ LocalizeText('widget.memenu.myclothes') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('avatar_effect') }>
|
||||
{ LocalizeText('product.type.effect') }
|
||||
</ContextMenuListItemView>
|
||||
{ (HasHabboClub() && !isRidingHorse) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('dance_menu') }>
|
||||
<FaChevronRight className="right fa-icon" />
|
||||
|
||||
@@ -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<ChatInputEmojiSelectorViewProps> = p
|
||||
setSelectorVisible(false);
|
||||
};
|
||||
|
||||
const toggleSelector = () => setSelectorVisible(prev => !prev);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Popover
|
||||
containerClassName="z-[1070]"
|
||||
content={ <Picker data={ data } onEmojiSelect={ handleEmojiSelect } /> }
|
||||
isOpen={ selectorVisible }
|
||||
positions={ [ 'top' ] }
|
||||
onClickOutside={ () => setSelectorVisible(false) }
|
||||
>
|
||||
<div className="cursor-pointer text-lg select-none px-1" onClick={ toggleSelector }>🙂</div>
|
||||
</Popover>
|
||||
</div>
|
||||
<Popover.Root open={ selectorVisible } onOpenChange={ setSelectorVisible }>
|
||||
<Popover.Trigger asChild>
|
||||
<div className="cursor-pointer text-lg select-none px-1">🙂</div>
|
||||
</Popover.Trigger>
|
||||
<Popover.Portal>
|
||||
<Popover.Content className="z-[1070]" side="top" sideOffset={ 8 }>
|
||||
<Picker data={ data } onEmojiSelect={ handleEmojiSelect } />
|
||||
</Popover.Content>
|
||||
</Popover.Portal>
|
||||
</Popover.Root>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<ChatInputStyleSelectorViewProps> = p
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover
|
||||
padding={12}
|
||||
isOpen={selectorVisible}
|
||||
positions={['top']}
|
||||
reposition={false}
|
||||
containerClassName="max-w-[276px] not-italic font-normal leading-normal text-left no-underline text-shadow-none normal-case tracking-[normal] [word-break:normal] [word-spacing:normal] whitespace-normal text-[.7875rem] [word-wrap:break-word] bg-[#dfdfdf] bg-clip-padding border border-[solid] border-[#283F5D] rounded-[.25rem] [box-shadow:0_2px_#00000073] z-1070"
|
||||
content={({ position, childRect, popoverRect }) => (
|
||||
<ArrowContainer
|
||||
arrowColor={'black'}
|
||||
arrowSize={7}
|
||||
arrowStyle={{ bottom: 'calc(-.5rem - 1px)' }}
|
||||
childRect={childRect}
|
||||
popoverRect={popoverRect}
|
||||
position={position}
|
||||
<Popover.Root open={selectorVisible} onOpenChange={setSelectorVisible}>
|
||||
<Popover.Trigger asChild>
|
||||
<div className="chatstyles-anchor">
|
||||
<div className="nitro-icon chatstyles-icon" />
|
||||
</div>
|
||||
</Popover.Trigger>
|
||||
<Popover.Portal>
|
||||
<Popover.Content
|
||||
side="top"
|
||||
sideOffset={12}
|
||||
className="max-w-[276px] not-italic font-normal leading-normal text-left no-underline normal-case tracking-normal whitespace-normal text-[.7875rem] [word-wrap:break-word] bg-[#dfdfdf] bg-clip-padding border border-solid border-[#283F5D] rounded-[.25rem] [box-shadow:0_2px_#00000073] z-[1070]"
|
||||
>
|
||||
<NitroCardContentView className="bg-transparent max-h-[210px]!" overflow="hidden">
|
||||
<Grid columnCount={3} overflow="auto">
|
||||
@@ -47,15 +44,9 @@ export const ChatInputStyleSelectorView: FC<ChatInputStyleSelectorViewProps> = p
|
||||
))}
|
||||
</Grid>
|
||||
</NitroCardContentView>
|
||||
</ArrowContainer>
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="chatstyles-anchor"
|
||||
onClick={() => setSelectorVisible(v => !v)}
|
||||
>
|
||||
<div className="nitro-icon chatstyles-icon" />
|
||||
</div>
|
||||
</Popover>
|
||||
<Popover.Arrow className="fill-black" width={14} height={7} />
|
||||
</Popover.Content>
|
||||
</Popover.Portal>
|
||||
</Popover.Root>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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<any>(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<ReactPlayer>(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 (
|
||||
<NitroCardView className="youtube-tv-widget">
|
||||
@@ -85,7 +37,26 @@ export const FurnitureYoutubeDisplayView: FC<{}> = FurnitureYoutubeDisplayViewPr
|
||||
<div className="row size-full">
|
||||
<div className="youtube-video-container col-span-9 overflow-hidden">
|
||||
{ (videoId && videoId.length > 0) &&
|
||||
<YouTube containerClassName={ 'youtubeContainer' } opts={ youtubeOptions } videoId={ videoId } onReady={ event => setPlayer(event.target) } onStateChange={ onStateChange } />
|
||||
<ReactPlayer
|
||||
ref={ playerRef }
|
||||
url={ `https://www.youtube.com/watch?v=${ videoId }` }
|
||||
width={ 500 }
|
||||
height={ 375 }
|
||||
playing={ playing }
|
||||
controls={ false }
|
||||
onPlay={ handlePlay }
|
||||
onPause={ handlePause }
|
||||
config={ {
|
||||
playerVars: {
|
||||
autoplay: 1,
|
||||
disablekb: 1,
|
||||
controls: 0,
|
||||
origin: window.origin,
|
||||
modestbranding: 1,
|
||||
start: videoStart,
|
||||
end: videoEnd
|
||||
}
|
||||
} } />
|
||||
}
|
||||
{ (!videoId || videoId.length === 0) &&
|
||||
<div className="empty-video size-full justify-center items-center flex">{ LocalizeText('widget.furni.video_viewer.no_videos') }</div>
|
||||
|
||||
Reference in New Issue
Block a user