@feature/jukebox
37
src/api/room/widgets/GetDiskColor.ts
Normal file
@ -0,0 +1,37 @@
|
||||
const DISK_COLOR_RED_MIN: number = 130;
|
||||
const DISK_COLOR_RED_RANGE: number = 100;
|
||||
const DISK_COLOR_GREEN_MIN: number = 130;
|
||||
const DISK_COLOR_GREEN_RANGE: number = 100;
|
||||
const DISK_COLOR_BLUE_MIN: number = 130;
|
||||
const DISK_COLOR_BLUE_RANGE: number = 100;
|
||||
|
||||
export const GetDiskColor = (name: string) =>
|
||||
{
|
||||
let r: number = 0;
|
||||
let g: number = 0;
|
||||
let b: number = 0;
|
||||
let index: number = 0;
|
||||
|
||||
while (index < name.length)
|
||||
{
|
||||
switch ((index % 3))
|
||||
{
|
||||
case 0:
|
||||
r = (r + ( name.charCodeAt(index) * 37) );
|
||||
break;
|
||||
case 1:
|
||||
g = (g + ( name.charCodeAt(index) * 37) );
|
||||
break;
|
||||
case 2:
|
||||
b = (b + ( name.charCodeAt(index) * 37) );
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
r = ((r % DISK_COLOR_RED_RANGE) + DISK_COLOR_RED_MIN);
|
||||
g = ((g % DISK_COLOR_GREEN_RANGE) + DISK_COLOR_GREEN_MIN);
|
||||
b = ((b % DISK_COLOR_BLUE_RANGE) + DISK_COLOR_BLUE_MIN);
|
||||
|
||||
return `rgb(${ r },${ g },${ b })`;
|
||||
}
|
@ -10,6 +10,7 @@ export * from './ChatMessageTypeEnum';
|
||||
export * from './DimmerFurnitureWidgetPresetItem';
|
||||
export * from './DoChatsOverlap';
|
||||
export * from './FurnitureDimmerUtilities';
|
||||
export * from './GetDiskColor';
|
||||
export * from './IAvatarInfo';
|
||||
export * from './ICraftingIngredient';
|
||||
export * from './ICraftingRecipe';
|
||||
|
BIN
src/assets/images/infostand/disk-creator.png
Normal file
After Width: | Height: | Size: 189 B |
BIN
src/assets/images/infostand/disk-icon.png
Normal file
After Width: | Height: | Size: 240 B |
BIN
src/assets/images/room-widgets/playlist-editor/disk_2.png
Normal file
After Width: | Height: | Size: 680 B |
BIN
src/assets/images/room-widgets/playlist-editor/disk_image.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
src/assets/images/room-widgets/playlist-editor/move.png
Normal file
After Width: | Height: | Size: 164 B |
BIN
src/assets/images/room-widgets/playlist-editor/pause-btn.png
Normal file
After Width: | Height: | Size: 111 B |
BIN
src/assets/images/room-widgets/playlist-editor/pause.png
Normal file
After Width: | Height: | Size: 114 B |
BIN
src/assets/images/room-widgets/playlist-editor/playing.png
Normal file
After Width: | Height: | Size: 309 B |
BIN
src/assets/images/room-widgets/playlist-editor/preview.png
Normal file
After Width: | Height: | Size: 147 B |
@ -200,6 +200,18 @@
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
&.disk-icon {
|
||||
background-image: url('../images/infostand/disk-icon.png');
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
&.disk-creator {
|
||||
background-image: url('../images/infostand/disk-creator.png');
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
&.trade-locked-icon {
|
||||
background-image: url("../images/inventory/trading/locked-icon.png");
|
||||
width: 29px;
|
||||
@ -595,6 +607,7 @@
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
}
|
||||
|
||||
&.icon-user {
|
||||
background-image: url("../images/icons/user.png");
|
||||
width: 18px;
|
||||
|
@ -52,7 +52,7 @@ export const LayoutPetImageView: FC<LayoutPetImageViewProps> = props =>
|
||||
let petTypeId = typeId;
|
||||
let petPaletteId = paletteId;
|
||||
let petColor1 = petColor;
|
||||
let petCustomParts = customParts;
|
||||
let petCustomParts: IPetCustomPart[] = customParts;
|
||||
let petHeadOnly = headOnly;
|
||||
|
||||
if(figure && figure.length)
|
||||
|
@ -0,0 +1,113 @@
|
||||
import { GetOfficialSongIdMessageComposer, MusicPriorities, OfficialSongIdMessageEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { GetConfiguration, GetNitroInstance, LocalizeText, ProductTypeEnum, SendMessageComposer } from '../../../../../api';
|
||||
import { Button, Column, Flex, Grid, LayoutImage, Text } from '../../../../../common';
|
||||
import { useCatalog, useMessageEvent } from '../../../../../hooks';
|
||||
import { CatalogHeaderView } from '../../catalog-header/CatalogHeaderView';
|
||||
import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView';
|
||||
import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView';
|
||||
import { CatalogLimitedItemWidgetView } from '../widgets/CatalogLimitedItemWidgetView';
|
||||
import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView';
|
||||
import { CatalogSpinnerWidgetView } from '../widgets/CatalogSpinnerWidgetView';
|
||||
import { CatalogTotalPriceWidget } from '../widgets/CatalogTotalPriceWidget';
|
||||
import { CatalogViewProductWidgetView } from '../widgets/CatalogViewProductWidgetView';
|
||||
import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||
|
||||
export const CatalogLayoutSoundMachineView: FC<CatalogLayoutProps> = props =>
|
||||
{
|
||||
const { page = null } = props;
|
||||
const [ songId, setSongId ] = useState(-1);
|
||||
const [ officialSongId, setOfficialSongId ] = useState('');
|
||||
const { currentOffer = null, currentPage = null } = useCatalog();
|
||||
|
||||
const previewSong = (previewSongId: number) => GetNitroInstance().soundManager.musicController?.playSong(previewSongId, MusicPriorities.PRIORITY_PURCHASE_PREVIEW, 15, 0, 0, 0);
|
||||
|
||||
useMessageEvent<OfficialSongIdMessageEvent>(OfficialSongIdMessageEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if(parser.officialSongId !== officialSongId) return;
|
||||
|
||||
setSongId(parser.songId);
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!currentOffer) return;
|
||||
|
||||
const product = currentOffer.product;
|
||||
|
||||
if(!product) return;
|
||||
|
||||
if(product.extraParam.length > 0)
|
||||
{
|
||||
const id = parseInt(product.extraParam);
|
||||
|
||||
if(id > 0)
|
||||
{
|
||||
setSongId(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
setOfficialSongId(product.extraParam);
|
||||
SendMessageComposer(new GetOfficialSongIdMessageComposer(product.extraParam));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
setOfficialSongId('');
|
||||
setSongId(-1);
|
||||
}
|
||||
|
||||
return () => GetNitroInstance().soundManager.musicController?.stop(MusicPriorities.PRIORITY_PURCHASE_PREVIEW);
|
||||
}, [ currentOffer ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
return () => GetNitroInstance().soundManager.musicController?.stop(MusicPriorities.PRIORITY_PURCHASE_PREVIEW);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid>
|
||||
<Column size={ 7 } overflow="hidden">
|
||||
{ GetConfiguration('catalog.headers') &&
|
||||
<CatalogHeaderView imageUrl={ currentPage.localization.getImage(0) }/> }
|
||||
<CatalogItemGridWidgetView />
|
||||
</Column>
|
||||
<Column center={ !currentOffer } size={ 5 } overflow="hidden">
|
||||
{ !currentOffer &&
|
||||
<>
|
||||
{ !!page.localization.getImage(1) &&
|
||||
<LayoutImage imageUrl={ page.localization.getImage(1) } /> }
|
||||
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
|
||||
</> }
|
||||
{ currentOffer &&
|
||||
<>
|
||||
<Flex center overflow="hidden" style={ { height: 140 } }>
|
||||
{ (currentOffer.product.productType !== ProductTypeEnum.BADGE) &&
|
||||
<>
|
||||
<CatalogViewProductWidgetView />
|
||||
<CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-1 end-1" />
|
||||
</> }
|
||||
{ (currentOffer.product.productType === ProductTypeEnum.BADGE) && <CatalogAddOnBadgeWidgetView className="scale-2" /> }
|
||||
</Flex>
|
||||
<Column grow gap={ 1 }>
|
||||
<CatalogLimitedItemWidgetView fullWidth />
|
||||
<Text grow truncate>{ currentOffer.localizationName }</Text>
|
||||
{ songId > -1 && <Button onClick={ () => previewSong(songId) }>{ LocalizeText('play_preview_button') }</Button>
|
||||
}
|
||||
<Flex justifyContent="between">
|
||||
<Column gap={ 1 }>
|
||||
<CatalogSpinnerWidgetView />
|
||||
</Column>
|
||||
<CatalogTotalPriceWidget justifyContent="end" alignItems="end" />
|
||||
</Flex>
|
||||
<CatalogPurchaseWidgetView />
|
||||
</Column>
|
||||
</> }
|
||||
</Column>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
@ -12,6 +12,7 @@ import { CatalogLayoutPets3View } from './CatalogLayoutPets3View';
|
||||
import { CatalogLayoutRoomAdsView } from './CatalogLayoutRoomAdsView';
|
||||
import { CatalogLayoutRoomBundleView } from './CatalogLayoutRoomBundleView';
|
||||
import { CatalogLayoutSingleBundleView } from './CatalogLayoutSingleBundleView';
|
||||
import { CatalogLayoutSoundMachineView } from './CatalogLayoutSoundMachineView';
|
||||
import { CatalogLayoutSpacesView } from './CatalogLayoutSpacesView';
|
||||
import { CatalogLayoutTrophiesView } from './CatalogLayoutTrophiesView';
|
||||
import { CatalogLayoutVipBuyView } from './CatalogLayoutVipBuyView';
|
||||
@ -69,6 +70,8 @@ export const GetCatalogLayout = (page: ICatalogPage, hideNavigation: () => void)
|
||||
return <CatalogLayoutRoomAdsView { ...layoutProps } />;
|
||||
case 'default_3x3_color_grouping':
|
||||
return <CatalogLayoutColorGroupingView { ...layoutProps } />;
|
||||
case 'soundmachine':
|
||||
return <CatalogLayoutSoundMachineView { ...layoutProps } />;
|
||||
case 'bots':
|
||||
case 'default_3x3':
|
||||
default:
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { CrackableDataType, GroupInformationComposer, GroupInformationEvent, RoomControllerLevel, RoomObjectCategory, RoomObjectOperationType, RoomObjectVariable, RoomWidgetEnumItemExtradataParameter, RoomWidgetFurniInfoUsagePolicyEnum, SetObjectDataMessageComposer, StringDataType } from '@nitrots/nitro-renderer';
|
||||
import { CrackableDataType, GroupInformationComposer, GroupInformationEvent, NowPlayingEvent, RoomControllerLevel, RoomObjectCategory, RoomObjectOperationType, RoomObjectVariable, RoomWidgetEnumItemExtradataParameter, RoomWidgetFurniInfoUsagePolicyEnum, SetObjectDataMessageComposer, SongInfoReceivedEvent, StringDataType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { AvatarInfoFurni, CreateLinkEvent, GetGroupInformation, GetRoomEngine, LocalizeText, SendMessageComposer } from '../../../../../api';
|
||||
import { Button, Column, Flex, LayoutBadgeImageView, LayoutLimitedEditionCompactPlateView, LayoutRarityLevelView, Text, UserProfileIconView } from '../../../../../common';
|
||||
import { useMessageEvent, useRoom } from '../../../../../hooks';
|
||||
import { AvatarInfoFurni, CreateLinkEvent, GetGroupInformation, GetNitroInstance, GetRoomEngine, LocalizeText, SendMessageComposer } from '../../../../../api';
|
||||
import { Base, Button, Column, Flex, LayoutBadgeImageView, LayoutLimitedEditionCompactPlateView, LayoutRarityLevelView, Text, UserProfileIconView } from '../../../../../common';
|
||||
import { useMessageEvent, useRoom, useSoundEvent } from '../../../../../hooks';
|
||||
|
||||
interface InfoStandWidgetFurniViewProps
|
||||
{
|
||||
@ -34,6 +34,28 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
||||
const [ godMode, setGodMode ] = useState(false);
|
||||
const [ canSeeFurniId, setCanSeeFurniId ] = useState(false);
|
||||
const [ groupName, setGroupName ] = useState<string>(null);
|
||||
const [ isJukeBox, setIsJukeBox ] = useState<boolean>(false);
|
||||
const [ isSongDisk, setIsSongDisk ] = useState<boolean>(false);
|
||||
const [ songId, setSongId ] = useState<number>(-1);
|
||||
const [ songName, setSongName ] = useState<string>('');
|
||||
const [ songCreator, setSongCreator ] = useState<string>('');
|
||||
|
||||
useSoundEvent<NowPlayingEvent>(NowPlayingEvent.NPE_SONG_CHANGED, event =>
|
||||
{
|
||||
setSongId(event.id);
|
||||
}, (isJukeBox || isSongDisk));
|
||||
|
||||
useSoundEvent<NowPlayingEvent>(SongInfoReceivedEvent.SIR_TRAX_SONG_INFO_RECEIVED, event =>
|
||||
{
|
||||
if(event.id !== songId) return;
|
||||
|
||||
const songInfo = GetNitroInstance().soundManager.musicController.getSongInfo(event.id);
|
||||
|
||||
if(!songInfo) return;
|
||||
|
||||
setSongName(songInfo.name);
|
||||
setSongCreator(songInfo.creator);
|
||||
}, (isJukeBox || isSongDisk));
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
@ -50,6 +72,9 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
||||
let crackableTarget = 0;
|
||||
let godMode = false;
|
||||
let canSeeFurniId = false;
|
||||
let furniIsJukebox = false;
|
||||
let furniIsSongDisk = false;
|
||||
let furniSongId = -1;
|
||||
|
||||
const isValidController = (avatarInfo.roomControllerLevel >= RoomControllerLevel.GUEST);
|
||||
|
||||
@ -80,6 +105,25 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
||||
crackableTarget = stuffData.target;
|
||||
}
|
||||
|
||||
else if(avatarInfo.extraParam === RoomWidgetEnumItemExtradataParameter.JUKEBOX)
|
||||
{
|
||||
const playlist = GetNitroInstance().soundManager.musicController.getRoomItemPlaylist();
|
||||
|
||||
if(playlist)
|
||||
{
|
||||
furniSongId = playlist.nowPlayingSongId;
|
||||
}
|
||||
|
||||
furniIsJukebox = true;
|
||||
}
|
||||
|
||||
else if(avatarInfo.extraParam.indexOf(RoomWidgetEnumItemExtradataParameter.SONGDISK) === 0)
|
||||
{
|
||||
furniSongId = parseInt(avatarInfo.extraParam.substr(RoomWidgetEnumItemExtradataParameter.SONGDISK.length));
|
||||
|
||||
furniIsSongDisk = true;
|
||||
}
|
||||
|
||||
if(godMode)
|
||||
{
|
||||
const extraParam = avatarInfo.extraParam.substr(RoomWidgetEnumItemExtradataParameter.BRANDING_OPTIONS.length);
|
||||
@ -142,6 +186,9 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
||||
setGodMode(godMode);
|
||||
setCanSeeFurniId(canSeeFurniId);
|
||||
setGroupName(null);
|
||||
setIsJukeBox(furniIsJukebox);
|
||||
setIsSongDisk(furniIsSongDisk);
|
||||
setSongId(furniSongId);
|
||||
|
||||
if(avatarInfo.groupId) SendMessageComposer(new GroupInformationComposer(avatarInfo.groupId, false));
|
||||
}, [ roomSession, avatarInfo ]);
|
||||
@ -157,6 +204,14 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
||||
setGroupName(parser.title);
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const songInfo = GetNitroInstance().soundManager.musicController.getSongInfo(songId);
|
||||
|
||||
setSongName(songInfo?.name ?? '');
|
||||
setSongCreator(songInfo?.creator ?? '');
|
||||
}, [ songId ]);
|
||||
|
||||
const onFurniSettingChange = useCallback((index: number, value: string) =>
|
||||
{
|
||||
const clone = Array.from(furniValues);
|
||||
@ -315,6 +370,28 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
||||
</Text>
|
||||
</Flex> }
|
||||
</Column>
|
||||
{ (isJukeBox || isSongDisk) &&
|
||||
<Column gap={ 1 }>
|
||||
<hr className="m-0" />
|
||||
{ (songId === -1) &&
|
||||
<Text variant="white" small wrap>
|
||||
{ LocalizeText('infostand.jukebox.text.not.playing') }
|
||||
</Text> }
|
||||
{ !!songName.length &&
|
||||
<Flex alignItems="center" gap={ 1 }>
|
||||
<Base className="icon disk-icon" />
|
||||
<Text variant="white" small wrap>
|
||||
{ songName }
|
||||
</Text>
|
||||
</Flex> }
|
||||
{ !!songCreator.length &&
|
||||
<Flex alignItems="center" gap={ 1 }>
|
||||
<Base className="icon disk-creator" />
|
||||
<Text variant="white" small wrap>
|
||||
{ songCreator }
|
||||
</Text>
|
||||
</Flex> }
|
||||
</Column> }
|
||||
<Column gap={ 1 }>
|
||||
{ isCrackable &&
|
||||
<>
|
||||
|
@ -49,7 +49,6 @@
|
||||
}
|
||||
|
||||
.nitro-external-image-widget {
|
||||
|
||||
.picture-preview {
|
||||
width: 320px;
|
||||
height: 320px;
|
||||
@ -62,7 +61,8 @@
|
||||
color: black;
|
||||
}
|
||||
|
||||
.picture-preview-buttons-previous, .picture-preview-buttons-next {
|
||||
.picture-preview-buttons-previous,
|
||||
.picture-preview-buttons-next {
|
||||
color: #222;
|
||||
background-color: white;
|
||||
padding: 10px;
|
||||
@ -221,14 +221,14 @@
|
||||
width: 31px;
|
||||
height: 39px;
|
||||
background-position: -380px -43px;
|
||||
background-image: url('../../../../assets/images/room-widgets/engraving-lock-widget/engraving-lock-spritesheet.png');
|
||||
background-image: url("../../../../assets/images/room-widgets/engraving-lock-widget/engraving-lock-spritesheet.png");
|
||||
}
|
||||
|
||||
.engraving-lock-stage-2 {
|
||||
width: 36px;
|
||||
height: 43px;
|
||||
background-position: -375px 0px;
|
||||
background-image: url('../../../../assets/images/room-widgets/engraving-lock-widget/engraving-lock-spritesheet.png');
|
||||
background-image: url("../../../../assets/images/room-widgets/engraving-lock-widget/engraving-lock-spritesheet.png");
|
||||
}
|
||||
}
|
||||
|
||||
@ -236,7 +236,7 @@
|
||||
width: 375px;
|
||||
height: 210px;
|
||||
background-position: 0px 0px;
|
||||
background-image: url('../../../../assets/images/room-widgets/engraving-lock-widget/engraving-lock-spritesheet.png');
|
||||
background-image: url("../../../../assets/images/room-widgets/engraving-lock-widget/engraving-lock-spritesheet.png");
|
||||
|
||||
color: #622e54;
|
||||
font-weight: bold;
|
||||
@ -251,7 +251,7 @@
|
||||
&.engraving-lock-4 {
|
||||
background-position: 0px -420px;
|
||||
color: #f1dcc8;
|
||||
text-shadow: 0px 2px rgba(0, 0, 0, .4);
|
||||
text-shadow: 0px 2px rgba(0, 0, 0, 0.4);
|
||||
|
||||
.engraving-lock-avatar {
|
||||
margin-bottom: 10px;
|
||||
@ -338,6 +338,7 @@
|
||||
|
||||
.playlist-controls {
|
||||
width: 100%;
|
||||
|
||||
.icon {
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
@ -350,3 +351,104 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-playlist-editor-widget {
|
||||
width: 625px;
|
||||
height: 440px;
|
||||
|
||||
img.my-music {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
left: -4px;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
img.playlist-img {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
img.get-more,
|
||||
img.add-songs {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.playlist-bottom {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.move-disk {
|
||||
width: 22px;
|
||||
height: 18px;
|
||||
background-image: url("../../../../assets/images/room-widgets/playlist-editor/move.png");
|
||||
}
|
||||
|
||||
.disk-2,
|
||||
.disk-image {
|
||||
background-blend-mode: multiply;
|
||||
}
|
||||
|
||||
.disk-2 {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
background-image: url("../../../../assets/images/room-widgets/playlist-editor/disk_2.png");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
&.playing-song {
|
||||
background-image: url("../../../../assets/images/room-widgets/playlist-editor/playing.png");
|
||||
}
|
||||
|
||||
&.selected-song {
|
||||
background-image: url("../../../../assets/images/room-widgets/playlist-editor/move.png");
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
&:not(.playing-song):not(.selected-song) {
|
||||
-webkit-mask-image: url("../../../../assets/images/room-widgets/playlist-editor/disk_2.png");
|
||||
}
|
||||
}
|
||||
|
||||
.pause-song {
|
||||
width: 18px;
|
||||
height: 20px;
|
||||
background-image: url("../../../../assets/images/room-widgets/playlist-editor/pause.png");
|
||||
}
|
||||
|
||||
.pause-btn {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
background-image: url("../../../../assets/images/room-widgets/playlist-editor/pause-btn.png");
|
||||
}
|
||||
|
||||
.music-note {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
background-image: url("../../../../assets/images/room-widgets/playlist-editor/playing.png");
|
||||
}
|
||||
|
||||
.preview-song {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-image: url("../../../../assets/images/room-widgets/playlist-editor/preview.png");
|
||||
}
|
||||
|
||||
.layout-grid-item {
|
||||
min-height: 95px;
|
||||
min-width: 95px;
|
||||
position: relative;
|
||||
|
||||
.disk-image {
|
||||
background: url("../../../../assets/images/room-widgets/playlist-editor/disk_image.png");
|
||||
-webkit-mask-image: url("../../../../assets/images/room-widgets/playlist-editor/disk_image.png");
|
||||
height: 76px;
|
||||
width: 76px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import { FurnitureStackHeightView } from './FurnitureStackHeightView';
|
||||
import { FurnitureStickieView } from './FurnitureStickieView';
|
||||
import { FurnitureTrophyView } from './FurnitureTrophyView';
|
||||
import { FurnitureYoutubeDisplayView } from './FurnitureYoutubeDisplayView';
|
||||
import { FurniturePlaylistEditorWidgetView } from './playlist-editor/FurniturePlaylistEditorWidgetView';
|
||||
|
||||
export const FurnitureWidgetsView: FC<{}> = props =>
|
||||
{
|
||||
@ -34,6 +35,7 @@ export const FurnitureWidgetsView: FC<{}> = props =>
|
||||
<FurnitureHighScoreView />
|
||||
<FurnitureInternalLinkView />
|
||||
<FurnitureMannequinView />
|
||||
<FurniturePlaylistEditorWidgetView />
|
||||
<FurnitureRoomLinkView />
|
||||
<FurnitureSpamWallPostItView />
|
||||
<FurnitureStackHeightView />
|
||||
|
@ -0,0 +1,89 @@
|
||||
import { IAdvancedMap, MusicPriorities } from '@nitrots/nitro-renderer';
|
||||
import { FC, MouseEvent, useCallback, useEffect, useState } from 'react';
|
||||
import { GetConfiguration, GetDiskColor, GetNitroInstance, LocalizeText } from '../../../../../api';
|
||||
import { AutoGrid, Base, Button, Flex, LayoutGridItem, Text } from '../../../../../common';
|
||||
|
||||
export interface DiskInventoryViewProps
|
||||
{
|
||||
diskInventory: IAdvancedMap<number, number>;
|
||||
addToPlaylist: (diskId: number, slotNumber: number) => void;
|
||||
}
|
||||
|
||||
export const DiskInventoryView: FC<DiskInventoryViewProps> = props =>
|
||||
{
|
||||
const { diskInventory = null, addToPlaylist = null } = props;
|
||||
const [ selectedItem, setSelectedItem ] = useState<number>(-1);
|
||||
const [ previewSongId, setPreviewSongId ] = useState<number>(-1);
|
||||
|
||||
const previewSong = useCallback((event: MouseEvent, songId: number) =>
|
||||
{
|
||||
event.stopPropagation();
|
||||
|
||||
setPreviewSongId(prevValue => (prevValue === songId) ? -1 : songId);
|
||||
}, []);
|
||||
|
||||
const addSong = useCallback((event: MouseEvent, diskId: number) =>
|
||||
{
|
||||
event.stopPropagation();
|
||||
|
||||
addToPlaylist(diskId, GetNitroInstance().soundManager.musicController?.getRoomItemPlaylist()?.length)
|
||||
}, [ addToPlaylist ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(previewSongId === -1) return;
|
||||
|
||||
GetNitroInstance().soundManager.musicController?.playSong(previewSongId, MusicPriorities.PRIORITY_SONG_PLAY, 0, 0, 0, 0);
|
||||
|
||||
return () =>
|
||||
{
|
||||
GetNitroInstance().soundManager.musicController?.stop(MusicPriorities.PRIORITY_SONG_PLAY);
|
||||
}
|
||||
}, [ previewSongId ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
return () => setPreviewSongId(-1);
|
||||
}, []);
|
||||
|
||||
return (<>
|
||||
<div className="bg-success py-3 container-fluid justify-content-center d-flex rounded">
|
||||
<img src={ GetConfiguration('image.library.url') + 'playlist/title_mymusic.gif' } className="my-music" />
|
||||
<h2 className="ms-4">{ LocalizeText('playlist.editor.my.music') }</h2>
|
||||
</div>
|
||||
<div className="h-100 overflow-y-scroll mt-4 py-2">
|
||||
<AutoGrid columnCount={ 3 } columnMinWidth={ 95 } gap={ 1 }>
|
||||
{ diskInventory && diskInventory.getKeys().map( (key, index) =>
|
||||
{
|
||||
const diskId = diskInventory.getKey(index);
|
||||
const songId = diskInventory.getWithIndex(index);
|
||||
const songData = GetNitroInstance().soundManager.musicController?.getSongInfo(songId);
|
||||
|
||||
return (
|
||||
<LayoutGridItem key={ index } itemActive={ (selectedItem === index) } onClick={ () => setSelectedItem(prev => prev === index ? -1 : index) } classNames={ [ 'text-black' ] }>
|
||||
<div className="disk-image flex-shrink-0 mb-n2" style={ { backgroundColor: GetDiskColor(songData?.songData) } }>
|
||||
</div>
|
||||
<Text truncate fullWidth className="text-center">{ songData?.name }</Text>
|
||||
{ (selectedItem === index) &&
|
||||
<Flex position="absolute" className="bottom-0 mb-1 bg-secondary p-1 rounded" alignItems="center" justifyContent="center" gap={ 2 }>
|
||||
<Button onClick={ event => previewSong(event, songId) } variant="light">
|
||||
<Base className={ (previewSongId === songId) ? 'pause-btn' : 'preview-song' }/>
|
||||
</Button>
|
||||
<Button onClick={ event => addSong(event, diskId) } variant="light">
|
||||
<Base className="move-disk"/>
|
||||
</Button>
|
||||
</Flex>
|
||||
}
|
||||
</LayoutGridItem>)
|
||||
}) }
|
||||
</AutoGrid>
|
||||
</div>
|
||||
<div className="playlist-bottom text-black p-1">
|
||||
<h5>{ LocalizeText('playlist.editor.text.get.more.music') }</h5>
|
||||
<div>{ LocalizeText('playlist.editor.text.you.have.no.songdisks.available') }</div>
|
||||
<div>{ LocalizeText('playlist.editor.text.you.can.buy.some.from.the.catalogue') }</div>
|
||||
<button className="btn btn-primary btn-sm">{ LocalizeText('playlist.editor.button.open.catalogue') }</button>
|
||||
</div>
|
||||
<img src={ GetConfiguration('image.library.url') + 'playlist/background_get_more_music.gif' } className="get-more" />
|
||||
</>);
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import { FC } from 'react';
|
||||
import { LocalizeText } from '../../../../../api';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../common';
|
||||
import { useFurniturePlaylistEditorWidget } from '../../../../../hooks';
|
||||
import { DiskInventoryView } from './DiskInventoryView';
|
||||
import { SongPlaylistView } from './SongPlaylistView';
|
||||
|
||||
export const FurniturePlaylistEditorWidgetView: FC<{}> = props =>
|
||||
{
|
||||
const { objectId = -1, currentPlayingIndex = -1, playlist = null, diskInventory = null, onClose = null, togglePlayPause = null, removeFromPlaylist = null, addToPlaylist = null } = useFurniturePlaylistEditorWidget();
|
||||
|
||||
if(objectId === -1) return null;
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-playlist-editor-widget" theme="primary-slim">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('playlist.editor.title') } onCloseClick={ onClose } />
|
||||
<NitroCardContentView>
|
||||
<div className="d-flex flex-row gap-1 h-100">
|
||||
<div className="w-50 position-relative overflow-hidden h-100 rounded d-flex flex-column">
|
||||
<DiskInventoryView addToPlaylist={ addToPlaylist } diskInventory={ diskInventory } />
|
||||
</div>
|
||||
<div className="w-50 position-relative overflow-hidden h-100 rounded d-flex flex-column">
|
||||
<SongPlaylistView furniId={ objectId } removeFromPlaylist={ removeFromPlaylist } playlist={ playlist } togglePlayPause={ togglePlayPause } currentPlayingIndex={ currentPlayingIndex }/>
|
||||
</div>
|
||||
</div>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
import { ISongInfo } from '@nitrots/nitro-renderer';
|
||||
import { FC, useState } from 'react';
|
||||
import { GetConfiguration, GetDiskColor, LocalizeText } from '../../../../../api';
|
||||
import { Base, Button, Flex, Text } from '../../../../../common';
|
||||
|
||||
export interface SongPlaylistViewProps
|
||||
{
|
||||
furniId: number;
|
||||
playlist: ISongInfo[];
|
||||
currentPlayingIndex: number;
|
||||
removeFromPlaylist(slotNumber: number): void;
|
||||
togglePlayPause(furniId: number, position: number): void;
|
||||
}
|
||||
|
||||
export const SongPlaylistView: FC<SongPlaylistViewProps> = props =>
|
||||
{
|
||||
const { furniId = -1, playlist = null, currentPlayingIndex = -1, removeFromPlaylist = null, togglePlayPause = null } = props;
|
||||
const [ selectedItem, setSelectedItem ] = useState<number>(-1);
|
||||
|
||||
const action = (index: number) =>
|
||||
{
|
||||
if(selectedItem === index) removeFromPlaylist(index);
|
||||
}
|
||||
|
||||
const playPause = (furniId: number, selectedItem: number) =>
|
||||
{
|
||||
togglePlayPause(furniId, selectedItem !== -1 ? selectedItem : 0 )
|
||||
}
|
||||
|
||||
return (<>
|
||||
<div className="bg-primary py-3 container-fluid justify-content-center d-flex rounded">
|
||||
<img src={ GetConfiguration('image.library.url') + 'playlist/title_playlist.gif' } className="playlist-img" />
|
||||
<h2 className="ms-4">{ LocalizeText('playlist.editor.playlist') }</h2>
|
||||
</div>
|
||||
<div className="h-100 overflow-y-scroll py-2">
|
||||
<Flex column gap={ 2 }>
|
||||
{ playlist && playlist.map( (songInfo, index) =>
|
||||
{
|
||||
return <Flex gap={ 1 } key={ index } className={ 'text-black cursor-pointer ' + (selectedItem === index ? 'border border-muted border-2 rounded' : 'border-2') } alignItems="center" onClick={ () => setSelectedItem(prev => prev === index ? -1 : index) }>
|
||||
<Base onClick={ () => action(index) } className={ 'disk-2 ' + (selectedItem === index ? 'selected-song' : '') } style={ { backgroundColor: (selectedItem === index ? '' : GetDiskColor(songInfo.songData)) } }/>
|
||||
{ songInfo.name }
|
||||
</Flex>
|
||||
}) }
|
||||
|
||||
</Flex>
|
||||
</div>
|
||||
{ (!playlist || playlist.length === 0 ) &&
|
||||
<><div className="playlist-bottom text-black p-1 ms-5">
|
||||
<h5>{ LocalizeText('playlist.editor.add.songs.to.your.playlist') }</h5>
|
||||
<div>{ LocalizeText('playlist.editor.text.click.song.to.choose.click.again.to.move') }</div>
|
||||
</div>
|
||||
<img src={ GetConfiguration('image.library.url') + 'playlist/background_add_songs.gif' } className="add-songs" /></>
|
||||
}
|
||||
{ (playlist && playlist.length > 0) &&
|
||||
<>
|
||||
{ (currentPlayingIndex === -1) &&
|
||||
<Button variant="success" size="lg" onClick={ () => playPause(furniId, selectedItem) }>
|
||||
{ LocalizeText('playlist.editor.button.play.now') }
|
||||
</Button>
|
||||
}
|
||||
{ (currentPlayingIndex !== -1) &&
|
||||
<Flex gap={ 1 }>
|
||||
<Button variant="danger" onClick={ () => playPause(furniId, selectedItem) }>
|
||||
<Base className="pause-song"/>
|
||||
</Button>
|
||||
<Flex column>
|
||||
<Text bold display="block">{ LocalizeText('playlist.editor.text.now.playing.in.your.room') }</Text>
|
||||
<Text>
|
||||
{ playlist[currentPlayingIndex].name + ' - ' + playlist[currentPlayingIndex].creator }
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
</Flex>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
</>);
|
||||
}
|
@ -5,3 +5,4 @@ export * from './useMainEvent';
|
||||
export * from './useRoomEngineEvent';
|
||||
export * from './useRoomSessionManagerEvent';
|
||||
export * from './useSessionDataManagerEvent';
|
||||
export * from './useSoundEvent';
|
||||
|
5
src/hooks/events/nitro/useSoundEvent.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { NitroEvent } from '@nitrots/nitro-renderer';
|
||||
import { GetNitroInstance } from '../../../api';
|
||||
import { useEventDispatcher } from '../useEventDispatcher';
|
||||
|
||||
export const useSoundEvent = <T extends NitroEvent>(type: string | string[], handler: (event: T) => void, enabled = true) => useEventDispatcher(type, GetNitroInstance().soundManager.events, handler, enabled);
|
@ -382,7 +382,7 @@ const useNotificationState = () =>
|
||||
|
||||
useMessageEvent<RoomEnterEvent>(RoomEnterEvent, onRoomEnterEvent);
|
||||
|
||||
return { alerts, bubbleAlerts, confirms, simpleAlert, showNitroAlert, showTradeAlert, showConfirm, closeAlert, closeBubbleAlert, closeConfirm };
|
||||
return { alerts, bubbleAlerts, confirms, simpleAlert, showNitroAlert, showTradeAlert, showConfirm, showSingleBubble, closeAlert, closeBubbleAlert, closeConfirm };
|
||||
}
|
||||
|
||||
export const useNotification = () => useBetween(useNotificationState);
|
||||
|
@ -9,6 +9,7 @@ export * from './useFurnitureFriendFurniWidget';
|
||||
export * from './useFurnitureHighScoreWidget';
|
||||
export * from './useFurnitureInternalLinkWidget';
|
||||
export * from './useFurnitureMannequinWidget';
|
||||
export * from './useFurniturePlaylistEditorWidget';
|
||||
export * from './useFurniturePresentWidget';
|
||||
export * from './useFurnitureRoomLinkWidget';
|
||||
export * from './useFurnitureSpamWallPostItWidget';
|
||||
|
@ -35,10 +35,9 @@ const useFurnitureExternalImageWidgetState = () =>
|
||||
if (object.type !== 'external_image_wallitem_poster_small') return null;
|
||||
|
||||
const data = object.model.getValue<string>(RoomObjectVariable.FURNITURE_DATA);
|
||||
const ownerId = object.model.getValue<number>(RoomObjectVariable.FURNITURE_OWNER_ID);
|
||||
const ownerName = object.model.getValue<string>(RoomObjectVariable.FURNITURE_OWNER_NAME);
|
||||
const jsonData: IPhotoData = JSON.parse(data);
|
||||
|
||||
datas.push({ s: JSON.parse(data).s, t: JSON.parse(data).t, u: JSON.parse(data).u, w: JSON.parse(data).w, oi: ownerId, o: ownerName });
|
||||
datas.push(jsonData);
|
||||
});
|
||||
|
||||
setObjectId(event.objectId);
|
||||
|
@ -0,0 +1,107 @@
|
||||
import { AddJukeboxDiskComposer, AdvancedMap, FurnitureListAddOrUpdateEvent, FurnitureListEvent, FurnitureListRemovedEvent, FurnitureMultiStateComposer, IAdvancedMap, IMessageEvent, ISongInfo, NotifyPlayedSongEvent, NowPlayingEvent, PlayListStatusEvent, RemoveJukeboxDiskComposer, RoomControllerLevel, RoomEngineTriggerWidgetEvent, SongDiskInventoryReceivedEvent } from '@nitrots/nitro-renderer';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { GetNitroInstance, GetRoomEngine, GetSessionDataManager, IsOwnerOfFurniture, LocalizeText, NotificationAlertType, NotificationBubbleType, SendMessageComposer } from '../../../../api';
|
||||
import { useMessageEvent, useRoomEngineEvent, useSoundEvent } from '../../../events';
|
||||
import { useNotification } from '../../../notification';
|
||||
import { useFurniRemovedEvent } from '../../engine';
|
||||
import { useRoom } from '../../useRoom';
|
||||
|
||||
const useFurniturePlaylistEditorWidgetState = () =>
|
||||
{
|
||||
const [ objectId, setObjectId ] = useState(-1);
|
||||
const [ category, setCategory ] = useState(-1);
|
||||
const [ currentPlayingIndex, setCurrentPlayingIndex ] = useState(-1);
|
||||
const [ diskInventory, setDiskInventory ] = useState<IAdvancedMap<number, number>>(new AdvancedMap());
|
||||
const [ playlist, setPlaylist ] = useState<ISongInfo[]>([]);
|
||||
const { roomSession = null } = useRoom();
|
||||
const { showSingleBubble = null, simpleAlert = null } = useNotification();
|
||||
|
||||
const onClose = () =>
|
||||
{
|
||||
setObjectId(-1);
|
||||
setCategory(-1);
|
||||
}
|
||||
|
||||
const addToPlaylist = useCallback((diskId: number, slotNumber: number) => SendMessageComposer(new AddJukeboxDiskComposer(diskId, slotNumber)), []);
|
||||
|
||||
const removeFromPlaylist = useCallback((slotNumber: number) => SendMessageComposer(new RemoveJukeboxDiskComposer(slotNumber)), []);
|
||||
|
||||
const togglePlayPause = useCallback((furniId: number, position: number) => SendMessageComposer(new FurnitureMultiStateComposer(furniId, position)), []);
|
||||
|
||||
useRoomEngineEvent<RoomEngineTriggerWidgetEvent>(RoomEngineTriggerWidgetEvent.REQUEST_PLAYLIST_EDITOR, event =>
|
||||
{
|
||||
const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category);
|
||||
|
||||
if(!roomObject) return;
|
||||
|
||||
if(IsOwnerOfFurniture(roomObject))
|
||||
{
|
||||
// show the editor
|
||||
setObjectId(event.objectId);
|
||||
setCategory(event.category);
|
||||
|
||||
GetNitroInstance().soundManager.musicController?.requestUserSongDisks();
|
||||
GetNitroInstance().soundManager.musicController?.getRoomItemPlaylist()?.requestPlayList();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(roomSession.isRoomOwner || (roomSession.controllerLevel >= RoomControllerLevel.GUEST) || GetSessionDataManager().isModerator) SendMessageComposer(new FurnitureMultiStateComposer(event.objectId, -2));
|
||||
});
|
||||
|
||||
useFurniRemovedEvent(((objectId !== -1) && (category !== -1)), event =>
|
||||
{
|
||||
if((event.id !== objectId) || (event.category !== category)) return;
|
||||
|
||||
onClose();
|
||||
});
|
||||
|
||||
useSoundEvent<NowPlayingEvent>(NowPlayingEvent.NPE_SONG_CHANGED, event =>
|
||||
{
|
||||
console.log(event);
|
||||
setCurrentPlayingIndex(event.position);
|
||||
});
|
||||
|
||||
useSoundEvent<NotifyPlayedSongEvent>(NotifyPlayedSongEvent.NOTIFY_PLAYED_SONG, event =>
|
||||
{
|
||||
showSingleBubble(LocalizeText('soundmachine.notification.playing', [ 'songname', 'songauthor' ], [ event.name, event.creator ]), NotificationBubbleType.SOUNDMACHINE)
|
||||
});
|
||||
|
||||
useSoundEvent<SongDiskInventoryReceivedEvent>(SongDiskInventoryReceivedEvent.SDIR_SONG_DISK_INVENTORY_RECEIVENT_EVENT, event =>
|
||||
{
|
||||
setDiskInventory(GetNitroInstance().soundManager.musicController?.songDiskInventory.clone());
|
||||
});
|
||||
|
||||
useSoundEvent<PlayListStatusEvent>(PlayListStatusEvent.PLUE_PLAY_LIST_UPDATED, event =>
|
||||
{
|
||||
setPlaylist(GetNitroInstance().soundManager.musicController?.getRoomItemPlaylist()?.entries.concat())
|
||||
});
|
||||
|
||||
useSoundEvent<PlayListStatusEvent>(PlayListStatusEvent.PLUE_PLAY_LIST_FULL, event =>
|
||||
{
|
||||
simpleAlert(LocalizeText('playlist.editor.alert.playlist.full'), NotificationAlertType.ALERT, '', '', LocalizeText('playlist.editor.alert.playlist.full.title'));
|
||||
});
|
||||
|
||||
const onFurniListUpdated = useCallback((event : IMessageEvent) =>
|
||||
{
|
||||
if(event instanceof FurnitureListEvent)
|
||||
{
|
||||
if(event.getParser().fragmentNumber === 0)
|
||||
{
|
||||
GetNitroInstance().soundManager.musicController?.requestUserSongDisks();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GetNitroInstance().soundManager.musicController?.requestUserSongDisks();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useMessageEvent(FurnitureListEvent, onFurniListUpdated);
|
||||
useMessageEvent(FurnitureListRemovedEvent, onFurniListUpdated);
|
||||
useMessageEvent(FurnitureListAddOrUpdateEvent, onFurniListUpdated);
|
||||
|
||||
return { objectId, diskInventory, playlist, currentPlayingIndex, onClose, addToPlaylist, removeFromPlaylist, togglePlayPause };
|
||||
}
|
||||
|
||||
export const useFurniturePlaylistEditorWidget = useFurniturePlaylistEditorWidgetState;
|