diff --git a/src/api/room/widgets/GetDiskColor.ts b/src/api/room/widgets/GetDiskColor.ts new file mode 100644 index 00000000..989f2949 --- /dev/null +++ b/src/api/room/widgets/GetDiskColor.ts @@ -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 })`; +} diff --git a/src/api/room/widgets/index.ts b/src/api/room/widgets/index.ts index ac77f8ef..c43d6aaa 100644 --- a/src/api/room/widgets/index.ts +++ b/src/api/room/widgets/index.ts @@ -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'; diff --git a/src/assets/images/infostand/disk-creator.png b/src/assets/images/infostand/disk-creator.png new file mode 100644 index 00000000..c4e95c9d Binary files /dev/null and b/src/assets/images/infostand/disk-creator.png differ diff --git a/src/assets/images/infostand/disk-icon.png b/src/assets/images/infostand/disk-icon.png new file mode 100644 index 00000000..9ee4ed83 Binary files /dev/null and b/src/assets/images/infostand/disk-icon.png differ diff --git a/src/assets/images/room-widgets/playlist-editor/disk_2.png b/src/assets/images/room-widgets/playlist-editor/disk_2.png new file mode 100644 index 00000000..30330209 Binary files /dev/null and b/src/assets/images/room-widgets/playlist-editor/disk_2.png differ diff --git a/src/assets/images/room-widgets/playlist-editor/disk_image.png b/src/assets/images/room-widgets/playlist-editor/disk_image.png new file mode 100644 index 00000000..7a8ab453 Binary files /dev/null and b/src/assets/images/room-widgets/playlist-editor/disk_image.png differ diff --git a/src/assets/images/room-widgets/playlist-editor/move.png b/src/assets/images/room-widgets/playlist-editor/move.png new file mode 100644 index 00000000..9d1635d8 Binary files /dev/null and b/src/assets/images/room-widgets/playlist-editor/move.png differ diff --git a/src/assets/images/room-widgets/playlist-editor/pause-btn.png b/src/assets/images/room-widgets/playlist-editor/pause-btn.png new file mode 100644 index 00000000..900f99b4 Binary files /dev/null and b/src/assets/images/room-widgets/playlist-editor/pause-btn.png differ diff --git a/src/assets/images/room-widgets/playlist-editor/pause.png b/src/assets/images/room-widgets/playlist-editor/pause.png new file mode 100644 index 00000000..ec5fef47 Binary files /dev/null and b/src/assets/images/room-widgets/playlist-editor/pause.png differ diff --git a/src/assets/images/room-widgets/playlist-editor/playing.png b/src/assets/images/room-widgets/playlist-editor/playing.png new file mode 100644 index 00000000..0e3449d1 Binary files /dev/null and b/src/assets/images/room-widgets/playlist-editor/playing.png differ diff --git a/src/assets/images/room-widgets/playlist-editor/preview.png b/src/assets/images/room-widgets/playlist-editor/preview.png new file mode 100644 index 00000000..160f0bef Binary files /dev/null and b/src/assets/images/room-widgets/playlist-editor/preview.png differ diff --git a/src/assets/styles/icons.scss b/src/assets/styles/icons.scss index 80ac69d0..805dcee2 100644 --- a/src/assets/styles/icons.scss +++ b/src/assets/styles/icons.scss @@ -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; diff --git a/src/common/layout/LayoutPetImageView.tsx b/src/common/layout/LayoutPetImageView.tsx index 092fe6f9..27d43ded 100644 --- a/src/common/layout/LayoutPetImageView.tsx +++ b/src/common/layout/LayoutPetImageView.tsx @@ -52,7 +52,7 @@ export const LayoutPetImageView: FC = props => let petTypeId = typeId; let petPaletteId = paletteId; let petColor1 = petColor; - let petCustomParts = customParts; + let petCustomParts: IPetCustomPart[] = customParts; let petHeadOnly = headOnly; if(figure && figure.length) diff --git a/src/components/catalog/views/page/layout/CatalogLayoutSoundMachineView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutSoundMachineView.tsx new file mode 100644 index 00000000..223be690 --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutSoundMachineView.tsx @@ -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 = 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, 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 ( + <> + + + { GetConfiguration('catalog.headers') && + } + + + + { !currentOffer && + <> + { !!page.localization.getImage(1) && + } + + } + { currentOffer && + <> + + { (currentOffer.product.productType !== ProductTypeEnum.BADGE) && + <> + + + } + { (currentOffer.product.productType === ProductTypeEnum.BADGE) && } + + + + { currentOffer.localizationName } + { songId > -1 && + } + + + + + + + + + } + + + + ); +} diff --git a/src/components/catalog/views/page/layout/GetCatalogLayout.tsx b/src/components/catalog/views/page/layout/GetCatalogLayout.tsx index c80e162b..ca609bcb 100644 --- a/src/components/catalog/views/page/layout/GetCatalogLayout.tsx +++ b/src/components/catalog/views/page/layout/GetCatalogLayout.tsx @@ -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 ; case 'default_3x3_color_grouping': return ; + case 'soundmachine': + return ; case 'bots': case 'default_3x3': default: diff --git a/src/components/room/RoomView.tsx b/src/components/room/RoomView.tsx index 29fd6418..b1714d31 100644 --- a/src/components/room/RoomView.tsx +++ b/src/components/room/RoomView.tsx @@ -29,7 +29,7 @@ export const RoomView: FC<{}> = props => const element = elementRef.current; if(!element) return; - + element.appendChild(canvas); }, []); diff --git a/src/components/room/widgets/avatar-info/AvatarInfoUseProductConfirmView.tsx b/src/components/room/widgets/avatar-info/AvatarInfoUseProductConfirmView.tsx index b9c00b34..5b63dee2 100644 --- a/src/components/room/widgets/avatar-info/AvatarInfoUseProductConfirmView.tsx +++ b/src/components/room/widgets/avatar-info/AvatarInfoUseProductConfirmView.tsx @@ -44,7 +44,7 @@ export const AvatarInfoUseProductConfirmView: FC { if(!petData || !furniData) return null; - + const petFigureData = new PetFigureData(petData.figure); const customParts = furniData.customParams.split(' '); const petIndex = parseInt(customParts[0]); @@ -86,7 +86,7 @@ export const AvatarInfoUseProductConfirmView: FC - { (mode === _Str_11906) && + { (mode === _Str_11906) && <> { LocalizeText('useproduct.widget.text.shampoo', [ 'productName' ], [ furniData.name ] ) } { LocalizeText('useproduct.widget.info.shampoo') } } - { (mode === _Str_11214) && + { (mode === _Str_11214) && <> { LocalizeText('useproduct.widget.text.custompart', [ 'productName' ], [ furniData.name ] ) } { LocalizeText('useproduct.widget.info.custompart') } } - { (mode === _Str_11733) && + { (mode === _Str_11733) && <> { LocalizeText('useproduct.widget.text.custompartshampoo', [ 'productName' ], [ furniData.name ] ) } { LocalizeText('useproduct.widget.info.custompartshampoo') } } - { (mode === _Str_11369) && + { (mode === _Str_11369) && <> { LocalizeText('useproduct.widget.text.saddle', [ 'productName' ], [ furniData.name ] ) } { LocalizeText('useproduct.widget.info.saddle') } } - { (mode === _Str_8759) && + { (mode === _Str_8759) && <> { LocalizeText('useproduct.widget.text.revive_monsterplant', [ 'productName' ], [ furniData.name ] ) } { LocalizeText('useproduct.widget.info.revive_monsterplant') } } - { (mode === _Str_8432) && + { (mode === _Str_8432) && <> { LocalizeText('useproduct.widget.text.rebreed_monsterplant', [ 'productName' ], [ furniData.name ] ) } { LocalizeText('useproduct.widget.info.rebreed_monsterplant') } } - { (mode === _Str_9653) && + { (mode === _Str_9653) && <> { LocalizeText('useproduct.widget.text.fertilize_monsterplant', [ 'productName' ], [ furniData.name ] ) } { LocalizeText('useproduct.widget.info.fertilize_monsterplant') } diff --git a/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetFurniView.tsx b/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetFurniView.tsx index 98816a13..02a64b5d 100644 --- a/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetFurniView.tsx +++ b/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetFurniView.tsx @@ -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 = props const [ godMode, setGodMode ] = useState(false); const [ canSeeFurniId, setCanSeeFurniId ] = useState(false); const [ groupName, setGroupName ] = useState(null); + const [ isJukeBox, setIsJukeBox ] = useState(false); + const [ isSongDisk, setIsSongDisk ] = useState(false); + const [ songId, setSongId ] = useState(-1); + const [ songName, setSongName ] = useState(''); + const [ songCreator, setSongCreator ] = useState(''); + + useSoundEvent(NowPlayingEvent.NPE_SONG_CHANGED, event => + { + setSongId(event.id); + }, (isJukeBox || isSongDisk)); + + useSoundEvent(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 = 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 = 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 = 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 = 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 = props } + { (isJukeBox || isSongDisk) && + +
+ { (songId === -1) && + + { LocalizeText('infostand.jukebox.text.not.playing') } + } + { !!songName.length && + + + + { songName } + + } + { !!songCreator.length && + + + + { songCreator } + + } +
} { isCrackable && <> diff --git a/src/components/room/widgets/furniture/FurnitureWidgets.scss b/src/components/room/widgets/furniture/FurnitureWidgets.scss index cfca72af..78125424 100644 --- a/src/components/room/widgets/furniture/FurnitureWidgets.scss +++ b/src/components/room/widgets/furniture/FurnitureWidgets.scss @@ -33,8 +33,8 @@ } .nitro-widget-crafting { - width: $nitro-widget-crafting-width; - height: $nitro-widget-crafting-height; + width: $nitro-widget-crafting-width; + height: $nitro-widget-crafting-height; } .nitro-widget-exchange-credit { @@ -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; + } + } +} diff --git a/src/components/room/widgets/furniture/FurnitureWidgetsView.tsx b/src/components/room/widgets/furniture/FurnitureWidgetsView.tsx index dda72172..d0e4066d 100644 --- a/src/components/room/widgets/furniture/FurnitureWidgetsView.tsx +++ b/src/components/room/widgets/furniture/FurnitureWidgetsView.tsx @@ -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 => + diff --git a/src/components/room/widgets/furniture/playlist-editor/DiskInventoryView.tsx b/src/components/room/widgets/furniture/playlist-editor/DiskInventoryView.tsx new file mode 100644 index 00000000..d354c06c --- /dev/null +++ b/src/components/room/widgets/furniture/playlist-editor/DiskInventoryView.tsx @@ -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; + addToPlaylist: (diskId: number, slotNumber: number) => void; +} + +export const DiskInventoryView: FC = props => +{ + const { diskInventory = null, addToPlaylist = null } = props; + const [ selectedItem, setSelectedItem ] = useState(-1); + const [ previewSongId, setPreviewSongId ] = useState(-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 (<> +
+ +

{ LocalizeText('playlist.editor.my.music') }

+
+
+ + { diskInventory && diskInventory.getKeys().map( (key, index) => + { + const diskId = diskInventory.getKey(index); + const songId = diskInventory.getWithIndex(index); + const songData = GetNitroInstance().soundManager.musicController?.getSongInfo(songId); + + return ( + setSelectedItem(prev => prev === index ? -1 : index) } classNames={ [ 'text-black' ] }> +
+
+ { songData?.name } + { (selectedItem === index) && + + + + + } +
) + }) } +
+
+
+
{ LocalizeText('playlist.editor.text.get.more.music') }
+
{ LocalizeText('playlist.editor.text.you.have.no.songdisks.available') }
+
{ LocalizeText('playlist.editor.text.you.can.buy.some.from.the.catalogue') }
+ +
+ + ); +} diff --git a/src/components/room/widgets/furniture/playlist-editor/FurniturePlaylistEditorWidgetView.tsx b/src/components/room/widgets/furniture/playlist-editor/FurniturePlaylistEditorWidgetView.tsx new file mode 100644 index 00000000..ceb0db9c --- /dev/null +++ b/src/components/room/widgets/furniture/playlist-editor/FurniturePlaylistEditorWidgetView.tsx @@ -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 ( + + + +
+
+ +
+
+ +
+
+
+
+ ); +} diff --git a/src/components/room/widgets/furniture/playlist-editor/SongPlaylistView.tsx b/src/components/room/widgets/furniture/playlist-editor/SongPlaylistView.tsx new file mode 100644 index 00000000..ecfca469 --- /dev/null +++ b/src/components/room/widgets/furniture/playlist-editor/SongPlaylistView.tsx @@ -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 = props => +{ + const { furniId = -1, playlist = null, currentPlayingIndex = -1, removeFromPlaylist = null, togglePlayPause = null } = props; + const [ selectedItem, setSelectedItem ] = useState(-1); + + const action = (index: number) => + { + if(selectedItem === index) removeFromPlaylist(index); + } + + const playPause = (furniId: number, selectedItem: number) => + { + togglePlayPause(furniId, selectedItem !== -1 ? selectedItem : 0 ) + } + + return (<> +
+ +

{ LocalizeText('playlist.editor.playlist') }

+
+
+ + { playlist && playlist.map( (songInfo, index) => + { + return setSelectedItem(prev => prev === index ? -1 : index) }> + action(index) } className={ 'disk-2 ' + (selectedItem === index ? 'selected-song' : '') } style={ { backgroundColor: (selectedItem === index ? '' : GetDiskColor(songInfo.songData)) } }/> + { songInfo.name } + + }) } + + +
+ { (!playlist || playlist.length === 0 ) && + <>
+
{ LocalizeText('playlist.editor.add.songs.to.your.playlist') }
+
{ LocalizeText('playlist.editor.text.click.song.to.choose.click.again.to.move') }
+
+ + } + { (playlist && playlist.length > 0) && + <> + { (currentPlayingIndex === -1) && + + } + { (currentPlayingIndex !== -1) && + + + + { LocalizeText('playlist.editor.text.now.playing.in.your.room') } + + { playlist[currentPlayingIndex].name + ' - ' + playlist[currentPlayingIndex].creator } + + + + + } + + } + + ); +} diff --git a/src/hooks/events/nitro/index.ts b/src/hooks/events/nitro/index.ts index 82431221..2248da9e 100644 --- a/src/hooks/events/nitro/index.ts +++ b/src/hooks/events/nitro/index.ts @@ -5,3 +5,4 @@ export * from './useMainEvent'; export * from './useRoomEngineEvent'; export * from './useRoomSessionManagerEvent'; export * from './useSessionDataManagerEvent'; +export * from './useSoundEvent'; diff --git a/src/hooks/events/nitro/useSoundEvent.tsx b/src/hooks/events/nitro/useSoundEvent.tsx new file mode 100644 index 00000000..4bfbd5d8 --- /dev/null +++ b/src/hooks/events/nitro/useSoundEvent.tsx @@ -0,0 +1,5 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; +import { GetNitroInstance } from '../../../api'; +import { useEventDispatcher } from '../useEventDispatcher'; + +export const useSoundEvent = (type: string | string[], handler: (event: T) => void, enabled = true) => useEventDispatcher(type, GetNitroInstance().soundManager.events, handler, enabled); diff --git a/src/hooks/notification/useNotification.ts b/src/hooks/notification/useNotification.ts index 61ba051b..e7bfa56f 100644 --- a/src/hooks/notification/useNotification.ts +++ b/src/hooks/notification/useNotification.ts @@ -382,7 +382,7 @@ const useNotificationState = () => useMessageEvent(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); diff --git a/src/hooks/rooms/useRoom.ts b/src/hooks/rooms/useRoom.ts index 35ed5bd1..c4619120 100644 --- a/src/hooks/rooms/useRoom.ts +++ b/src/hooks/rooms/useRoom.ts @@ -14,7 +14,7 @@ const useRoomState = () => const updateRoomBackgroundColor = (hue: number, saturation: number, lightness: number, original: boolean = false) => { if(!roomBackground) return; - + const newColor = ColorConverter.hslToRGB(((((hue & 0xFF) << 16) + ((saturation & 0xFF) << 8)) + (lightness & 0xFF))); if(original) setOriginalRoomBackgroundColor(newColor); @@ -47,7 +47,7 @@ const useRoomState = () => useUiEvent(RoomWidgetUpdateBackgroundColorPreviewEvent.CLEAR_PREVIEW, event => { if(!roomBackground) return; - + roomBackground.tint = originalRoomBackgroundColor; }); @@ -71,7 +71,7 @@ const useRoomState = () => color = event.color; brightness = event.brightness; } - + updateRoomFilter(ColorConverter.hslToRGB(((ColorConverter.rgbToHSL(color) & 0xFFFF00) + brightness))); }); @@ -212,7 +212,7 @@ const useRoomState = () => renderer.resolution = window.devicePixelRatio; renderer.resize(width, height); } - + const displayObject = roomEngine.getRoomInstanceDisplay(roomId, canvasId, width, height, RoomGeometry.SCALE_ZOOMED_IN); const canvas = GetRoomEngine().getRoomInstanceRenderingCanvas(roomId, canvasId); diff --git a/src/hooks/rooms/widgets/furniture/index.ts b/src/hooks/rooms/widgets/furniture/index.ts index f4d96675..37fa5732 100644 --- a/src/hooks/rooms/widgets/furniture/index.ts +++ b/src/hooks/rooms/widgets/furniture/index.ts @@ -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'; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureExternalImageWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureExternalImageWidget.ts index de65ff24..a628eb03 100644 --- a/src/hooks/rooms/widgets/furniture/useFurnitureExternalImageWidget.ts +++ b/src/hooks/rooms/widgets/furniture/useFurnitureExternalImageWidget.ts @@ -33,12 +33,11 @@ const useFurnitureExternalImageWidgetState = () => roomTotalImages.forEach(object => { if (object.type !== 'external_image_wallitem_poster_small') return null; - - const data = object.model.getValue(RoomObjectVariable.FURNITURE_DATA); - const ownerId = object.model.getValue(RoomObjectVariable.FURNITURE_OWNER_ID); - const ownerName = object.model.getValue(RoomObjectVariable.FURNITURE_OWNER_NAME); - 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 }); + const data = object.model.getValue(RoomObjectVariable.FURNITURE_DATA); + const jsonData: IPhotoData = JSON.parse(data); + + datas.push(jsonData); }); setObjectId(event.objectId); diff --git a/src/hooks/rooms/widgets/furniture/useFurniturePlaylistEditorWidget.ts b/src/hooks/rooms/widgets/furniture/useFurniturePlaylistEditorWidget.ts new file mode 100644 index 00000000..6dee5bab --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurniturePlaylistEditorWidget.ts @@ -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>(new AdvancedMap()); + const [ playlist, setPlaylist ] = useState([]); + 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.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.NPE_SONG_CHANGED, event => + { + console.log(event); + setCurrentPlayingIndex(event.position); + }); + + useSoundEvent(NotifyPlayedSongEvent.NOTIFY_PLAYED_SONG, event => + { + showSingleBubble(LocalizeText('soundmachine.notification.playing', [ 'songname', 'songauthor' ], [ event.name, event.creator ]), NotificationBubbleType.SOUNDMACHINE) + }); + + useSoundEvent(SongDiskInventoryReceivedEvent.SDIR_SONG_DISK_INVENTORY_RECEIVENT_EVENT, event => + { + setDiskInventory(GetNitroInstance().soundManager.musicController?.songDiskInventory.clone()); + }); + + useSoundEvent(PlayListStatusEvent.PLUE_PLAY_LIST_UPDATED, event => + { + setPlaylist(GetNitroInstance().soundManager.musicController?.getRoomItemPlaylist()?.entries.concat()) + }); + + useSoundEvent(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; diff --git a/src/hooks/rooms/widgets/useAvatarInfoWidget.ts b/src/hooks/rooms/widgets/useAvatarInfoWidget.ts index 0b8ebd1d..9545362a 100644 --- a/src/hooks/rooms/widgets/useAvatarInfoWidget.ts +++ b/src/hooks/rooms/widgets/useAvatarInfoWidget.ts @@ -56,7 +56,7 @@ const useAvatarInfoWidgetState = () => const name = AvatarInfoUtilities.getObjectName(objectId, category); if(!name) return; - + setActiveNameBubble(name); if(category !== RoomObjectCategory.UNIT) setProductBubbles([]); @@ -100,7 +100,7 @@ const useAvatarInfoWidgetState = () => } if(!info) return; - + setAvatarInfo(info); } @@ -140,7 +140,7 @@ const useAvatarInfoWidgetState = () => const oldIndex = newValue.findIndex(oldBubble => (oldBubble.id === bubble.id)); if(oldIndex > -1) newValue.splice(oldIndex, 1); - + newValue.push(bubble); }); @@ -278,7 +278,7 @@ const useAvatarInfoWidgetState = () => useObjectRollOverEvent(event => { if(avatarInfo || (event.category !== RoomObjectCategory.UNIT)) return; - + getObjectName(event.id, event.category); }); @@ -331,7 +331,7 @@ const useAvatarInfoWidgetState = () => useEffect(() => { if(!avatarInfo) return; - + setActiveNameBubble(null); setNameBubbles([]); setProductBubbles([]);