From 110c3ad3933e60f09e262bba9715b6840e1ab082 Mon Sep 17 00:00:00 2001 From: duckietm Date: Wed, 3 Apr 2024 09:16:49 +0200 Subject: [PATCH] init: Fork from Original V2 Renderer --- .browserslistrc | 11 + .eslintrc.json | 110 + .gitignore | 31 + README.md | 50 + index.html | 36 + package.json | 39 + public/android-chrome-192x192.png | Bin 0 -> 9604 bytes public/android-chrome-512x512.png | Bin 0 -> 29298 bytes public/apple-touch-icon.png | Bin 0 -> 8241 bytes public/browserconfig.xml | 9 + public/favicon-16x16.png | Bin 0 -> 1017 bytes public/favicon-32x32.png | Bin 0 -> 1778 bytes public/favicon.ico | Bin 0 -> 15086 bytes public/mstile-150x150.png | Bin 0 -> 6600 bytes public/robots.txt | 3 + public/safari-pinned-tab.svg | 154 + public/site.webmanifest | 20 + src/App.scss | 102 + src/App.tsx | 102 + src/api/GetRendererVersion.ts | 3 + src/api/GetUIVersion.ts | 1 + src/api/achievements/AchievementCategory.ts | 40 + src/api/achievements/AchievementUtilities.ts | 97 + src/api/achievements/IAchievementCategory.ts | 7 + src/api/achievements/index.ts | 3 + src/api/avatar/AvatarEditorAction.ts | 7 + src/api/avatar/AvatarEditorGridColorItem.ts | 65 + src/api/avatar/AvatarEditorGridPartItem.ts | 336 ++ .../avatar/AvatarEditorThumbnailsHelper.ts | 189 + src/api/avatar/AvatarEditorUtilities.ts | 277 ++ src/api/avatar/BodyModel.ts | 75 + src/api/avatar/CategoryBaseModel.ts | 246 ++ src/api/avatar/CategoryData.ts | 487 +++ src/api/avatar/FigureData.ts | 287 ++ src/api/avatar/FigureGenerator.ts | 89 + src/api/avatar/HeadModel.ts | 24 + src/api/avatar/IAvatarEditorCategory.ts | 9 + src/api/avatar/IAvatarEditorCategoryModel.ts | 19 + .../avatar/IAvatarEditorCategoryPartItem.ts | 10 + src/api/avatar/LegModel.ts | 22 + src/api/avatar/TorsoModel.ts | 23 + src/api/avatar/index.ts | 16 + src/api/camera/CameraEditorTabs.ts | 5 + src/api/camera/CameraPicture.ts | 9 + src/api/camera/CameraPictureThumbnail.ts | 7 + src/api/camera/index.ts | 3 + src/api/campaign/CalendarItem.ts | 30 + src/api/campaign/CalendarItemState.ts | 7 + src/api/campaign/ICalendarItem.ts | 6 + src/api/campaign/index.ts | 3 + .../catalog/BuilderFurniPlaceableStatus.ts | 10 + src/api/catalog/CatalogNode.ts | 124 + src/api/catalog/CatalogPage.ts | 59 + src/api/catalog/CatalogPageName.ts | 26 + src/api/catalog/CatalogPetPalette.ts | 10 + src/api/catalog/CatalogPurchaseState.ts | 10 + src/api/catalog/CatalogType.ts | 5 + src/api/catalog/CatalogUtilities.ts | 124 + src/api/catalog/FurnitureOffer.ts | 120 + src/api/catalog/GetImageIconUrlForProduct.ts | 19 + src/api/catalog/GiftWrappingConfiguration.ts | 51 + src/api/catalog/ICatalogNode.ts | 21 + src/api/catalog/ICatalogOptions.ts | 13 + src/api/catalog/ICatalogPage.ts | 12 + src/api/catalog/IMarketplaceSearchOptions.ts | 7 + src/api/catalog/IPageLocalization.ts | 5 + src/api/catalog/IProduct.ts | 16 + src/api/catalog/IPurchasableOffer.ts | 25 + src/api/catalog/IPurchaseOptions.ts | 9 + src/api/catalog/MarketplaceOfferData.ts | 128 + src/api/catalog/MarketplaceOfferState.ts | 7 + src/api/catalog/MarketplaceSearchType.ts | 6 + src/api/catalog/Offer.ts | 245 ++ src/api/catalog/PageLocalization.ts | 36 + src/api/catalog/PlacedObjectPurchaseData.ts | 41 + src/api/catalog/Product.ts | 143 + src/api/catalog/ProductTypeEnum.ts | 11 + src/api/catalog/RequestedPage.ts | 63 + src/api/catalog/SearchResult.ts | 11 + src/api/catalog/index.ts | 29 + src/api/chat-history/ChatEntryType.ts | 6 + .../chat-history/ChatHistoryCurrentDate.ts | 6 + src/api/chat-history/IChatEntry.ts | 17 + src/api/chat-history/IRoomHistoryEntry.ts | 5 + .../MessengerHistoryCurrentDate.ts | 6 + src/api/chat-history/index.ts | 5 + src/api/events/DispatchEvent.ts | 3 + src/api/events/DispatchMainEvent.ts | 4 + src/api/events/DispatchUiEvent.ts | 5 + src/api/events/UI_EVENT_DISPATCHER.ts | 3 + src/api/events/index.ts | 4 + src/api/friends/GetGroupChatData.ts | 13 + src/api/friends/IGroupChatData.ts | 6 + src/api/friends/MessengerFriend.ts | 43 + src/api/friends/MessengerGroupType.ts | 5 + src/api/friends/MessengerIconState.ts | 6 + src/api/friends/MessengerRequest.ts | 41 + src/api/friends/MessengerSettings.ts | 11 + src/api/friends/MessengerThread.ts | 96 + src/api/friends/MessengerThreadChat.ts | 54 + src/api/friends/MessengerThreadChatGroup.ts | 41 + src/api/friends/OpenMessengerChat.ts | 7 + src/api/friends/index.ts | 11 + src/api/groups/GetGroupInformation.ts | 7 + src/api/groups/GetGroupManager.ts | 6 + src/api/groups/GetGroupMembers.ts | 7 + src/api/groups/GroupBadgePart.ts | 30 + src/api/groups/GroupMembershipType.ts | 6 + src/api/groups/GroupType.ts | 6 + src/api/groups/IGroupCustomize.ts | 8 + src/api/groups/IGroupData.ts | 13 + src/api/groups/ToggleFavoriteGroup.ts | 7 + src/api/groups/TryJoinGroup.ts | 4 + src/api/groups/index.ts | 10 + src/api/guide-tool/GuideSessionState.ts | 23 + src/api/guide-tool/GuideToolMessage.ts | 21 + src/api/guide-tool/GuideToolMessageGroup.ts | 28 + src/api/guide-tool/index.ts | 3 + src/api/hc-center/ClubStatus.ts | 6 + src/api/hc-center/GetClubBadge.ts | 11 + src/api/hc-center/index.ts | 2 + src/api/help/CallForHelpResult.ts | 5 + src/api/help/GetCloseReasonKey.ts | 8 + src/api/help/IHelpReport.ts | 19 + src/api/help/IReportedUser.ts | 5 + src/api/help/ReportState.ts | 8 + src/api/help/ReportType.ts | 11 + src/api/help/index.ts | 6 + src/api/index.ts | 28 + src/api/inventory/FurniCategory.ts | 26 + src/api/inventory/FurnitureItem.ts | 245 ++ src/api/inventory/FurnitureUtilities.ts | 171 + src/api/inventory/GroupItem.ts | 461 +++ src/api/inventory/IBotItem.ts | 6 + src/api/inventory/IFurnitureItem.ts | 17 + src/api/inventory/IPetItem.ts | 6 + src/api/inventory/IUnseenItemTracker.ts | 12 + src/api/inventory/InventoryUtilities.ts | 117 + src/api/inventory/PetUtilities.ts | 103 + src/api/inventory/TradeState.ts | 10 + src/api/inventory/TradeUserData.ts | 15 + src/api/inventory/TradingNotificationType.ts | 12 + src/api/inventory/TradingUtilities.ts | 70 + src/api/inventory/UnseenItemCategory.ts | 9 + src/api/inventory/index.ts | 15 + src/api/mod-tools/GetIssueCategoryName.ts | 35 + src/api/mod-tools/ISelectedUser.ts | 5 + src/api/mod-tools/IUserInfo.ts | 6 + src/api/mod-tools/ModActionDefinition.ts | 49 + src/api/mod-tools/index.ts | 4 + src/api/navigator/DoorStateType.ts | 12 + src/api/navigator/INavigatorData.ts | 17 + src/api/navigator/INavigatorSearchFilter.ts | 5 + src/api/navigator/IRoomChatSettings.ts | 8 + src/api/navigator/IRoomData.ts | 23 + src/api/navigator/IRoomModel.ts | 6 + src/api/navigator/IRoomModerationSettings.ts | 6 + .../NavigatorSearchResultViewDisplayMode.ts | 6 + src/api/navigator/RoomInfoData.ts | 60 + src/api/navigator/RoomSettingsUtils.ts | 10 + src/api/navigator/SearchFilterOptions.ts | 24 + src/api/navigator/TryVisitRoom.ts | 7 + src/api/navigator/index.ts | 12 + src/api/nitro/GetConfigurationValue.ts | 6 + src/api/nitro/OpenUrl.ts | 15 + src/api/nitro/SendMessageComposer.ts | 3 + src/api/nitro/index.ts | 5 + src/api/nitro/room/DispatchMouseEvent.ts | 54 + src/api/nitro/room/DispatchTouchEvent.ts | 81 + src/api/nitro/room/GetOwnRoomObject.ts | 31 + src/api/nitro/room/GetRoomObjectBounds.ts | 13 + .../nitro/room/GetRoomObjectScreenLocation.ts | 13 + .../InitializeRoomInstanceRenderingCanvas.ts | 9 + .../room/IsFurnitureSelectionDisabled.ts | 22 + .../nitro/room/ProcessRoomObjectOperation.ts | 6 + src/api/nitro/room/SetActiveRoomId.ts | 6 + src/api/nitro/room/index.ts | 9 + .../nitro/session/CanManipulateFurniture.ts | 9 + src/api/nitro/session/CreateRoomSession.ts | 6 + src/api/nitro/session/GetCanStandUp.ts | 13 + src/api/nitro/session/GetCanUseExpression.ts | 14 + src/api/nitro/session/GetClubMemberLevel.ts | 9 + src/api/nitro/session/GetFurnitureData.ts | 19 + .../GetFurnitureDataForProductOffer.ts | 20 + .../session/GetFurnitureDataForRoomObject.ts | 20 + src/api/nitro/session/GetOwnPosture.ts | 13 + .../session/GetProductDataForLocalization.ts | 8 + src/api/nitro/session/GetRoomSession.ts | 3 + src/api/nitro/session/GoToDesktop.ts | 7 + src/api/nitro/session/HasHabboClub.ts | 6 + src/api/nitro/session/HasHabboVip.ts | 6 + .../nitro/session/IsOwnerOfFloorFurniture.ts | 14 + src/api/nitro/session/IsOwnerOfFurniture.ts | 11 + src/api/nitro/session/IsRidingHorse.ts | 14 + src/api/nitro/session/StartRoomSession.ts | 6 + src/api/nitro/session/VisitDesktop.ts | 11 + src/api/nitro/session/index.ts | 19 + src/api/notification/NotificationAlertItem.ts | 67 + src/api/notification/NotificationAlertType.ts | 10 + .../notification/NotificationBubbleItem.ts | 48 + .../notification/NotificationBubbleType.ts | 19 + .../notification/NotificationConfirmItem.ts | 67 + .../notification/NotificationConfirmType.ts | 4 + src/api/notification/index.ts | 6 + src/api/purse/IPurse.ts | 15 + src/api/purse/Purse.ts | 165 + src/api/purse/index.ts | 2 + .../room/events/RoomWidgetPollUpdateEvent.ts | 110 + ...WidgetUpdateBackgroundColorPreviewEvent.ts | 35 + .../RoomWidgetUpdateChatInputContentEvent.ts | 29 + src/api/room/events/RoomWidgetUpdateEvent.ts | 4 + .../RoomWidgetUpdateRentableBotChatEvent.ts | 62 + .../events/RoomWidgetUpdateRoomObjectEvent.ts | 43 + src/api/room/events/index.ts | 6 + src/api/room/index.ts | 2 + src/api/room/widgets/AvatarInfoFurni.ts | 37 + src/api/room/widgets/AvatarInfoName.ts | 11 + src/api/room/widgets/AvatarInfoPet.ts | 46 + src/api/room/widgets/AvatarInfoRentableBot.ts | 23 + src/api/room/widgets/AvatarInfoUser.ts | 49 + src/api/room/widgets/AvatarInfoUtilities.ts | 439 +++ src/api/room/widgets/BotSkillsEnum.ts | 18 + src/api/room/widgets/ChatBubbleMessage.ts | 54 + src/api/room/widgets/ChatBubbleUtilities.ts | 69 + src/api/room/widgets/ChatMessageTypeEnum.ts | 6 + .../DimmerFurnitureWidgetPresetItem.ts | 9 + src/api/room/widgets/DoChatsOverlap.ts | 7 + .../room/widgets/FurnitureDimmerUtilities.ts | 30 + src/api/room/widgets/GetDiskColor.ts | 37 + src/api/room/widgets/IAvatarInfo.ts | 4 + src/api/room/widgets/ICraftingIngredient.ts | 6 + src/api/room/widgets/ICraftingRecipe.ts | 6 + src/api/room/widgets/IPhotoData.ts | 42 + src/api/room/widgets/MannequinUtilities.ts | 38 + src/api/room/widgets/PetSupplementEnum.ts | 5 + src/api/room/widgets/PostureTypeEnum.ts | 5 + src/api/room/widgets/RoomDimmerPreset.ts | 35 + src/api/room/widgets/RoomObjectItem.ts | 28 + src/api/room/widgets/UseProductItem.ts | 12 + src/api/room/widgets/VoteValue.ts | 8 + .../widgets/YoutubeVideoPlaybackStateEnum.ts | 9 + src/api/room/widgets/index.ts | 26 + src/api/user/GetUserProfile.ts | 7 + src/api/user/index.ts | 1 + src/api/utils/CloneObject.ts | 14 + src/api/utils/ColorUtils.ts | 65 + src/api/utils/ConvertSeconds.ts | 9 + src/api/utils/FixedSizeStack.ts | 65 + src/api/utils/FriendlyTime.ts | 47 + src/api/utils/GetLocalStorage.ts | 11 + src/api/utils/LocalStorageKeys.ts | 5 + src/api/utils/LocalizeBadgeDescription.ts | 10 + src/api/utils/LocalizeBageName.ts | 10 + src/api/utils/LocalizeFormattedNumber.ts | 6 + src/api/utils/LocalizeShortNumber.ts | 36 + src/api/utils/LocalizeText.ts | 6 + src/api/utils/PlaySound.ts | 24 + src/api/utils/ProductImageUtility.ts | 58 + src/api/utils/Randomizer.ts | 28 + src/api/utils/RoomChatFormatter.ts | 75 + src/api/utils/SetLocalStorage.ts | 1 + src/api/utils/SoundNames.ts | 9 + src/api/utils/WindowSaveOptions.ts | 5 + src/api/utils/index.ts | 19 + src/api/wired/GetWiredTimeLocale.ts | 8 + src/api/wired/WiredActionLayoutCode.ts | 29 + src/api/wired/WiredConditionLayoutCode.ts | 29 + src/api/wired/WiredDateToString.ts | 1 + src/api/wired/WiredFurniType.ts | 7 + src/api/wired/WiredSelectionVisualizer.ts | 85 + src/api/wired/WiredStringDelimeter.ts | 1 + src/api/wired/WiredTriggerLayoutCode.ts | 17 + src/api/wired/index.ts | 8 + src/assets/images/achievements/back-arrow.png | Bin 0 -> 331 bytes .../images/avatareditor/arrow-left-icon.png | Bin 0 -> 198 bytes .../images/avatareditor/arrow-right-icon.png | Bin 0 -> 192 bytes .../avatar-editor-spritesheet.png | Bin 0 -> 23260 bytes src/assets/images/avatareditor/ca-icon.png | Bin 0 -> 259 bytes .../images/avatareditor/ca-selected-icon.png | Bin 0 -> 335 bytes src/assets/images/avatareditor/cc-icon.png | Bin 0 -> 282 bytes .../images/avatareditor/cc-selected-icon.png | Bin 0 -> 338 bytes src/assets/images/avatareditor/ch-icon.png | Bin 0 -> 228 bytes .../images/avatareditor/ch-selected-icon.png | Bin 0 -> 260 bytes src/assets/images/avatareditor/clear-icon.png | Bin 0 -> 272 bytes src/assets/images/avatareditor/cp-icon.png | Bin 0 -> 252 bytes .../images/avatareditor/cp-selected-icon.png | Bin 0 -> 280 bytes src/assets/images/avatareditor/ea-icon.png | Bin 0 -> 251 bytes .../images/avatareditor/ea-selected-icon.png | Bin 0 -> 298 bytes src/assets/images/avatareditor/fa-icon.png | Bin 0 -> 234 bytes .../images/avatareditor/fa-selected-icon.png | Bin 0 -> 286 bytes .../images/avatareditor/female-icon.png | Bin 0 -> 236 bytes .../avatareditor/female-selected-icon.png | Bin 0 -> 270 bytes src/assets/images/avatareditor/ha-icon.png | Bin 0 -> 241 bytes .../images/avatareditor/ha-selected-icon.png | Bin 0 -> 285 bytes src/assets/images/avatareditor/he-icon.png | Bin 0 -> 267 bytes .../images/avatareditor/he-selected-icon.png | Bin 0 -> 338 bytes src/assets/images/avatareditor/hr-icon.png | Bin 0 -> 257 bytes .../images/avatareditor/hr-selected-icon.png | Bin 0 -> 348 bytes src/assets/images/avatareditor/lg-icon.png | Bin 0 -> 196 bytes .../images/avatareditor/lg-selected-icon.png | Bin 0 -> 236 bytes .../images/avatareditor/loading-icon.png | Bin 0 -> 181 bytes src/assets/images/avatareditor/male-icon.png | Bin 0 -> 228 bytes .../avatareditor/male-selected-icon.png | Bin 0 -> 256 bytes .../images/avatareditor/sellable-icon.png | Bin 0 -> 229 bytes src/assets/images/avatareditor/sh-icon.png | Bin 0 -> 208 bytes .../images/avatareditor/sh-selected-icon.png | Bin 0 -> 266 bytes .../images/avatareditor/spotlight-icon.png | Bin 0 -> 11373 bytes src/assets/images/avatareditor/wa-icon.png | Bin 0 -> 257 bytes .../images/avatareditor/wa-selected-icon.png | Bin 0 -> 308 bytes src/assets/images/campaign/available.png | Bin 0 -> 1118 bytes .../campaign/campaign_day_generic_bg.png | Bin 0 -> 36045 bytes .../images/campaign/campaign_opened.png | Bin 0 -> 744 bytes .../images/campaign/campaign_spritesheet.png | Bin 0 -> 83689 bytes src/assets/images/campaign/locked.png | Bin 0 -> 220 bytes src/assets/images/campaign/locked_bg.png | Bin 0 -> 5414 bytes src/assets/images/campaign/next.png | Bin 0 -> 244 bytes src/assets/images/campaign/prev.png | Bin 0 -> 235 bytes src/assets/images/campaign/unavailable.png | Bin 0 -> 448 bytes src/assets/images/campaign/unlocked_bg.png | Bin 0 -> 8798 bytes .../catalog/diamond_info_illustration.gif | Bin 0 -> 3883 bytes src/assets/images/catalog/hc_banner_big.png | Bin 0 -> 3539 bytes src/assets/images/catalog/hc_big.png | Bin 0 -> 2596 bytes src/assets/images/catalog/hc_small.png | Bin 0 -> 2208 bytes src/assets/images/catalog/paint-icon.png | Bin 0 -> 262 bytes src/assets/images/catalog/target-price.png | Bin 0 -> 3562 bytes src/assets/images/catalog/vip.png | Bin 0 -> 521 bytes .../images/chat/chatbubbles/bubble_0.png | Bin 0 -> 5025 bytes .../chatbubbles/bubble_0_1_33_34_pointer.png | Bin 0 -> 127 bytes .../chat/chatbubbles/bubble_0_transparent.png | Bin 0 -> 256 bytes .../images/chat/chatbubbles/bubble_1.png | Bin 0 -> 449 bytes .../images/chat/chatbubbles/bubble_10.png | Bin 0 -> 778 bytes .../chat/chatbubbles/bubble_10_pointer.png | Bin 0 -> 138 bytes .../images/chat/chatbubbles/bubble_11.png | Bin 0 -> 242 bytes .../chat/chatbubbles/bubble_11_pointer.png | Bin 0 -> 104 bytes .../images/chat/chatbubbles/bubble_12.png | Bin 0 -> 242 bytes .../chat/chatbubbles/bubble_12_pointer.png | Bin 0 -> 104 bytes .../images/chat/chatbubbles/bubble_13.png | Bin 0 -> 397 bytes .../chat/chatbubbles/bubble_13_pointer.png | Bin 0 -> 105 bytes .../images/chat/chatbubbles/bubble_14.png | Bin 0 -> 234 bytes .../chat/chatbubbles/bubble_14_pointer.png | Bin 0 -> 105 bytes .../images/chat/chatbubbles/bubble_15.png | Bin 0 -> 247 bytes .../chat/chatbubbles/bubble_15_pointer.png | Bin 0 -> 105 bytes .../images/chat/chatbubbles/bubble_16.png | Bin 0 -> 716 bytes .../chat/chatbubbles/bubble_16_pointer.png | Bin 0 -> 149 bytes .../images/chat/chatbubbles/bubble_17.png | Bin 0 -> 806 bytes .../chat/chatbubbles/bubble_17_pointer.png | Bin 0 -> 105 bytes .../images/chat/chatbubbles/bubble_18.png | Bin 0 -> 247 bytes .../chat/chatbubbles/bubble_18_pointer.png | Bin 0 -> 119 bytes .../images/chat/chatbubbles/bubble_19.png | Bin 0 -> 688 bytes .../chat/chatbubbles/bubble_19_20_pointer.png | Bin 0 -> 110 bytes .../images/chat/chatbubbles/bubble_2.png | Bin 0 -> 436 bytes .../images/chat/chatbubbles/bubble_20.png | Bin 0 -> 417 bytes .../images/chat/chatbubbles/bubble_21.png | Bin 0 -> 584 bytes .../chat/chatbubbles/bubble_21_pointer.png | Bin 0 -> 109 bytes .../images/chat/chatbubbles/bubble_22.png | Bin 0 -> 650 bytes .../chat/chatbubbles/bubble_22_pointer.png | Bin 0 -> 109 bytes .../images/chat/chatbubbles/bubble_23.png | Bin 0 -> 332 bytes .../chat/chatbubbles/bubble_23_37_pointer.png | Bin 0 -> 104 bytes .../images/chat/chatbubbles/bubble_24.png | Bin 0 -> 380 bytes .../chat/chatbubbles/bubble_24_pointer.png | Bin 0 -> 105 bytes .../images/chat/chatbubbles/bubble_25.png | Bin 0 -> 280 bytes .../chat/chatbubbles/bubble_25_pointer.png | Bin 0 -> 131 bytes .../images/chat/chatbubbles/bubble_26.png | Bin 0 -> 557 bytes .../chat/chatbubbles/bubble_26_pointer.png | Bin 0 -> 149 bytes .../images/chat/chatbubbles/bubble_27.png | Bin 0 -> 632 bytes .../chat/chatbubbles/bubble_27_pointer.png | Bin 0 -> 105 bytes .../images/chat/chatbubbles/bubble_28.png | Bin 0 -> 758 bytes .../chat/chatbubbles/bubble_28_pointer.png | Bin 0 -> 105 bytes .../images/chat/chatbubbles/bubble_29.png | Bin 0 -> 476 bytes .../chat/chatbubbles/bubble_29_pointer.png | Bin 0 -> 102 bytes .../chat/chatbubbles/bubble_2_31_pointer.png | Bin 0 -> 178 bytes .../images/chat/chatbubbles/bubble_3.png | Bin 0 -> 236 bytes .../images/chat/chatbubbles/bubble_30.png | Bin 0 -> 427 bytes .../chat/chatbubbles/bubble_30_pointer.png | Bin 0 -> 143 bytes .../images/chat/chatbubbles/bubble_32.png | Bin 0 -> 399 bytes .../chat/chatbubbles/bubble_32_pointer.png | Bin 0 -> 144 bytes .../images/chat/chatbubbles/bubble_33_34.png | Bin 0 -> 344 bytes .../chat/chatbubbles/bubble_33_extra.png | Bin 0 -> 542 bytes .../chat/chatbubbles/bubble_34_extra.png | Bin 0 -> 310 bytes .../images/chat/chatbubbles/bubble_35.png | Bin 0 -> 869 bytes .../chat/chatbubbles/bubble_35_pointer.png | Bin 0 -> 126 bytes .../images/chat/chatbubbles/bubble_36.png | Bin 0 -> 266 bytes .../chat/chatbubbles/bubble_36_extra.png | Bin 0 -> 239 bytes .../chat/chatbubbles/bubble_36_pointer.png | Bin 0 -> 105 bytes .../images/chat/chatbubbles/bubble_37.png | Bin 0 -> 376 bytes .../images/chat/chatbubbles/bubble_38.png | Bin 0 -> 276 bytes .../chat/chatbubbles/bubble_38_extra.png | Bin 0 -> 228 bytes .../chat/chatbubbles/bubble_38_pointer.png | Bin 0 -> 105 bytes .../chat/chatbubbles/bubble_3_pointer.png | Bin 0 -> 105 bytes .../images/chat/chatbubbles/bubble_4.png | Bin 0 -> 242 bytes .../chat/chatbubbles/bubble_4_pointer.png | Bin 0 -> 104 bytes .../images/chat/chatbubbles/bubble_5.png | Bin 0 -> 372 bytes .../chat/chatbubbles/bubble_5_pointer.png | Bin 0 -> 104 bytes .../images/chat/chatbubbles/bubble_6.png | Bin 0 -> 402 bytes .../chat/chatbubbles/bubble_6_pointer.png | Bin 0 -> 105 bytes .../images/chat/chatbubbles/bubble_7.png | Bin 0 -> 231 bytes .../chat/chatbubbles/bubble_7_pointer.png | Bin 0 -> 105 bytes .../images/chat/chatbubbles/bubble_8.png | Bin 0 -> 1206 bytes .../chat/chatbubbles/bubble_8_pointer.png | Bin 0 -> 105 bytes .../images/chat/chatbubbles/bubble_9.png | Bin 0 -> 499 bytes .../chat/chatbubbles/bubble_9_pointer.png | Bin 0 -> 146 bytes src/assets/images/chat/styles-icon.png | Bin 0 -> 314 bytes .../floorplaneditor/door-direction-0.png | Bin 0 -> 742 bytes .../floorplaneditor/door-direction-1.png | Bin 0 -> 738 bytes .../floorplaneditor/door-direction-2.png | Bin 0 -> 750 bytes .../floorplaneditor/door-direction-3.png | Bin 0 -> 697 bytes .../floorplaneditor/door-direction-4.png | Bin 0 -> 756 bytes .../floorplaneditor/door-direction-5.png | Bin 0 -> 754 bytes .../floorplaneditor/door-direction-6.png | Bin 0 -> 747 bytes .../floorplaneditor/door-direction-7.png | Bin 0 -> 698 bytes .../images/floorplaneditor/icon-door.png | Bin 0 -> 806 bytes .../images/floorplaneditor/icon-tile-down.png | Bin 0 -> 609 bytes .../images/floorplaneditor/icon-tile-set.png | Bin 0 -> 525 bytes .../floorplaneditor/icon-tile-unset.png | Bin 0 -> 544 bytes .../images/floorplaneditor/icon-tile-up.png | Bin 0 -> 555 bytes .../images/floorplaneditor/preview_tile.png | Bin 0 -> 146 bytes .../floorplaneditor/selected_height_icon.png | Bin 0 -> 175 bytes .../images/friends/friends-spritesheet.png | Bin 0 -> 3494 bytes src/assets/images/friends/icon-accept.png | Bin 0 -> 174 bytes src/assets/images/friends/icon-add.png | Bin 0 -> 205 bytes src/assets/images/friends/icon-bobba.png | Bin 0 -> 169 bytes src/assets/images/friends/icon-chat.png | Bin 0 -> 199 bytes src/assets/images/friends/icon-deny.png | Bin 0 -> 173 bytes src/assets/images/friends/icon-follow.png | Bin 0 -> 162 bytes .../images/friends/icon-friendbar-chat.png | Bin 0 -> 1740 bytes .../images/friends/icon-friendbar-visit.png | Bin 0 -> 2150 bytes src/assets/images/friends/icon-heart.png | Bin 0 -> 201 bytes .../images/friends/icon-new-message.png | Bin 0 -> 219 bytes src/assets/images/friends/icon-none.png | Bin 0 -> 177 bytes .../images/friends/icon-profile-sm-hover.png | Bin 0 -> 264 bytes src/assets/images/friends/icon-profile-sm.png | Bin 0 -> 257 bytes src/assets/images/friends/icon-profile.png | Bin 0 -> 1819 bytes src/assets/images/friends/icon-smile.png | Bin 0 -> 205 bytes src/assets/images/friends/icon-warning.png | Bin 0 -> 225 bytes .../friends/messenger_notification_icon.png | Bin 0 -> 164 bytes src/assets/images/gamecenter/selectedIcon.png | Bin 0 -> 248 bytes src/assets/images/gift/gift_tag.png | Bin 0 -> 795 bytes src/assets/images/gift/incognito.png | Bin 0 -> 1057 bytes src/assets/images/groups/creator_images.png | Bin 0 -> 7966 bytes src/assets/images/groups/creator_tabs.png | Bin 0 -> 1215 bytes .../groups/icons/group_decorate_icon.png | Bin 0 -> 311 bytes .../images/groups/icons/group_favorite.png | Bin 0 -> 198 bytes .../images/groups/icons/group_icon_admin.png | Bin 0 -> 203 bytes .../groups/icons/group_icon_big_admin.png | Bin 0 -> 429 bytes .../groups/icons/group_icon_big_member.png | Bin 0 -> 447 bytes .../groups/icons/group_icon_big_owner.png | Bin 0 -> 460 bytes .../groups/icons/group_icon_not_admin.png | Bin 0 -> 198 bytes .../groups/icons/group_icon_small_owner.png | Bin 0 -> 215 bytes .../images/groups/icons/group_notfavorite.png | Bin 0 -> 175 bytes .../images/groups/icons/grouptype_icon_0.png | Bin 0 -> 542 bytes .../images/groups/icons/grouptype_icon_1.png | Bin 0 -> 349 bytes .../images/groups/icons/grouptype_icon_2.png | Bin 0 -> 373 bytes src/assets/images/groups/no-group-1.png | Bin 0 -> 5007 bytes src/assets/images/groups/no-group-2.png | Bin 0 -> 4401 bytes src/assets/images/groups/no-group-3.png | Bin 0 -> 4566 bytes .../images/groups/no-group-spritesheet.png | Bin 0 -> 29386 bytes .../guide-tool/guide_tool_duty_switch.png | Bin 0 -> 537 bytes .../guide-tool/guide_tool_info_icon.png | Bin 0 -> 184 bytes src/assets/images/hc-center/benefits.png | Bin 0 -> 14475 bytes src/assets/images/hc-center/clock.png | Bin 0 -> 267 bytes src/assets/images/hc-center/hc_logo.gif | Bin 0 -> 1251 bytes src/assets/images/hc-center/payday.png | Bin 0 -> 721 bytes src/assets/images/help/help_index.png | Bin 0 -> 2569 bytes src/assets/images/icons/arrows.png | Bin 0 -> 222 bytes .../images/icons/camera-colormatrix.png | Bin 0 -> 249 bytes src/assets/images/icons/camera-composite.png | Bin 0 -> 204 bytes src/assets/images/icons/camera-small.png | Bin 0 -> 296 bytes src/assets/images/icons/chat-history.png | Bin 0 -> 574 bytes src/assets/images/icons/close.png | Bin 0 -> 1163 bytes src/assets/images/icons/cog.png | Bin 0 -> 448 bytes src/assets/images/icons/help.png | Bin 0 -> 233 bytes src/assets/images/icons/house-small.png | Bin 0 -> 361 bytes src/assets/images/icons/icon_cog.png | Bin 0 -> 218 bytes src/assets/images/icons/like-room.png | Bin 0 -> 481 bytes src/assets/images/icons/loading-icon.png | Bin 0 -> 222 bytes src/assets/images/icons/room-link.png | Bin 0 -> 322 bytes src/assets/images/icons/sign-exclamation.png | Bin 0 -> 236 bytes src/assets/images/icons/sign-heart.png | Bin 0 -> 256 bytes src/assets/images/icons/sign-red.png | Bin 0 -> 324 bytes src/assets/images/icons/sign-skull.png | Bin 0 -> 199 bytes src/assets/images/icons/sign-smile.png | Bin 0 -> 288 bytes src/assets/images/icons/sign-soccer.png | Bin 0 -> 641 bytes src/assets/images/icons/sign-yellow.png | Bin 0 -> 304 bytes src/assets/images/icons/small-room.png | Bin 0 -> 413 bytes src/assets/images/icons/tickets.png | Bin 0 -> 143 bytes src/assets/images/icons/user.png | Bin 0 -> 881 bytes src/assets/images/icons/zoom-less.png | Bin 0 -> 252 bytes src/assets/images/icons/zoom-more.png | Bin 0 -> 475 bytes .../images/infostand/bot_background.png | Bin 0 -> 811 bytes .../images/infostand/countown-timer.png | Bin 0 -> 219 bytes src/assets/images/infostand/disk-creator.png | Bin 0 -> 189 bytes src/assets/images/infostand/disk-icon.png | Bin 0 -> 240 bytes src/assets/images/infostand/pencil-icon.png | Bin 0 -> 288 bytes src/assets/images/infostand/rarity-level.png | Bin 0 -> 273 bytes src/assets/images/inventory/empty.png | Bin 0 -> 6377 bytes src/assets/images/inventory/rarity-level.png | Bin 0 -> 131 bytes .../images/inventory/trading/locked-icon.png | Bin 0 -> 309 bytes .../inventory/trading/unlocked-icon.png | Bin 0 -> 332 bytes .../loading/connecting-duck-spritesheet.png | Bin 0 -> 34720 bytes .../images/loading/connecting_duck_01.png | Bin 0 -> 3985 bytes .../images/loading/connecting_duck_02.png | Bin 0 -> 4923 bytes .../images/loading/connecting_duck_03.png | Bin 0 -> 5842 bytes .../images/loading/connecting_duck_04.png | Bin 0 -> 3270 bytes .../images/loading/connecting_duck_05.png | Bin 0 -> 4558 bytes .../images/loading/connecting_duck_06.png | Bin 0 -> 5290 bytes .../images/loading/connecting_duck_07.png | Bin 0 -> 2731 bytes src/assets/images/loading/progress_habbos.gif | Bin 0 -> 11805 bytes src/assets/images/modtool/chatlog.gif | Bin 0 -> 109 bytes src/assets/images/modtool/key.gif | Bin 0 -> 214 bytes src/assets/images/modtool/m_icon.png | Bin 0 -> 299 bytes src/assets/images/modtool/reports.png | Bin 0 -> 3551 bytes src/assets/images/modtool/room.gif | Bin 0 -> 169 bytes src/assets/images/modtool/room.png | Bin 0 -> 3102 bytes src/assets/images/modtool/user.gif | Bin 0 -> 1130 bytes src/assets/images/modtool/wrench.gif | Bin 0 -> 117 bytes .../chain_mysterybox_box_overlay.png | Bin 0 -> 369 bytes src/assets/images/mysterybox/key_overlay.png | Bin 0 -> 260 bytes src/assets/images/mysterybox/mystery_box.png | Bin 0 -> 514 bytes .../images/mysterybox/mystery_box_key.png | Bin 0 -> 487 bytes .../mysterytrophy/frank_mystery_trophy.png | Bin 0 -> 1653 bytes src/assets/images/navigator/icons/info.png | Bin 0 -> 256 bytes .../images/navigator/icons/room_group.png | Bin 0 -> 191 bytes .../images/navigator/icons/room_invisible.png | Bin 0 -> 173 bytes .../images/navigator/icons/room_locked.png | Bin 0 -> 221 bytes .../images/navigator/icons/room_password.png | Bin 0 -> 188 bytes .../images/navigator/models/model_0.png | Bin 0 -> 4027 bytes .../images/navigator/models/model_1.png | Bin 0 -> 956 bytes .../images/navigator/models/model_2.png | Bin 0 -> 920 bytes .../images/navigator/models/model_3.png | Bin 0 -> 581 bytes .../images/navigator/models/model_4.png | Bin 0 -> 709 bytes .../images/navigator/models/model_5.png | Bin 0 -> 1730 bytes .../images/navigator/models/model_6.png | Bin 0 -> 2147 bytes .../images/navigator/models/model_7.png | Bin 0 -> 2368 bytes .../images/navigator/models/model_8.png | Bin 0 -> 2123 bytes .../images/navigator/models/model_9.png | Bin 0 -> 1739 bytes .../images/navigator/models/model_a.png | Bin 0 -> 3279 bytes .../images/navigator/models/model_b.png | Bin 0 -> 3254 bytes .../images/navigator/models/model_c.png | Bin 0 -> 3102 bytes .../images/navigator/models/model_d.png | Bin 0 -> 3293 bytes .../images/navigator/models/model_e.png | Bin 0 -> 3267 bytes .../images/navigator/models/model_f.png | Bin 0 -> 3294 bytes .../images/navigator/models/model_g.png | Bin 0 -> 3395 bytes .../images/navigator/models/model_h.png | Bin 0 -> 3354 bytes .../images/navigator/models/model_i.png | Bin 0 -> 3690 bytes .../images/navigator/models/model_j.png | Bin 0 -> 3603 bytes .../images/navigator/models/model_k.png | Bin 0 -> 3785 bytes .../images/navigator/models/model_l.png | Bin 0 -> 3717 bytes .../images/navigator/models/model_m.png | Bin 0 -> 3559 bytes .../images/navigator/models/model_n.png | Bin 0 -> 3778 bytes .../images/navigator/models/model_o.png | Bin 0 -> 3598 bytes .../images/navigator/models/model_p.png | Bin 0 -> 3751 bytes .../images/navigator/models/model_q.png | Bin 0 -> 3874 bytes .../images/navigator/models/model_r.png | Bin 0 -> 4231 bytes .../navigator/models/model_snowwar1.png | Bin 0 -> 19090 bytes .../navigator/models/model_snowwar2.png | Bin 0 -> 19090 bytes .../images/navigator/models/model_t.png | Bin 0 -> 4329 bytes .../images/navigator/models/model_u.png | Bin 0 -> 4126 bytes .../images/navigator/models/model_v.png | Bin 0 -> 4416 bytes .../images/navigator/models/model_w.png | Bin 0 -> 4331 bytes .../images/navigator/models/model_x.png | Bin 0 -> 4103 bytes .../images/navigator/models/model_y.png | Bin 0 -> 4245 bytes .../images/navigator/models/model_z.png | Bin 0 -> 4087 bytes .../navigator/thumbnail_placeholder.png | Bin 0 -> 1801 bytes src/assets/images/nitro/nitro-dark.svg | 43 + src/assets/images/nitro/nitro-light.svg | 43 + src/assets/images/nitro/nitro-n-dark.svg | 28 + src/assets/images/nitro/nitro-n-light.svg | 29 + src/assets/images/notifications/frank.gif | Bin 0 -> 1204 bytes src/assets/images/pets/pet-package/gnome.png | Bin 0 -> 1600 bytes .../pets/pet-package/leprechaun_box.png | Bin 0 -> 1520 bytes .../images/pets/pet-package/petbox_epic.png | Bin 0 -> 2180 bytes .../images/pets/pet-package/pterosaur_egg.png | Bin 0 -> 1631 bytes .../images/pets/pet-package/val11_present.png | Bin 0 -> 2720 bytes .../pets/pet-package/velociraptor_egg.png | Bin 0 -> 1511 bytes src/assets/images/prize/prize_background.png | Bin 0 -> 5861 bytes src/assets/images/profile/icons/offline.png | Bin 0 -> 306 bytes src/assets/images/profile/icons/online.gif | Bin 0 -> 666 bytes src/assets/images/profile/icons/tick.png | Bin 0 -> 129 bytes .../room_spectator_bottom_left.png | Bin 0 -> 461 bytes .../room_spectator_bottom_right.png | Bin 0 -> 456 bytes .../room_spectator_middle_bottom.png | Bin 0 -> 98 bytes .../room_spectator_middle_left.png | Bin 0 -> 94 bytes .../room_spectator_middle_right.png | Bin 0 -> 94 bytes .../room_spectator_middle_top.png | Bin 0 -> 95 bytes .../room_spectator_top_left.png | Bin 0 -> 408 bytes .../room_spectator_top_right.png | Bin 0 -> 412 bytes .../avatar-info/preview-background.png | Bin 0 -> 7756 bytes .../images/room-widgets/camera-widget/btn.png | Bin 0 -> 830 bytes .../room-widgets/camera-widget/btn_down.png | Bin 0 -> 1000 bytes .../room-widgets/camera-widget/btn_hi.png | Bin 0 -> 935 bytes .../room-widgets/camera-widget/cam_bg.png | Bin 0 -> 2166 bytes .../camera-widget/camera-spritesheet.png | Bin 0 -> 15640 bytes .../room-widgets/camera-widget/viewfinder.png | Bin 0 -> 959 bytes .../dimmer-widget/dimmer_banner.png | Bin 0 -> 1041 bytes .../engraving-lock-spritesheet.png | Bin 0 -> 41062 bytes .../exchange-credit/exchange-credit-image.png | Bin 0 -> 9507 bytes .../monsterplant-preview.png | Bin 0 -> 2048 bytes .../mannequin-spritesheet.png | Bin 0 -> 5719 bytes .../room-widgets/playlist-editor/disk_2.png | Bin 0 -> 680 bytes .../playlist-editor/disk_image.png | Bin 0 -> 1441 bytes .../room-widgets/playlist-editor/move.png | Bin 0 -> 164 bytes .../playlist-editor/pause-btn.png | Bin 0 -> 111 bytes .../room-widgets/playlist-editor/pause.png | Bin 0 -> 114 bytes .../room-widgets/playlist-editor/playing.png | Bin 0 -> 309 bytes .../room-widgets/playlist-editor/preview.png | Bin 0 -> 147 bytes .../stickie-widget/stickie-blue.png | Bin 0 -> 401 bytes .../stickie-widget/stickie-christmas.png | Bin 0 -> 1272 bytes .../stickie-widget/stickie-close.png | Bin 0 -> 189 bytes .../stickie-widget/stickie-dreams.png | Bin 0 -> 26556 bytes .../stickie-widget/stickie-green.png | Bin 0 -> 401 bytes .../stickie-widget/stickie-heart.png | Bin 0 -> 945 bytes .../stickie-widget/stickie-juninas.png | Bin 0 -> 28162 bytes .../stickie-widget/stickie-pink.png | Bin 0 -> 758 bytes .../stickie-widget/stickie-shakesp.png | Bin 0 -> 5036 bytes .../stickie-widget/stickie-spritesheet.png | Bin 0 -> 5828 bytes .../stickie-widget/stickie-trash.png | Bin 0 -> 170 bytes .../stickie-widget/stickie-yellow.png | Bin 0 -> 401 bytes .../thumbnail-camera-spritesheet.png | Bin 0 -> 883 bytes .../trophy-widget/trophy-spritesheet.png | Bin 0 -> 6595 bytes .../wordquiz-widget/thumbs-down-small.png | Bin 0 -> 145 bytes .../wordquiz-widget/thumbs-down.png | Bin 0 -> 182 bytes .../wordquiz-widget/thumbs-up-small.png | Bin 0 -> 135 bytes .../wordquiz-widget/thumbs-up.png | Bin 0 -> 174 bytes .../room-widgets/youtube-widget/next.png | Bin 0 -> 164 bytes .../room-widgets/youtube-widget/prev.png | Bin 0 -> 249 bytes .../images/stackhelper/slider-background.png | Bin 0 -> 695 bytes .../images/stackhelper/slider-pointer.png | Bin 0 -> 373 bytes src/assets/images/toolbar/arrow.png | Bin 0 -> 14533 bytes src/assets/images/toolbar/friend-search.png | Bin 0 -> 2213 bytes .../images/toolbar/icons/buildersclub.png | Bin 0 -> 576 bytes src/assets/images/toolbar/icons/camera.png | Bin 0 -> 1732 bytes src/assets/images/toolbar/icons/catalog.png | Bin 0 -> 1202 bytes .../images/toolbar/icons/friend_all.png | Bin 0 -> 648 bytes .../images/toolbar/icons/friend_head.png | Bin 0 -> 680 bytes .../images/toolbar/icons/friend_search.png | Bin 0 -> 15241 bytes src/assets/images/toolbar/icons/game.png | Bin 0 -> 740 bytes src/assets/images/toolbar/icons/habbo.png | Bin 0 -> 1304 bytes src/assets/images/toolbar/icons/house.png | Bin 0 -> 399 bytes src/assets/images/toolbar/icons/inventory.png | Bin 0 -> 1973 bytes src/assets/images/toolbar/icons/joinroom.png | Bin 0 -> 1084 bytes .../toolbar/icons/me-menu/achievements.png | Bin 0 -> 2306 bytes .../images/toolbar/icons/me-menu/clothing.png | Bin 0 -> 2255 bytes .../images/toolbar/icons/me-menu/cog.png | Bin 0 -> 657 bytes .../images/toolbar/icons/me-menu/forums.png | Bin 0 -> 416 bytes .../toolbar/icons/me-menu/helper-tool.png | Bin 0 -> 309 bytes .../images/toolbar/icons/me-menu/my-rooms.png | Bin 0 -> 2299 bytes .../images/toolbar/icons/me-menu/profile.png | Bin 0 -> 468 bytes .../images/toolbar/icons/me-menu/rooms.png | Bin 0 -> 1465 bytes .../images/toolbar/icons/me-menu/talents.png | Bin 0 -> 397 bytes src/assets/images/toolbar/icons/message.png | Bin 0 -> 1099 bytes .../images/toolbar/icons/message_unsee.gif | Bin 0 -> 1511 bytes src/assets/images/toolbar/icons/modtools.png | Bin 0 -> 404 bytes src/assets/images/toolbar/icons/rooms.png | Bin 0 -> 1465 bytes .../images/toolbar/icons/sendmessage.png | Bin 0 -> 15647 bytes .../images/unique/catalog-info-amount-bg.png | Bin 0 -> 757 bytes .../images/unique/catalog-info-sold-out.png | Bin 0 -> 1100 bytes src/assets/images/unique/grid-bg-glass.png | Bin 0 -> 213 bytes src/assets/images/unique/grid-bg-sold-out.png | Bin 0 -> 332 bytes src/assets/images/unique/grid-bg.png | Bin 0 -> 201 bytes src/assets/images/unique/grid-count-bg.png | Bin 0 -> 155 bytes .../unique/inventory-info-amount-bg.png | Bin 0 -> 521 bytes src/assets/images/unique/numbers.png | Bin 0 -> 146 bytes .../images/wired/card-action-corners.png | Bin 0 -> 1827 bytes src/assets/images/wired/icon_action.png | Bin 0 -> 171 bytes src/assets/images/wired/icon_condition.png | Bin 0 -> 173 bytes src/assets/images/wired/icon_trigger.png | Bin 0 -> 169 bytes src/assets/images/wired/icon_wired_around.png | Bin 0 -> 197 bytes .../images/wired/icon_wired_left_right.png | Bin 0 -> 172 bytes .../images/wired/icon_wired_north_east.png | Bin 0 -> 157 bytes .../images/wired/icon_wired_north_west.png | Bin 0 -> 166 bytes .../wired/icon_wired_rotate_clockwise.png | Bin 0 -> 146 bytes .../icon_wired_rotate_counter_clockwise.png | Bin 0 -> 148 bytes .../images/wired/icon_wired_south_east.png | Bin 0 -> 171 bytes .../images/wired/icon_wired_south_west.png | Bin 0 -> 169 bytes .../images/wired/icon_wired_up_down.png | Bin 0 -> 153 bytes src/assets/styles/bootstrap/_accordion.scss | 118 + src/assets/styles/bootstrap/_alert.scss | 58 + src/assets/styles/bootstrap/_badge.scss | 29 + src/assets/styles/bootstrap/_breadcrumb.scss | 28 + .../styles/bootstrap/_button-group.scss | 139 + src/assets/styles/bootstrap/_buttons.scss | 116 + src/assets/styles/bootstrap/_card.scss | 216 ++ src/assets/styles/bootstrap/_carousel.scss | 229 ++ src/assets/styles/bootstrap/_close.scss | 40 + src/assets/styles/bootstrap/_containers.scss | 41 + src/assets/styles/bootstrap/_dropdown.scss | 240 ++ src/assets/styles/bootstrap/_forms.scss | 9 + src/assets/styles/bootstrap/_functions.scss | 298 ++ src/assets/styles/bootstrap/_grid.scss | 33 + src/assets/styles/bootstrap/_helpers.scss | 9 + src/assets/styles/bootstrap/_images.scss | 42 + src/assets/styles/bootstrap/_list-group.scss | 176 + src/assets/styles/bootstrap/_mixins.scss | 43 + src/assets/styles/bootstrap/_modal.scss | 209 ++ src/assets/styles/bootstrap/_nav.scss | 160 + src/assets/styles/bootstrap/_navbar.scss | 335 ++ src/assets/styles/bootstrap/_offcanvas.scss | 83 + src/assets/styles/bootstrap/_pagination.scss | 64 + .../styles/bootstrap/_placeholders.scss | 51 + src/assets/styles/bootstrap/_popover.scss | 158 + src/assets/styles/bootstrap/_progress.scss | 48 + src/assets/styles/bootstrap/_reboot.scss | 609 ++++ src/assets/styles/bootstrap/_root.scss | 53 + src/assets/styles/bootstrap/_spinners.scss | 69 + src/assets/styles/bootstrap/_tables.scss | 151 + src/assets/styles/bootstrap/_toasts.scss | 51 + src/assets/styles/bootstrap/_tooltip.scss | 115 + src/assets/styles/bootstrap/_transitions.scss | 27 + src/assets/styles/bootstrap/_type.scss | 104 + src/assets/styles/bootstrap/_utilities.scss | 638 ++++ src/assets/styles/bootstrap/_variables.scss | 1683 +++++++++ .../styles/bootstrap/bootstrap-grid.scss | 65 + .../styles/bootstrap/bootstrap-reboot.scss | 15 + .../styles/bootstrap/bootstrap-utilities.scss | 18 + src/assets/styles/bootstrap/bootstrap.scss | 53 + .../bootstrap/forms/_floating-labels.scss | 63 + .../styles/bootstrap/forms/_form-check.scss | 152 + .../styles/bootstrap/forms/_form-control.scss | 219 ++ .../styles/bootstrap/forms/_form-range.scss | 91 + .../styles/bootstrap/forms/_form-select.scss | 70 + .../styles/bootstrap/forms/_form-text.scss | 11 + .../styles/bootstrap/forms/_input-group.scss | 121 + .../styles/bootstrap/forms/_labels.scss | 36 + .../styles/bootstrap/forms/_validation.scss | 12 + .../styles/bootstrap/helpers/_clearfix.scss | 3 + .../bootstrap/helpers/_colored-links.scss | 12 + .../styles/bootstrap/helpers/_position.scss | 30 + .../styles/bootstrap/helpers/_ratio.scss | 26 + .../styles/bootstrap/helpers/_stacks.scss | 15 + .../bootstrap/helpers/_stretched-link.scss | 15 + .../bootstrap/helpers/_text-truncation.scss | 7 + .../bootstrap/helpers/_visually-hidden.scss | 8 + src/assets/styles/bootstrap/helpers/_vr.scss | 8 + .../styles/bootstrap/mixins/_alert.scss | 11 + .../styles/bootstrap/mixins/_backdrop.scss | 14 + .../bootstrap/mixins/_border-radius.scss | 78 + .../styles/bootstrap/mixins/_box-shadow.scss | 18 + .../styles/bootstrap/mixins/_breakpoints.scss | 127 + .../styles/bootstrap/mixins/_buttons.scss | 133 + .../styles/bootstrap/mixins/_caret.scss | 64 + .../styles/bootstrap/mixins/_clearfix.scss | 9 + .../bootstrap/mixins/_color-scheme.scss | 7 + .../styles/bootstrap/mixins/_container.scss | 9 + .../styles/bootstrap/mixins/_deprecate.scss | 10 + .../styles/bootstrap/mixins/_forms.scss | 144 + .../styles/bootstrap/mixins/_gradients.scss | 47 + src/assets/styles/bootstrap/mixins/_grid.scss | 150 + .../styles/bootstrap/mixins/_image.scss | 16 + .../styles/bootstrap/mixins/_list-group.scss | 24 + .../styles/bootstrap/mixins/_lists.scss | 7 + .../styles/bootstrap/mixins/_pagination.scss | 31 + .../styles/bootstrap/mixins/_reset-text.scss | 17 + .../styles/bootstrap/mixins/_resize.scss | 6 + .../bootstrap/mixins/_table-variants.scss | 21 + .../bootstrap/mixins/_text-truncate.scss | 8 + .../styles/bootstrap/mixins/_transition.scss | 26 + .../styles/bootstrap/mixins/_utilities.scss | 89 + .../bootstrap/mixins/_visually-hidden.scss | 29 + .../styles/bootstrap/utilities/_api.scss | 47 + src/assets/styles/bootstrap/vendor/_rfs.scss | 355 ++ src/assets/styles/fonts.scss | 4 + src/assets/styles/icons.scss | 629 ++++ src/assets/styles/index.scss | 26 + src/assets/styles/scrollbars.scss | 53 + src/assets/styles/slider.scss | 54 + src/assets/styles/utils.scss | 134 + src/assets/webfonts/Ubuntu-C.ttf | Bin 0 -> 369840 bytes src/assets/webfonts/Ubuntu-b.ttf | Bin 0 -> 152496 bytes src/assets/webfonts/Ubuntu-i.ttf | Bin 0 -> 160792 bytes src/assets/webfonts/Ubuntu-ib.ttf | Bin 0 -> 148524 bytes src/assets/webfonts/Ubuntu-m.ttf | Bin 0 -> 140976 bytes src/assets/webfonts/Ubuntu.ttf | Bin 0 -> 155908 bytes src/common/AutoGrid.tsx | 28 + src/common/Base.tsx | 84 + src/common/Button.tsx | 35 + src/common/ButtonGroup.tsx | 22 + src/common/Column.tsx | 46 + src/common/Flex.tsx | 50 + src/common/FormGroup.tsx | 22 + src/common/Grid.tsx | 63 + src/common/GridContext.tsx | 17 + src/common/HorizontalRule.tsx | 38 + src/common/InfiniteGrid.tsx | 79 + src/common/InfiniteScroll.tsx | 55 + src/common/Text.tsx | 63 + src/common/card/NitroCardContentView.tsx | 18 + src/common/card/NitroCardContext.tsx | 17 + src/common/card/NitroCardHeaderView.tsx | 48 + src/common/card/NitroCardSubHeaderView.tsx | 23 + src/common/card/NitroCardView.scss | 248 ++ src/common/card/NitroCardView.tsx | 34 + .../accordion/NitroCardAccordionContext.tsx | 21 + .../accordion/NitroCardAccordionItemView.tsx | 18 + .../accordion/NitroCardAccordionSetView.tsx | 84 + .../card/accordion/NitroCardAccordionView.tsx | 25 + src/common/card/accordion/index.ts | 4 + src/common/card/index.ts | 7 + .../card/tabs/NitroCardTabsItemView.tsx | 35 + src/common/card/tabs/NitroCardTabsView.tsx | 22 + src/common/card/tabs/index.ts | 2 + src/common/classNames.ts | 1 + .../draggable-window/DraggableWindow.tsx | 270 ++ .../DraggableWindowPosition.ts | 7 + src/common/draggable-window/index.ts | 2 + src/common/index.scss | 562 +++ src/common/index.ts | 23 + src/common/layout/LayoutAvatarImageView.tsx | 103 + src/common/layout/LayoutBackgroundImage.tsx | 23 + src/common/layout/LayoutBadgeImageView.tsx | 106 + src/common/layout/LayoutCounterTimeView.tsx | 43 + src/common/layout/LayoutCurrencyIcon.tsx | 44 + .../layout/LayoutFurniIconImageView.tsx | 17 + src/common/layout/LayoutFurniImageView.tsx | 70 + src/common/layout/LayoutGiftTagView.tsx | 41 + src/common/layout/LayoutGridItem.tsx | 75 + src/common/layout/LayoutImage.tsx | 13 + src/common/layout/LayoutItemCountView.tsx | 28 + .../layout/LayoutLoadingSpinnerView.tsx | 15 + src/common/layout/LayoutMiniCameraView.tsx | 44 + .../layout/LayoutNotificationAlertView.tsx | 35 + .../layout/LayoutNotificationBubbleView.tsx | 52 + src/common/layout/LayoutPetImageView.tsx | 123 + .../layout/LayoutPrizeProductImageView.tsx | 30 + src/common/layout/LayoutProgressBar.tsx | 34 + src/common/layout/LayoutRarityLevelView.tsx | 28 + .../layout/LayoutRoomObjectImageView.tsx | 59 + src/common/layout/LayoutRoomPreviewerView.tsx | 88 + src/common/layout/LayoutRoomThumbnailView.tsx | 37 + src/common/layout/LayoutTrophyView.tsx | 42 + src/common/layout/UserProfileIconView.tsx | 29 + src/common/layout/index.ts | 24 + .../LayoutLimitedEditionCompactPlateView.tsx | 35 + .../LayoutLimitedEditionCompletePlateView.tsx | 41 + .../LayoutLimitedEditionStyledNumberView.tsx | 18 + src/common/layout/limited-edition/index.ts | 3 + .../transitions/TransitionAnimation.tsx | 52 + .../transitions/TransitionAnimationStyles.ts | 136 + .../transitions/TransitionAnimationTypes.ts | 11 + src/common/transitions/index.ts | 3 + src/common/types/AlignItemType.ts | 1 + src/common/types/AlignSelfType.ts | 1 + src/common/types/ButtonSizeType.ts | 1 + src/common/types/ColorVariantType.ts | 1 + src/common/types/ColumnSizesType.ts | 1 + src/common/types/DisplayType.ts | 1 + src/common/types/FloatType.ts | 1 + src/common/types/FontSizeType.ts | 1 + src/common/types/FontWeightType.ts | 1 + src/common/types/JustifyContentType.ts | 1 + src/common/types/OverflowType.ts | 1 + src/common/types/PositionType.ts | 1 + src/common/types/SpacingType.ts | 1 + src/common/types/TextAlignType.ts | 1 + src/common/types/index.ts | 14 + src/common/utils/CreateTransitionToIcon.ts | 13 + src/common/utils/FriendlyTimeView.tsx | 28 + src/common/utils/index.ts | 2 + .../achievements/AchievementsView.scss | 15 + .../achievements/AchievementsView.tsx | 72 + .../views/AchievementBadgeView.tsx | 19 + .../views/AchievementCategoryView.tsx | 37 + .../views/AchievementDetailsView.tsx | 53 + .../AchievementListItemView.tsx | 24 + .../achievement-list/AchievementListView.tsx | 20 + .../views/achievement-list/index.ts | 2 + .../AchievementsCategoryListItemView.tsx | 31 + .../AchievementsCategoryListView.tsx | 22 + .../achievements/views/category-list/index.ts | 2 + src/components/achievements/views/index.ts | 5 + .../avatar-editor-new/AvatarEditorView.scss | 336 ++ .../avatar-editor-new/AvatarEditorView.tsx | 114 + .../views/AvatarEditorIcon.tsx | 30 + .../views/AvatarEditorModelView.tsx | 85 + .../AvatarEditorFigureSetItemView.tsx | 53 + .../figure-set/AvatarEditorFigureSetView.tsx | 36 + .../views/figure-set/index.ts | 2 + .../AvatarEditorPaletteSetItemView.tsx | 27 + .../AvatarEditorPaletteSetView.tsx | 33 + .../views/palette-set/index.ts | 2 + .../avatar-editor/AvatarEditorView.scss | 336 ++ .../avatar-editor/AvatarEditorView.tsx | 316 ++ .../views/AvatarEditorFigurePreviewView.tsx | 55 + .../avatar-editor/views/AvatarEditorIcon.tsx | 30 + .../views/AvatarEditorModelView.tsx | 88 + .../views/AvatarEditorWardrobeView.tsx | 79 + .../AvatarEditorFigureSetItemView.tsx | 35 + .../figure-set/AvatarEditorFigureSetView.tsx | 44 + .../avatar-editor/views/figure-set/index.ts | 2 + src/components/avatar-editor/views/index.ts | 6 + .../AvatarEditorPaletteSetItemView.tsx | 32 + .../AvatarEditorPaletteSetView.tsx | 41 + .../avatar-editor/views/palette-set/index.ts | 2 + src/components/camera/CameraWidgetView.scss | 133 + src/components/camera/CameraWidgetView.tsx | 96 + src/components/camera/index.ts | 4 + .../camera/views/CameraWidgetCaptureView.tsx | 90 + .../camera/views/CameraWidgetCheckoutView.tsx | 159 + .../views/CameraWidgetShowPhotoView.tsx | 71 + .../views/editor/CameraWidgetEditorView.tsx | 243 ++ .../CameraWidgetEffectListItemView.tsx | 40 + .../CameraWidgetEffectListView.tsx | 31 + .../camera/views/editor/effect-list/index.ts | 2 + src/components/camera/views/editor/index.ts | 2 + src/components/camera/views/index.ts | 5 + src/components/campaign/CalendarItemView.tsx | 53 + src/components/campaign/CalendarView.tsx | 144 + src/components/campaign/CampaignView.scss | 71 + src/components/campaign/CampaignView.tsx | 101 + src/components/catalog/CatalogView.scss | 158 + src/components/catalog/CatalogView.tsx | 111 + .../views/CatalogPurchaseConfirmView.tsx | 10 + .../catalog-header/CatalogHeaderView.tsx | 26 + .../views/catalog-icon/CatalogIconView.tsx | 20 + .../CatalogRoomPreviewerView.tsx | 44 + .../catalog/views/gift/CatalogGiftView.tsx | 289 ++ .../navigation/CatalogNavigationItemView.tsx | 35 + .../navigation/CatalogNavigationSetView.tsx | 25 + .../navigation/CatalogNavigationView.tsx | 34 + .../page/common/CatalogGridOfferView.tsx | 59 + .../page/common/CatalogRedeemVoucherView.tsx | 60 + .../views/page/common/CatalogSearchView.tsx | 94 + .../views/page/layout/CatalogLayout.types.ts | 7 + .../layout/CatalogLayoutBadgeDisplayView.tsx | 54 + .../layout/CatalogLayoutColorGroupingView.tsx | 176 + .../page/layout/CatalogLayoutDefaultView.tsx | 61 + .../CatalogLayoutGuildCustomFurniView.tsx | 48 + .../layout/CatalogLayoutGuildForumView.tsx | 49 + .../CatalogLayoutGuildFrontpageView.tsx | 30 + .../layout/CatalogLayoutInfoLoyaltyView.tsx | 15 + .../page/layout/CatalogLayoutPets2View.tsx | 8 + .../page/layout/CatalogLayoutPets3View.tsx | 25 + .../page/layout/CatalogLayoutRoomAdsView.tsx | 113 + .../layout/CatalogLayoutRoomBundleView.tsx | 41 + .../layout/CatalogLayoutSingleBundleView.tsx | 41 + .../layout/CatalogLayoutSoundMachineView.tsx | 113 + .../page/layout/CatalogLayoutSpacesView.tsx | 47 + .../page/layout/CatalogLayoutTrophiesView.tsx | 56 + .../page/layout/CatalogLayoutVipBuyView.tsx | 191 + .../views/page/layout/GetCatalogLayout.tsx | 80 + .../CatalogLayoutFrontPageItemView.tsx | 37 + .../CatalogLayoutFrontpage4View.tsx | 49 + .../CatalogLayoutMarketplaceItemView.tsx | 83 + .../CatalogLayoutMarketplaceOwnItemsView.tsx | 102 + ...atalogLayoutMarketplacePublicItemsView.tsx | 161 + ...CatalogLayoutMarketplaceSearchFormView.tsx | 71 + .../marketplace/MarketplacePostOfferView.tsx | 121 + .../page/layout/pets/CatalogLayoutPetView.tsx | 243 ++ .../vip-gifts/CatalogLayoutVipGiftsView.tsx | 62 + .../page/layout/vip-gifts/VipGiftItemView.tsx | 63 + .../widgets/CatalogAddOnBadgeWidgetView.tsx | 18 + .../CatalogBadgeSelectorWidgetView.tsx | 76 + .../widgets/CatalogBundleGridWidgetView.tsx | 29 + .../CatalogFirstProductSelectorWidgetView.tsx | 16 + .../widgets/CatalogGuildBadgeWidgetView.tsx | 31 + .../CatalogGuildSelectorWidgetView.tsx | 75 + .../widgets/CatalogItemGridWidgetView.tsx | 52 + .../widgets/CatalogLimitedItemWidgetView.tsx | 19 + .../widgets/CatalogPriceDisplayWidgetView.tsx | 37 + .../widgets/CatalogPurchaseWidgetView.tsx | 164 + .../widgets/CatalogSimplePriceWidgetView.tsx | 21 + .../widgets/CatalogSingleViewWidgetView.tsx | 7 + .../page/widgets/CatalogSpacesWidgetView.tsx | 115 + .../page/widgets/CatalogSpinnerWidgetView.tsx | 46 + .../page/widgets/CatalogTotalPriceWidget.tsx | 20 + .../widgets/CatalogViewProductWidgetView.tsx | 99 + .../catalog/views/targeted-offer/Offer.scss | 27 + .../views/targeted-offer/OfferBubbleView.tsx | 16 + .../views/targeted-offer/OfferView.tsx | 32 + .../views/targeted-offer/OfferWindowView.tsx | 82 + .../chat-history/ChatHistoryView.scss | 4 + .../chat-history/ChatHistoryView.tsx | 96 + .../FloorplanEditorContext.tsx | 22 + .../floorplan-editor/FloorplanEditorView.scss | 9 + .../floorplan-editor/FloorplanEditorView.tsx | 160 + .../floorplan-editor/common/ActionSettings.ts | 39 + .../floorplan-editor/common/Constants.ts | 44 + .../common/ConvertMapToString.ts | 1 + .../common/FloorplanEditor.ts | 375 ++ .../common/FloorplanResource.ts | 227 ++ .../common/IFloorplanSettings.ts | 8 + .../common/IVisualizationSettings.ts | 7 + .../floorplan-editor/common/Tile.ts | 31 + .../floorplan-editor/common/Utils.ts | 53 + .../views/FloorplanCanvasView.tsx | 199 ++ .../views/FloorplanImportExportView.tsx | 55 + .../views/FloorplanOptionsView.tsx | 189 + src/components/friends/FriendsView.scss | 244 ++ src/components/friends/FriendsView.tsx | 21 + .../views/friends-bar/FriendBarItemView.tsx | 59 + .../views/friends-bar/FriendsBarView.tsx | 26 + .../FriendsListRemoveConfirmationView.tsx | 29 + .../FriendsListRoomInviteView.tsx | 31 + .../friends-list/FriendsListSearchView.tsx | 103 + .../views/friends-list/FriendsListView.tsx | 150 + .../FriendsListGroupItemView.tsx | 85 + .../FriendsListGroupView.tsx | 23 + .../FriendsListRequestItemView.tsx | 25 + .../FriendsListRequestView.tsx | 29 + .../views/messenger/FriendsMessengerView.tsx | 177 + .../FriendsMessengerThreadGroup.tsx | 73 + .../FriendsMessengerThreadView.tsx | 16 + .../game-center/GameCenterView.scss | 44 + src/components/game-center/GameCenterView.tsx | 49 + .../game-center/views/GameListView.tsx | 32 + .../game-center/views/GameStageView.tsx | 47 + src/components/game-center/views/GameView.tsx | 58 + src/components/groups/GroupView.scss | 190 + src/components/groups/GroupsView.tsx | 63 + .../groups/views/GroupBadgeCreatorView.tsx | 83 + .../groups/views/GroupCreatorView.tsx | 164 + .../views/GroupInformationStandaloneView.tsx | 29 + .../groups/views/GroupInformationView.tsx | 146 + .../groups/views/GroupManagerView.tsx | 119 + .../groups/views/GroupMembersView.tsx | 210 ++ .../groups/views/GroupRoomInformationView.tsx | 132 + .../groups/views/tabs/GroupTabBadgeView.tsx | 120 + .../groups/views/tabs/GroupTabColorsView.tsx | 127 + .../tabs/GroupTabCreatorConfirmationView.tsx | 67 + .../views/tabs/GroupTabIdentityView.tsx | 116 + .../views/tabs/GroupTabSettingsView.tsx | 89 + src/components/guide-tool/GuideToolView.scss | 87 + src/components/guide-tool/GuideToolView.tsx | 356 ++ .../guide-tool/views/GuideToolAcceptView.tsx | 35 + .../guide-tool/views/GuideToolMenuView.tsx | 76 + .../guide-tool/views/GuideToolOngoingView.tsx | 130 + .../views/GuideToolUserCreateRequestView.tsx | 32 + .../views/GuideToolUserFeedbackView.tsx | 43 + .../views/GuideToolUserNoHelpersView.tsx | 13 + .../views/GuideToolUserPendingView.tsx | 33 + .../views/GuideToolUserSomethingWrogView.tsx | 12 + .../views/GuideToolUserThanksView.tsx | 13 + src/components/hc-center/HcCenterView.scss | 44 + src/components/hc-center/HcCenterView.tsx | 204 ++ src/components/help/HelpView.scss | 18 + src/components/help/HelpView.tsx | 116 + .../help/views/DescribeReportView.tsx | 48 + src/components/help/views/HelpIndexView.tsx | 37 + .../help/views/ReportSummaryView.tsx | 57 + .../help/views/SanctionStatusView.tsx | 75 + .../help/views/SelectReportedChatsView.tsx | 90 + .../help/views/SelectReportedUserView.tsx | 85 + src/components/help/views/SelectTopicView.tsx | 53 + .../NameChangeConfirmationView.tsx | 44 + .../views/name-change/NameChangeInitView.tsx | 20 + .../views/name-change/NameChangeInputView.tsx | 97 + .../help/views/name-change/NameChangeView.tsx | 66 + .../views/name-change/NameChangeView.types.ts | 5 + src/components/hotel-view/HotelView.scss | 97 + src/components/hotel-view/HotelView.tsx | 106 + .../views/widgets/GetWidgetLayout.tsx | 29 + .../views/widgets/HotelViewWidgets.scss | 4 + .../views/widgets/WidgetSlotView.tsx | 20 + .../bonus-rare/BonusRareWidgetView.scss | 10 + .../bonus-rare/BonusRareWidgetView.tsx | 42 + .../hall-of-fame-item/HallOfFameItemView.tsx | 27 + .../hall-of-fame/HallOfFameWidgetView.scss | 70 + .../hall-of-fame/HallOfFameWidgetView.tsx | 37 + .../HallOfFameWidgetView.types.ts | 5 + .../promo-article/PromoArticleWidgetView.scss | 27 + .../promo-article/PromoArticleWidgetView.tsx | 46 + .../widget-container/WidgetContainerView.scss | 9 + .../widget-container/WidgetContainerView.tsx | 39 + src/components/index.scss | 26 + src/components/inventory/InventoryView.scss | 17 + src/components/inventory/InventoryView.tsx | 152 + .../views/InventoryCategoryEmptyView.tsx | 26 + .../views/badge/InventoryBadgeItemView.tsx | 19 + .../views/badge/InventoryBadgeView.tsx | 66 + .../views/bot/InventoryBotItemView.tsx | 43 + .../inventory/views/bot/InventoryBotView.tsx | 91 + .../furniture/InventoryFurnitureItemView.tsx | 38 + .../InventoryFurnitureSearchView.tsx | 47 + .../furniture/InventoryFurnitureView.tsx | 146 + .../views/furniture/InventoryTradeView.tsx | 279 ++ .../views/pet/InventoryPetItemView.tsx | 43 + .../inventory/views/pet/InventoryPetView.tsx | 90 + src/components/loading/LoadingView.scss | 70 + src/components/loading/LoadingView.tsx | 21 + src/components/main/MainView.tsx | 113 + src/components/mod-tools/ModToolsView.scss | 70 + src/components/mod-tools/ModToolsView.tsx | 147 + .../mod-tools/views/chatlog/ChatlogRecord.ts | 11 + .../mod-tools/views/chatlog/ChatlogView.tsx | 91 + .../views/room/ModToolsChatlogView.tsx | 44 + .../mod-tools/views/room/ModToolsRoomView.tsx | 117 + .../views/tickets/CfhChatlogView.tsx | 41 + .../views/tickets/ModToolsIssueInfoView.tsx | 86 + .../views/tickets/ModToolsMyIssuesTabView.tsx | 47 + .../tickets/ModToolsOpenIssuesTabView.tsx | 42 + .../tickets/ModToolsPickedIssuesTabView.tsx | 39 + .../views/tickets/ModToolsTicketsView.tsx | 90 + .../views/user/ModToolsUserChatlogView.tsx | 44 + .../views/user/ModToolsUserModActionView.tsx | 176 + .../views/user/ModToolsUserRoomVisitsView.tsx | 60 + .../user/ModToolsUserSendMessageView.tsx | 45 + .../mod-tools/views/user/ModToolsUserView.tsx | 156 + src/components/navigator/NavigatorView.scss | 65 + src/components/navigator/NavigatorView.tsx | 233 ++ .../views/NavigatorDoorStateView.tsx | 110 + .../views/NavigatorRoomCreatorView.tsx | 122 + .../navigator/views/NavigatorRoomInfoView.tsx | 180 + .../navigator/views/NavigatorRoomLinkView.tsx | 33 + .../NavigatorRoomSettingsAccessTabView.tsx | 88 + .../NavigatorRoomSettingsBasicTabView.tsx | 171 + .../NavigatorRoomSettingsModTabView.tsx | 118 + .../NavigatorRoomSettingsRightsTabView.tsx | 91 + .../NavigatorRoomSettingsView.tsx | 206 ++ .../NavigatorRoomSettingsVipChatTabView.tsx | 76 + .../NavigatorSearchResultItemInfoView.tsx | 81 + .../search/NavigatorSearchResultItemView.tsx | 121 + .../search/NavigatorSearchResultView.tsx | 118 + .../views/search/NavigatorSearchView.tsx | 84 + src/components/nitropedia/NitropediaView.scss | 47 + src/components/nitropedia/NitropediaView.tsx | 105 + .../NotificationCenterView.scss | 59 + .../NotificationCenterView.tsx | 77 + .../views/alert-layouts/GetAlertLayout.tsx | 21 + .../alert-layouts/NitroSystemAlertView.tsx | 39 + .../NotificationDefaultAlertView.tsx | 56 + .../NotificationSearchAlertView.tsx | 61 + .../views/bubble-layouts/GetBubbleLayout.tsx | 18 + .../NotificationClubGiftBubbleView.tsx | 26 + .../NotificationDefaultBubbleView.tsx | 25 + .../confirm-layouts/GetConfirmLayout.tsx | 15 + .../NotificationDefaultConfirmView.tsx | 40 + src/components/purse/PurseView.scss | 26 + src/components/purse/PurseView.tsx | 90 + src/components/purse/views/CurrencyView.tsx | 39 + src/components/purse/views/SeasonalView.tsx | 24 + src/components/right-side/RightSideView.scss | 10 + src/components/right-side/RightSideView.tsx | 24 + src/components/room/RoomView.scss | 2 + src/components/room/RoomView.tsx | 46 + .../room/spectator/RoomSpectatorView.scss | 26 + .../room/spectator/RoomSpectatorView.tsx | 8 + src/components/room/widgets/RoomWidgets.scss | 110 + .../room/widgets/RoomWidgetsView.tsx | 172 + .../AvatarInfoPetTrainingPanelView.tsx | 59 + .../AvatarInfoRentableBotChatView.tsx | 68 + .../AvatarInfoUseProductConfirmView.tsx | 281 ++ .../avatar-info/AvatarInfoUseProductView.tsx | 135 + .../avatar-info/AvatarInfoWidgetView.scss | 134 + .../avatar-info/AvatarInfoWidgetView.tsx | 139 + .../infostand/InfoStandWidgetBotView.tsx | 55 + .../infostand/InfoStandWidgetFurniView.tsx | 474 +++ .../infostand/InfoStandWidgetPetView.tsx | 209 ++ .../InfoStandWidgetRentableBotView.tsx | 84 + ...nfoStandWidgetUserRelationshipItemView.tsx | 31 + .../InfoStandWidgetUserRelationshipsView.tsx | 23 + .../infostand/InfoStandWidgetUserTagsView.tsx | 31 + .../infostand/InfoStandWidgetUserView.tsx | 215 ++ .../menu/AvatarInfoWidgetAvatarView.tsx | 372 ++ .../menu/AvatarInfoWidgetDecorateView.tsx | 29 + .../menu/AvatarInfoWidgetFurniView.tsx | 67 + .../menu/AvatarInfoWidgetNameView.tsx | 32 + .../menu/AvatarInfoWidgetOwnAvatarView.tsx | 292 ++ .../menu/AvatarInfoWidgetOwnPetView.tsx | 220 ++ .../menu/AvatarInfoWidgetPetView.tsx | 138 + .../menu/AvatarInfoWidgetRentableBotView.tsx | 197 ++ .../chat-input/ChatInputStyleSelectorView.tsx | 68 + .../widgets/chat-input/ChatInputView.scss | 84 + .../room/widgets/chat-input/ChatInputView.tsx | 248 ++ .../widgets/chat/ChatWidgetMessageView.tsx | 95 + .../room/widgets/chat/ChatWidgetView.scss | 919 +++++ .../room/widgets/chat/ChatWidgetView.tsx | 162 + .../widgets/choosers/ChooserWidgetView.scss | 4 + .../widgets/choosers/ChooserWidgetView.tsx | 51 + .../choosers/FurniChooserWidgetView.tsx | 31 + .../choosers/UserChooserWidgetView.tsx | 31 + .../widgets/context-menu/ContextMenu.scss | 129 + .../context-menu/ContextMenuCaretView.tsx | 26 + .../context-menu/ContextMenuHeaderView.tsx | 18 + .../context-menu/ContextMenuListItemView.tsx | 32 + .../context-menu/ContextMenuListView.tsx | 18 + .../widgets/context-menu/ContextMenuView.tsx | 170 + .../widgets/doorbell/DoorbellWidgetView.tsx | 51 + .../FriendRequestDialogView.scss | 4 + .../FriendRequestDialogView.tsx | 28 + .../FriendRequestWidgetView.tsx | 17 + .../FurnitureBackgroundColorView.tsx | 30 + .../furniture/FurnitureBadgeDisplayView.tsx | 12 + .../furniture/FurnitureCraftingView.tsx | 115 + .../widgets/furniture/FurnitureDimmerView.tsx | 86 + .../furniture/FurnitureExchangeCreditView.tsx | 33 + .../furniture/FurnitureExternalImageView.tsx | 23 + .../furniture/FurnitureFriendFurniView.tsx | 66 + .../furniture/FurnitureGiftOpeningView.tsx | 73 + .../furniture/FurnitureHighScoreView.tsx | 60 + .../furniture/FurnitureInternalLinkView.tsx | 9 + .../furniture/FurnitureMannequinView.tsx | 144 + .../FurnitureMysteryBoxOpenDialogView.tsx | 81 + .../FurnitureMysteryTrophyOpenDialogView.tsx | 50 + .../furniture/FurnitureRoomLinkView.tsx | 9 + .../furniture/FurnitureSpamWallPostItView.tsx | 46 + .../furniture/FurnitureStackHeightView.tsx | 58 + .../furniture/FurnitureStickieView.tsx | 66 + .../widgets/furniture/FurnitureTrophyView.tsx | 12 + .../widgets/furniture/FurnitureWidgets.scss | 526 +++ .../furniture/FurnitureWidgetsView.tsx | 48 + .../furniture/FurnitureYoutubeDisplayView.tsx | 109 + .../context-menu/EffectBoxConfirmView.tsx | 40 + .../context-menu/FurnitureContextMenuView.tsx | 130 + .../MonsterPlantSeedConfirmView.tsx | 85 + .../PurchasableClothingConfirmView.tsx | 104 + .../playlist-editor/DiskInventoryView.tsx | 94 + .../FurniturePlaylistEditorWidgetView.tsx | 29 + .../playlist-editor/SongPlaylistView.tsx | 79 + .../mysterybox/MysteryBoxExtensionView.scss | 52 + .../mysterybox/MysteryBoxExtensionView.tsx | 67 + .../object-location/ObjectLocationView.tsx | 61 + .../pet-package/PetPackageWidgetView.scss | 106 + .../pet-package/PetPackageWidgetView.tsx | 42 + .../RoomFilterWordsWidgetView.tsx | 74 + .../room-promotes/RoomPromotesWidgetView.tsx | 55 + .../views/RoomPromoteEditWidgetView.tsx | 44 + .../views/RoomPromoteMyOwnEventWidgetView.tsx | 36 + .../views/RoomPromoteOtherEventWidgetView.tsx | 30 + .../room/widgets/room-promotes/views/index.ts | 3 + .../RoomThumbnailWidgetView.tsx | 41 + .../room-tools/RoomToolsWidgetView.tsx | 100 + .../user-location/UserLocationView.tsx | 24 + .../word-quiz/WordQuizQuestionView.tsx | 44 + .../widgets/word-quiz/WordQuizVoteView.tsx | 24 + .../widgets/word-quiz/WordQuizWidgetView.tsx | 19 + src/components/toolbar/ToolbarMeView.tsx | 52 + src/components/toolbar/ToolbarView.scss | 81 + src/components/toolbar/ToolbarView.tsx | 111 + .../user-profile/UserProfileVew.scss | 67 + .../user-profile/UserProfileView.tsx | 122 + .../views/BadgesContainerView.tsx | 25 + .../views/FriendsContainerView.tsx | 28 + .../views/GroupsContainerView.tsx | 90 + .../views/RelationshipsContainerView.tsx | 62 + .../user-profile/views/UserContainerView.tsx | 74 + .../user-settings/UserSettingsView.tsx | 187 + src/components/wired/WiredView.scss | 175 + src/components/wired/WiredView.tsx | 21 + src/components/wired/views/WiredBaseView.tsx | 114 + .../wired/views/WiredFurniSelectorView.tsx | 16 + .../views/actions/WiredActionBaseView.tsx | 41 + .../WiredActionBotChangeFigureView.tsx | 38 + .../WiredActionBotFollowAvatarView.tsx | 43 + .../WiredActionBotGiveHandItemView.tsx | 42 + .../views/actions/WiredActionBotMoveView.tsx | 27 + .../WiredActionBotTalkToAvatarView.tsx | 52 + .../views/actions/WiredActionBotTalkView.tsx | 52 + .../actions/WiredActionBotTeleportView.tsx | 27 + .../WiredActionCallAnotherStackView.tsx | 8 + .../views/actions/WiredActionChaseView.tsx | 8 + .../views/actions/WiredActionChatView.tsx | 27 + .../views/actions/WiredActionFleeView.tsx | 8 + .../actions/WiredActionGiveRewardView.tsx | 160 + ...redActionGiveScoreToPredefinedTeamView.tsx | 67 + .../actions/WiredActionGiveScoreView.tsx | 52 + .../views/actions/WiredActionJoinTeamView.tsx | 35 + .../actions/WiredActionKickFromRoomView.tsx | 27 + .../views/actions/WiredActionLayoutView.tsx | 85 + .../actions/WiredActionLeaveTeamView.tsx | 8 + .../WiredActionMoveAndRotateFurniView.tsx | 82 + .../actions/WiredActionMoveFurniToView.tsx | 76 + .../actions/WiredActionMoveFurniView.tsx | 100 + .../views/actions/WiredActionMuteUserView.tsx | 43 + .../views/actions/WiredActionResetView.tsx | 8 + .../WiredActionSetFurniStateToView.tsx | 42 + .../views/actions/WiredActionTeleportView.tsx | 8 + .../WiredActionToggleFurniStateView.tsx | 8 + .../WiredConditionActorHasHandItem.tsx | 34 + .../WiredConditionActorIsGroupMemberView.tsx | 8 + .../WiredConditionActorIsOnFurniView.tsx | 8 + .../WiredConditionActorIsTeamMemberView.tsx | 37 + .../WiredConditionActorIsWearingBadgeView.tsx | 27 + ...WiredConditionActorIsWearingEffectView.tsx | 27 + .../conditions/WiredConditionBaseView.tsx | 23 + .../WiredConditionDateRangeView.tsx | 58 + .../WiredConditionFurniHasAvatarOnView.tsx | 8 + .../WiredConditionFurniHasFurniOnView.tsx | 35 + .../WiredConditionFurniHasNotFurniOnView.tsx | 35 + .../WiredConditionFurniIsOfTypeView.tsx | 8 + ...WiredConditionFurniMatchesSnapshotView.tsx | 42 + .../conditions/WiredConditionLayoutView.tsx | 64 + .../WiredConditionTimeElapsedLessView.tsx | 33 + .../WiredConditionTimeElapsedMoreView.tsx | 33 + .../WiredConditionUserCountInRoomView.tsx | 52 + .../WiredTriggerAvatarEnterRoomView.tsx | 38 + .../WiredTriggerAvatarSaysSomethingView.tsx | 45 + .../WiredTriggerAvatarWalksOffFurniView.tsx | 8 + .../WiredTriggerAvatarWalksOnFurni.tsx | 8 + .../views/triggers/WiredTriggerBaseView.tsx | 23 + .../WiredTriggerBotReachedAvatarView.tsx | 27 + .../WiredTriggerBotReachedStuffView.tsx | 27 + .../triggers/WiredTriggerCollisionView.tsx | 8 + .../triggers/WiredTriggerExecuteOnceView.tsx | 33 + ...iredTriggerExecutePeriodicallyLongView.tsx | 33 + .../WiredTriggerExecutePeriodicallyView.tsx | 33 + .../triggers/WiredTriggerGameEndsView.tsx | 8 + .../triggers/WiredTriggerGameStartsView.tsx | 8 + .../views/triggers/WiredTriggerLayoutView.tsx | 52 + .../WiredTriggerScoreAchievedView.tsx | 33 + .../triggers/WiredTriggerToggleFurniView.tsx | 8 + src/events/catalog/CatalogEvent.ts | 14 + src/events/catalog/CatalogInitGiftEvent.ts | 32 + .../CatalogPostMarketplaceOfferEvent.ts | 20 + .../catalog/CatalogPurchaseFailureEvent.ts | 20 + .../catalog/CatalogPurchaseNotAllowedEvent.ts | 20 + .../catalog/CatalogPurchaseOverrideEvent.ts | 19 + .../catalog/CatalogPurchaseSoldOutEvent.ts | 11 + src/events/catalog/CatalogPurchasedEvent.ts | 20 + .../CatalogSetRoomPreviewerStuffDataEvent.ts | 19 + src/events/catalog/CatalogWidgetEvent.ts | 26 + .../catalog/SetRoomPreviewerStuffDataEvent.ts | 28 + src/events/catalog/index.ts | 11 + src/events/guide-tool/GuideToolEvent.ts | 10 + src/events/guide-tool/index.ts | 1 + src/events/help/HelpNameChangeEvent.ts | 6 + src/events/help/index.ts | 1 + src/events/index.ts | 6 + .../inventory/InventoryFurniAddedEvent.ts | 14 + src/events/inventory/index.ts | 1 + src/events/room-widgets/index.ts | 1 + .../thumbnail/RoomWidgetThumbnailEvent.ts | 8 + src/events/room-widgets/thumbnail/index.ts | 1 + src/hooks/UseMountEffect.tsx | 7 + src/hooks/achievements/index.ts | 1 + src/hooks/achievements/useAchievements.ts | 185 + src/hooks/avatar-editor/index.ts | 2 + src/hooks/avatar-editor/useAvatarEditor.ts | 244 ++ src/hooks/avatar-editor/useFigureData.ts | 114 + src/hooks/camera/index.ts | 1 + src/hooks/camera/useCamera.ts | 42 + src/hooks/catalog/index.ts | 3 + src/hooks/catalog/useCatalog.ts | 913 +++++ .../catalog/useCatalogPlaceMultipleItems.ts | 7 + .../useCatalogSkipPurchaseConfirmation.ts | 7 + src/hooks/chat-history/index.ts | 1 + src/hooks/chat-history/useChatHistory.ts | 104 + src/hooks/events/index.ts | 4 + src/hooks/events/useEventDispatcher.tsx | 31 + src/hooks/events/useMessageEvent.tsx | 15 + src/hooks/events/useNitroEvent.tsx | 4 + src/hooks/events/useUiEvent.tsx | 5 + src/hooks/friends/index.ts | 2 + src/hooks/friends/useFriends.ts | 252 ++ src/hooks/friends/useMessenger.ts | 187 + src/hooks/game-center/index.ts | 1 + src/hooks/game-center/useGameCenter.ts | 83 + src/hooks/groups/index.ts | 1 + src/hooks/groups/useGroup.ts | 55 + src/hooks/help/index.ts | 1 + src/hooks/help/useHelp.ts | 149 + src/hooks/index.ts | 25 + src/hooks/inventory/index.ts | 6 + src/hooks/inventory/useInventoryBadges.ts | 152 + src/hooks/inventory/useInventoryBots.ts | 158 + src/hooks/inventory/useInventoryFurni.ts | 298 ++ src/hooks/inventory/useInventoryPets.ts | 107 + src/hooks/inventory/useInventoryTrade.ts | 288 ++ .../inventory/useInventoryUnseenTracker.ts | 132 + src/hooks/mod-tools/index.ts | 1 + src/hooks/mod-tools/useModTools.ts | 207 ++ src/hooks/navigator/index.ts | 1 + src/hooks/navigator/useNavigator.ts | 442 +++ src/hooks/notification/index.ts | 1 + src/hooks/notification/useNotification.ts | 433 +++ src/hooks/purse/index.ts | 1 + src/hooks/purse/usePurse.ts | 126 + src/hooks/rooms/engine/index.ts | 9 + src/hooks/rooms/engine/useFurniAddedEvent.ts | 19 + .../rooms/engine/useFurniRemovedEvent.ts | 19 + .../rooms/engine/useObjectDeselectedEvent.ts | 7 + .../engine/useObjectDoubleClickedEvent.ts | 7 + .../rooms/engine/useObjectRollOutEvent.ts | 7 + .../rooms/engine/useObjectRollOverEvent.ts | 7 + .../rooms/engine/useObjectSelectedEvent.ts | 7 + src/hooks/rooms/engine/useUserAddedEvent.ts | 19 + src/hooks/rooms/engine/useUserRemovedEvent.ts | 19 + src/hooks/rooms/index.ts | 4 + src/hooks/rooms/promotes/index.ts | 1 + src/hooks/rooms/promotes/useRoomPromote.ts | 23 + src/hooks/rooms/useRoom.ts | 283 ++ src/hooks/rooms/widgets/furniture/index.ts | 19 + .../useFurnitureBackgroundColorWidget.ts | 71 + .../useFurnitureBadgeDisplayWidget.ts | 75 + .../useFurnitureContextMenuWidget.ts | 179 + .../furniture/useFurnitureCraftingWidget.ts | 166 + .../furniture/useFurnitureDimmerWidget.ts | 110 + .../furniture/useFurnitureExchangeWidget.ts | 48 + .../useFurnitureExternalImageWidget.ts | 74 + .../useFurnitureFriendFurniWidget.ts | 75 + .../furniture/useFurnitureHighScoreWidget.ts | 55 + .../useFurnitureInternalLinkWidget.ts | 26 + .../furniture/useFurnitureMannequinWidget.ts | 80 + .../useFurniturePlaylistEditorWidget.ts | 108 + .../furniture/useFurniturePresentWidget.ts | 235 ++ .../furniture/useFurnitureRoomLinkWidget.ts | 49 + .../useFurnitureSpamWallPostItWidget.ts | 59 + .../useFurnitureStackHeightWidget.ts | 79 + .../furniture/useFurnitureStickieWidget.ts | 85 + .../furniture/useFurnitureTrophyWidget.ts | 62 + .../furniture/useFurnitureYoutubeWidget.ts | 127 + src/hooks/rooms/widgets/index.ts | 12 + .../rooms/widgets/useAvatarInfoWidget.ts | 355 ++ src/hooks/rooms/widgets/useChatInputWidget.ts | 282 ++ src/hooks/rooms/widgets/useChatWidget.ts | 191 + src/hooks/rooms/widgets/useDoorbellWidget.ts | 44 + .../rooms/widgets/useFilterWordsWidget.ts | 23 + .../rooms/widgets/useFriendRequestWidget.ts | 81 + .../rooms/widgets/useFurniChooserWidget.ts | 132 + .../rooms/widgets/usePetPackageWidget.ts | 75 + src/hooks/rooms/widgets/usePollWidget.ts | 52 + .../rooms/widgets/useUserChooserWidget.ts | 80 + src/hooks/rooms/widgets/useWordQuizWidget.ts | 149 + src/hooks/session/index.ts | 1 + src/hooks/session/useSessionInfo.ts | 63 + src/hooks/useLocalStorage.ts | 44 + src/hooks/useSharedVisibility.ts | 44 + src/hooks/wired/index.ts | 1 + src/hooks/wired/useWired.ts | 140 + src/index.scss | 26 + src/index.tsx | 5 + src/react-app-env.d.ts | 1 + src/workers/IntervalWebWorker.ts | 26 + src/workers/WorkerBuilder.ts | 10 + tsconfig.json | 29 + vite.config.mjs | 32 + yarn.lock | 3068 +++++++++++++++++ 1425 files changed, 67522 insertions(+) create mode 100644 .browserslistrc create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 README.md create mode 100644 index.html create mode 100644 package.json create mode 100644 public/android-chrome-192x192.png create mode 100644 public/android-chrome-512x512.png create mode 100644 public/apple-touch-icon.png create mode 100644 public/browserconfig.xml create mode 100644 public/favicon-16x16.png create mode 100644 public/favicon-32x32.png create mode 100644 public/favicon.ico create mode 100644 public/mstile-150x150.png create mode 100644 public/robots.txt create mode 100644 public/safari-pinned-tab.svg create mode 100644 public/site.webmanifest create mode 100644 src/App.scss create mode 100644 src/App.tsx create mode 100644 src/api/GetRendererVersion.ts create mode 100644 src/api/GetUIVersion.ts create mode 100644 src/api/achievements/AchievementCategory.ts create mode 100644 src/api/achievements/AchievementUtilities.ts create mode 100644 src/api/achievements/IAchievementCategory.ts create mode 100644 src/api/achievements/index.ts create mode 100644 src/api/avatar/AvatarEditorAction.ts create mode 100644 src/api/avatar/AvatarEditorGridColorItem.ts create mode 100644 src/api/avatar/AvatarEditorGridPartItem.ts create mode 100644 src/api/avatar/AvatarEditorThumbnailsHelper.ts create mode 100644 src/api/avatar/AvatarEditorUtilities.ts create mode 100644 src/api/avatar/BodyModel.ts create mode 100644 src/api/avatar/CategoryBaseModel.ts create mode 100644 src/api/avatar/CategoryData.ts create mode 100644 src/api/avatar/FigureData.ts create mode 100644 src/api/avatar/FigureGenerator.ts create mode 100644 src/api/avatar/HeadModel.ts create mode 100644 src/api/avatar/IAvatarEditorCategory.ts create mode 100644 src/api/avatar/IAvatarEditorCategoryModel.ts create mode 100644 src/api/avatar/IAvatarEditorCategoryPartItem.ts create mode 100644 src/api/avatar/LegModel.ts create mode 100644 src/api/avatar/TorsoModel.ts create mode 100644 src/api/avatar/index.ts create mode 100644 src/api/camera/CameraEditorTabs.ts create mode 100644 src/api/camera/CameraPicture.ts create mode 100644 src/api/camera/CameraPictureThumbnail.ts create mode 100644 src/api/camera/index.ts create mode 100644 src/api/campaign/CalendarItem.ts create mode 100644 src/api/campaign/CalendarItemState.ts create mode 100644 src/api/campaign/ICalendarItem.ts create mode 100644 src/api/campaign/index.ts create mode 100644 src/api/catalog/BuilderFurniPlaceableStatus.ts create mode 100644 src/api/catalog/CatalogNode.ts create mode 100644 src/api/catalog/CatalogPage.ts create mode 100644 src/api/catalog/CatalogPageName.ts create mode 100644 src/api/catalog/CatalogPetPalette.ts create mode 100644 src/api/catalog/CatalogPurchaseState.ts create mode 100644 src/api/catalog/CatalogType.ts create mode 100644 src/api/catalog/CatalogUtilities.ts create mode 100644 src/api/catalog/FurnitureOffer.ts create mode 100644 src/api/catalog/GetImageIconUrlForProduct.ts create mode 100644 src/api/catalog/GiftWrappingConfiguration.ts create mode 100644 src/api/catalog/ICatalogNode.ts create mode 100644 src/api/catalog/ICatalogOptions.ts create mode 100644 src/api/catalog/ICatalogPage.ts create mode 100644 src/api/catalog/IMarketplaceSearchOptions.ts create mode 100644 src/api/catalog/IPageLocalization.ts create mode 100644 src/api/catalog/IProduct.ts create mode 100644 src/api/catalog/IPurchasableOffer.ts create mode 100644 src/api/catalog/IPurchaseOptions.ts create mode 100644 src/api/catalog/MarketplaceOfferData.ts create mode 100644 src/api/catalog/MarketplaceOfferState.ts create mode 100644 src/api/catalog/MarketplaceSearchType.ts create mode 100644 src/api/catalog/Offer.ts create mode 100644 src/api/catalog/PageLocalization.ts create mode 100644 src/api/catalog/PlacedObjectPurchaseData.ts create mode 100644 src/api/catalog/Product.ts create mode 100644 src/api/catalog/ProductTypeEnum.ts create mode 100644 src/api/catalog/RequestedPage.ts create mode 100644 src/api/catalog/SearchResult.ts create mode 100644 src/api/catalog/index.ts create mode 100644 src/api/chat-history/ChatEntryType.ts create mode 100644 src/api/chat-history/ChatHistoryCurrentDate.ts create mode 100644 src/api/chat-history/IChatEntry.ts create mode 100644 src/api/chat-history/IRoomHistoryEntry.ts create mode 100644 src/api/chat-history/MessengerHistoryCurrentDate.ts create mode 100644 src/api/chat-history/index.ts create mode 100644 src/api/events/DispatchEvent.ts create mode 100644 src/api/events/DispatchMainEvent.ts create mode 100644 src/api/events/DispatchUiEvent.ts create mode 100644 src/api/events/UI_EVENT_DISPATCHER.ts create mode 100644 src/api/events/index.ts create mode 100644 src/api/friends/GetGroupChatData.ts create mode 100644 src/api/friends/IGroupChatData.ts create mode 100644 src/api/friends/MessengerFriend.ts create mode 100644 src/api/friends/MessengerGroupType.ts create mode 100644 src/api/friends/MessengerIconState.ts create mode 100644 src/api/friends/MessengerRequest.ts create mode 100644 src/api/friends/MessengerSettings.ts create mode 100644 src/api/friends/MessengerThread.ts create mode 100644 src/api/friends/MessengerThreadChat.ts create mode 100644 src/api/friends/MessengerThreadChatGroup.ts create mode 100644 src/api/friends/OpenMessengerChat.ts create mode 100644 src/api/friends/index.ts create mode 100644 src/api/groups/GetGroupInformation.ts create mode 100644 src/api/groups/GetGroupManager.ts create mode 100644 src/api/groups/GetGroupMembers.ts create mode 100644 src/api/groups/GroupBadgePart.ts create mode 100644 src/api/groups/GroupMembershipType.ts create mode 100644 src/api/groups/GroupType.ts create mode 100644 src/api/groups/IGroupCustomize.ts create mode 100644 src/api/groups/IGroupData.ts create mode 100644 src/api/groups/ToggleFavoriteGroup.ts create mode 100644 src/api/groups/TryJoinGroup.ts create mode 100644 src/api/groups/index.ts create mode 100644 src/api/guide-tool/GuideSessionState.ts create mode 100644 src/api/guide-tool/GuideToolMessage.ts create mode 100644 src/api/guide-tool/GuideToolMessageGroup.ts create mode 100644 src/api/guide-tool/index.ts create mode 100644 src/api/hc-center/ClubStatus.ts create mode 100644 src/api/hc-center/GetClubBadge.ts create mode 100644 src/api/hc-center/index.ts create mode 100644 src/api/help/CallForHelpResult.ts create mode 100644 src/api/help/GetCloseReasonKey.ts create mode 100644 src/api/help/IHelpReport.ts create mode 100644 src/api/help/IReportedUser.ts create mode 100644 src/api/help/ReportState.ts create mode 100644 src/api/help/ReportType.ts create mode 100644 src/api/help/index.ts create mode 100644 src/api/index.ts create mode 100644 src/api/inventory/FurniCategory.ts create mode 100644 src/api/inventory/FurnitureItem.ts create mode 100644 src/api/inventory/FurnitureUtilities.ts create mode 100644 src/api/inventory/GroupItem.ts create mode 100644 src/api/inventory/IBotItem.ts create mode 100644 src/api/inventory/IFurnitureItem.ts create mode 100644 src/api/inventory/IPetItem.ts create mode 100644 src/api/inventory/IUnseenItemTracker.ts create mode 100644 src/api/inventory/InventoryUtilities.ts create mode 100644 src/api/inventory/PetUtilities.ts create mode 100644 src/api/inventory/TradeState.ts create mode 100644 src/api/inventory/TradeUserData.ts create mode 100644 src/api/inventory/TradingNotificationType.ts create mode 100644 src/api/inventory/TradingUtilities.ts create mode 100644 src/api/inventory/UnseenItemCategory.ts create mode 100644 src/api/inventory/index.ts create mode 100644 src/api/mod-tools/GetIssueCategoryName.ts create mode 100644 src/api/mod-tools/ISelectedUser.ts create mode 100644 src/api/mod-tools/IUserInfo.ts create mode 100644 src/api/mod-tools/ModActionDefinition.ts create mode 100644 src/api/mod-tools/index.ts create mode 100644 src/api/navigator/DoorStateType.ts create mode 100644 src/api/navigator/INavigatorData.ts create mode 100644 src/api/navigator/INavigatorSearchFilter.ts create mode 100644 src/api/navigator/IRoomChatSettings.ts create mode 100644 src/api/navigator/IRoomData.ts create mode 100644 src/api/navigator/IRoomModel.ts create mode 100644 src/api/navigator/IRoomModerationSettings.ts create mode 100644 src/api/navigator/NavigatorSearchResultViewDisplayMode.ts create mode 100644 src/api/navigator/RoomInfoData.ts create mode 100644 src/api/navigator/RoomSettingsUtils.ts create mode 100644 src/api/navigator/SearchFilterOptions.ts create mode 100644 src/api/navigator/TryVisitRoom.ts create mode 100644 src/api/navigator/index.ts create mode 100644 src/api/nitro/GetConfigurationValue.ts create mode 100644 src/api/nitro/OpenUrl.ts create mode 100644 src/api/nitro/SendMessageComposer.ts create mode 100644 src/api/nitro/index.ts create mode 100644 src/api/nitro/room/DispatchMouseEvent.ts create mode 100644 src/api/nitro/room/DispatchTouchEvent.ts create mode 100644 src/api/nitro/room/GetOwnRoomObject.ts create mode 100644 src/api/nitro/room/GetRoomObjectBounds.ts create mode 100644 src/api/nitro/room/GetRoomObjectScreenLocation.ts create mode 100644 src/api/nitro/room/InitializeRoomInstanceRenderingCanvas.ts create mode 100644 src/api/nitro/room/IsFurnitureSelectionDisabled.ts create mode 100644 src/api/nitro/room/ProcessRoomObjectOperation.ts create mode 100644 src/api/nitro/room/SetActiveRoomId.ts create mode 100644 src/api/nitro/room/index.ts create mode 100644 src/api/nitro/session/CanManipulateFurniture.ts create mode 100644 src/api/nitro/session/CreateRoomSession.ts create mode 100644 src/api/nitro/session/GetCanStandUp.ts create mode 100644 src/api/nitro/session/GetCanUseExpression.ts create mode 100644 src/api/nitro/session/GetClubMemberLevel.ts create mode 100644 src/api/nitro/session/GetFurnitureData.ts create mode 100644 src/api/nitro/session/GetFurnitureDataForProductOffer.ts create mode 100644 src/api/nitro/session/GetFurnitureDataForRoomObject.ts create mode 100644 src/api/nitro/session/GetOwnPosture.ts create mode 100644 src/api/nitro/session/GetProductDataForLocalization.ts create mode 100644 src/api/nitro/session/GetRoomSession.ts create mode 100644 src/api/nitro/session/GoToDesktop.ts create mode 100644 src/api/nitro/session/HasHabboClub.ts create mode 100644 src/api/nitro/session/HasHabboVip.ts create mode 100644 src/api/nitro/session/IsOwnerOfFloorFurniture.ts create mode 100644 src/api/nitro/session/IsOwnerOfFurniture.ts create mode 100644 src/api/nitro/session/IsRidingHorse.ts create mode 100644 src/api/nitro/session/StartRoomSession.ts create mode 100644 src/api/nitro/session/VisitDesktop.ts create mode 100644 src/api/nitro/session/index.ts create mode 100644 src/api/notification/NotificationAlertItem.ts create mode 100644 src/api/notification/NotificationAlertType.ts create mode 100644 src/api/notification/NotificationBubbleItem.ts create mode 100644 src/api/notification/NotificationBubbleType.ts create mode 100644 src/api/notification/NotificationConfirmItem.ts create mode 100644 src/api/notification/NotificationConfirmType.ts create mode 100644 src/api/notification/index.ts create mode 100644 src/api/purse/IPurse.ts create mode 100644 src/api/purse/Purse.ts create mode 100644 src/api/purse/index.ts create mode 100644 src/api/room/events/RoomWidgetPollUpdateEvent.ts create mode 100644 src/api/room/events/RoomWidgetUpdateBackgroundColorPreviewEvent.ts create mode 100644 src/api/room/events/RoomWidgetUpdateChatInputContentEvent.ts create mode 100644 src/api/room/events/RoomWidgetUpdateEvent.ts create mode 100644 src/api/room/events/RoomWidgetUpdateRentableBotChatEvent.ts create mode 100644 src/api/room/events/RoomWidgetUpdateRoomObjectEvent.ts create mode 100644 src/api/room/events/index.ts create mode 100644 src/api/room/index.ts create mode 100644 src/api/room/widgets/AvatarInfoFurni.ts create mode 100644 src/api/room/widgets/AvatarInfoName.ts create mode 100644 src/api/room/widgets/AvatarInfoPet.ts create mode 100644 src/api/room/widgets/AvatarInfoRentableBot.ts create mode 100644 src/api/room/widgets/AvatarInfoUser.ts create mode 100644 src/api/room/widgets/AvatarInfoUtilities.ts create mode 100644 src/api/room/widgets/BotSkillsEnum.ts create mode 100644 src/api/room/widgets/ChatBubbleMessage.ts create mode 100644 src/api/room/widgets/ChatBubbleUtilities.ts create mode 100644 src/api/room/widgets/ChatMessageTypeEnum.ts create mode 100644 src/api/room/widgets/DimmerFurnitureWidgetPresetItem.ts create mode 100644 src/api/room/widgets/DoChatsOverlap.ts create mode 100644 src/api/room/widgets/FurnitureDimmerUtilities.ts create mode 100644 src/api/room/widgets/GetDiskColor.ts create mode 100644 src/api/room/widgets/IAvatarInfo.ts create mode 100644 src/api/room/widgets/ICraftingIngredient.ts create mode 100644 src/api/room/widgets/ICraftingRecipe.ts create mode 100644 src/api/room/widgets/IPhotoData.ts create mode 100644 src/api/room/widgets/MannequinUtilities.ts create mode 100644 src/api/room/widgets/PetSupplementEnum.ts create mode 100644 src/api/room/widgets/PostureTypeEnum.ts create mode 100644 src/api/room/widgets/RoomDimmerPreset.ts create mode 100644 src/api/room/widgets/RoomObjectItem.ts create mode 100644 src/api/room/widgets/UseProductItem.ts create mode 100644 src/api/room/widgets/VoteValue.ts create mode 100644 src/api/room/widgets/YoutubeVideoPlaybackStateEnum.ts create mode 100644 src/api/room/widgets/index.ts create mode 100644 src/api/user/GetUserProfile.ts create mode 100644 src/api/user/index.ts create mode 100644 src/api/utils/CloneObject.ts create mode 100644 src/api/utils/ColorUtils.ts create mode 100644 src/api/utils/ConvertSeconds.ts create mode 100644 src/api/utils/FixedSizeStack.ts create mode 100644 src/api/utils/FriendlyTime.ts create mode 100644 src/api/utils/GetLocalStorage.ts create mode 100644 src/api/utils/LocalStorageKeys.ts create mode 100644 src/api/utils/LocalizeBadgeDescription.ts create mode 100644 src/api/utils/LocalizeBageName.ts create mode 100644 src/api/utils/LocalizeFormattedNumber.ts create mode 100644 src/api/utils/LocalizeShortNumber.ts create mode 100644 src/api/utils/LocalizeText.ts create mode 100644 src/api/utils/PlaySound.ts create mode 100644 src/api/utils/ProductImageUtility.ts create mode 100644 src/api/utils/Randomizer.ts create mode 100644 src/api/utils/RoomChatFormatter.ts create mode 100644 src/api/utils/SetLocalStorage.ts create mode 100644 src/api/utils/SoundNames.ts create mode 100644 src/api/utils/WindowSaveOptions.ts create mode 100644 src/api/utils/index.ts create mode 100644 src/api/wired/GetWiredTimeLocale.ts create mode 100644 src/api/wired/WiredActionLayoutCode.ts create mode 100644 src/api/wired/WiredConditionLayoutCode.ts create mode 100644 src/api/wired/WiredDateToString.ts create mode 100644 src/api/wired/WiredFurniType.ts create mode 100644 src/api/wired/WiredSelectionVisualizer.ts create mode 100644 src/api/wired/WiredStringDelimeter.ts create mode 100644 src/api/wired/WiredTriggerLayoutCode.ts create mode 100644 src/api/wired/index.ts create mode 100644 src/assets/images/achievements/back-arrow.png create mode 100644 src/assets/images/avatareditor/arrow-left-icon.png create mode 100644 src/assets/images/avatareditor/arrow-right-icon.png create mode 100644 src/assets/images/avatareditor/avatar-editor-spritesheet.png create mode 100644 src/assets/images/avatareditor/ca-icon.png create mode 100644 src/assets/images/avatareditor/ca-selected-icon.png create mode 100644 src/assets/images/avatareditor/cc-icon.png create mode 100644 src/assets/images/avatareditor/cc-selected-icon.png create mode 100644 src/assets/images/avatareditor/ch-icon.png create mode 100644 src/assets/images/avatareditor/ch-selected-icon.png create mode 100644 src/assets/images/avatareditor/clear-icon.png create mode 100644 src/assets/images/avatareditor/cp-icon.png create mode 100644 src/assets/images/avatareditor/cp-selected-icon.png create mode 100644 src/assets/images/avatareditor/ea-icon.png create mode 100644 src/assets/images/avatareditor/ea-selected-icon.png create mode 100644 src/assets/images/avatareditor/fa-icon.png create mode 100644 src/assets/images/avatareditor/fa-selected-icon.png create mode 100644 src/assets/images/avatareditor/female-icon.png create mode 100644 src/assets/images/avatareditor/female-selected-icon.png create mode 100644 src/assets/images/avatareditor/ha-icon.png create mode 100644 src/assets/images/avatareditor/ha-selected-icon.png create mode 100644 src/assets/images/avatareditor/he-icon.png create mode 100644 src/assets/images/avatareditor/he-selected-icon.png create mode 100644 src/assets/images/avatareditor/hr-icon.png create mode 100644 src/assets/images/avatareditor/hr-selected-icon.png create mode 100644 src/assets/images/avatareditor/lg-icon.png create mode 100644 src/assets/images/avatareditor/lg-selected-icon.png create mode 100644 src/assets/images/avatareditor/loading-icon.png create mode 100644 src/assets/images/avatareditor/male-icon.png create mode 100644 src/assets/images/avatareditor/male-selected-icon.png create mode 100644 src/assets/images/avatareditor/sellable-icon.png create mode 100644 src/assets/images/avatareditor/sh-icon.png create mode 100644 src/assets/images/avatareditor/sh-selected-icon.png create mode 100644 src/assets/images/avatareditor/spotlight-icon.png create mode 100644 src/assets/images/avatareditor/wa-icon.png create mode 100644 src/assets/images/avatareditor/wa-selected-icon.png create mode 100644 src/assets/images/campaign/available.png create mode 100644 src/assets/images/campaign/campaign_day_generic_bg.png create mode 100644 src/assets/images/campaign/campaign_opened.png create mode 100644 src/assets/images/campaign/campaign_spritesheet.png create mode 100644 src/assets/images/campaign/locked.png create mode 100644 src/assets/images/campaign/locked_bg.png create mode 100644 src/assets/images/campaign/next.png create mode 100644 src/assets/images/campaign/prev.png create mode 100644 src/assets/images/campaign/unavailable.png create mode 100644 src/assets/images/campaign/unlocked_bg.png create mode 100644 src/assets/images/catalog/diamond_info_illustration.gif create mode 100644 src/assets/images/catalog/hc_banner_big.png create mode 100644 src/assets/images/catalog/hc_big.png create mode 100644 src/assets/images/catalog/hc_small.png create mode 100644 src/assets/images/catalog/paint-icon.png create mode 100644 src/assets/images/catalog/target-price.png create mode 100644 src/assets/images/catalog/vip.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_0.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_0_1_33_34_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_0_transparent.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_1.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_10.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_10_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_11.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_11_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_12.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_12_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_13.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_13_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_14.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_14_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_15.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_15_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_16.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_16_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_17.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_17_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_18.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_18_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_19.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_19_20_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_2.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_20.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_21.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_21_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_22.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_22_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_23.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_23_37_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_24.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_24_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_25.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_25_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_26.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_26_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_27.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_27_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_28.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_28_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_29.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_29_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_2_31_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_3.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_30.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_30_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_32.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_32_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_33_34.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_33_extra.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_34_extra.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_35.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_35_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_36.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_36_extra.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_36_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_37.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_38.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_38_extra.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_38_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_3_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_4.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_4_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_5.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_5_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_6.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_6_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_7.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_7_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_8.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_8_pointer.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_9.png create mode 100644 src/assets/images/chat/chatbubbles/bubble_9_pointer.png create mode 100644 src/assets/images/chat/styles-icon.png create mode 100644 src/assets/images/floorplaneditor/door-direction-0.png create mode 100644 src/assets/images/floorplaneditor/door-direction-1.png create mode 100644 src/assets/images/floorplaneditor/door-direction-2.png create mode 100644 src/assets/images/floorplaneditor/door-direction-3.png create mode 100644 src/assets/images/floorplaneditor/door-direction-4.png create mode 100644 src/assets/images/floorplaneditor/door-direction-5.png create mode 100644 src/assets/images/floorplaneditor/door-direction-6.png create mode 100644 src/assets/images/floorplaneditor/door-direction-7.png create mode 100644 src/assets/images/floorplaneditor/icon-door.png create mode 100644 src/assets/images/floorplaneditor/icon-tile-down.png create mode 100644 src/assets/images/floorplaneditor/icon-tile-set.png create mode 100644 src/assets/images/floorplaneditor/icon-tile-unset.png create mode 100644 src/assets/images/floorplaneditor/icon-tile-up.png create mode 100644 src/assets/images/floorplaneditor/preview_tile.png create mode 100644 src/assets/images/floorplaneditor/selected_height_icon.png create mode 100644 src/assets/images/friends/friends-spritesheet.png create mode 100644 src/assets/images/friends/icon-accept.png create mode 100644 src/assets/images/friends/icon-add.png create mode 100644 src/assets/images/friends/icon-bobba.png create mode 100644 src/assets/images/friends/icon-chat.png create mode 100644 src/assets/images/friends/icon-deny.png create mode 100644 src/assets/images/friends/icon-follow.png create mode 100644 src/assets/images/friends/icon-friendbar-chat.png create mode 100644 src/assets/images/friends/icon-friendbar-visit.png create mode 100644 src/assets/images/friends/icon-heart.png create mode 100644 src/assets/images/friends/icon-new-message.png create mode 100644 src/assets/images/friends/icon-none.png create mode 100644 src/assets/images/friends/icon-profile-sm-hover.png create mode 100644 src/assets/images/friends/icon-profile-sm.png create mode 100644 src/assets/images/friends/icon-profile.png create mode 100644 src/assets/images/friends/icon-smile.png create mode 100644 src/assets/images/friends/icon-warning.png create mode 100644 src/assets/images/friends/messenger_notification_icon.png create mode 100644 src/assets/images/gamecenter/selectedIcon.png create mode 100644 src/assets/images/gift/gift_tag.png create mode 100644 src/assets/images/gift/incognito.png create mode 100644 src/assets/images/groups/creator_images.png create mode 100644 src/assets/images/groups/creator_tabs.png create mode 100644 src/assets/images/groups/icons/group_decorate_icon.png create mode 100644 src/assets/images/groups/icons/group_favorite.png create mode 100644 src/assets/images/groups/icons/group_icon_admin.png create mode 100644 src/assets/images/groups/icons/group_icon_big_admin.png create mode 100644 src/assets/images/groups/icons/group_icon_big_member.png create mode 100644 src/assets/images/groups/icons/group_icon_big_owner.png create mode 100644 src/assets/images/groups/icons/group_icon_not_admin.png create mode 100644 src/assets/images/groups/icons/group_icon_small_owner.png create mode 100644 src/assets/images/groups/icons/group_notfavorite.png create mode 100644 src/assets/images/groups/icons/grouptype_icon_0.png create mode 100644 src/assets/images/groups/icons/grouptype_icon_1.png create mode 100644 src/assets/images/groups/icons/grouptype_icon_2.png create mode 100644 src/assets/images/groups/no-group-1.png create mode 100644 src/assets/images/groups/no-group-2.png create mode 100644 src/assets/images/groups/no-group-3.png create mode 100644 src/assets/images/groups/no-group-spritesheet.png create mode 100644 src/assets/images/guide-tool/guide_tool_duty_switch.png create mode 100644 src/assets/images/guide-tool/guide_tool_info_icon.png create mode 100644 src/assets/images/hc-center/benefits.png create mode 100644 src/assets/images/hc-center/clock.png create mode 100644 src/assets/images/hc-center/hc_logo.gif create mode 100644 src/assets/images/hc-center/payday.png create mode 100644 src/assets/images/help/help_index.png create mode 100644 src/assets/images/icons/arrows.png create mode 100644 src/assets/images/icons/camera-colormatrix.png create mode 100644 src/assets/images/icons/camera-composite.png create mode 100644 src/assets/images/icons/camera-small.png create mode 100644 src/assets/images/icons/chat-history.png create mode 100644 src/assets/images/icons/close.png create mode 100644 src/assets/images/icons/cog.png create mode 100644 src/assets/images/icons/help.png create mode 100644 src/assets/images/icons/house-small.png create mode 100644 src/assets/images/icons/icon_cog.png create mode 100644 src/assets/images/icons/like-room.png create mode 100644 src/assets/images/icons/loading-icon.png create mode 100644 src/assets/images/icons/room-link.png create mode 100644 src/assets/images/icons/sign-exclamation.png create mode 100644 src/assets/images/icons/sign-heart.png create mode 100644 src/assets/images/icons/sign-red.png create mode 100644 src/assets/images/icons/sign-skull.png create mode 100644 src/assets/images/icons/sign-smile.png create mode 100644 src/assets/images/icons/sign-soccer.png create mode 100644 src/assets/images/icons/sign-yellow.png create mode 100644 src/assets/images/icons/small-room.png create mode 100644 src/assets/images/icons/tickets.png create mode 100644 src/assets/images/icons/user.png create mode 100644 src/assets/images/icons/zoom-less.png create mode 100644 src/assets/images/icons/zoom-more.png create mode 100644 src/assets/images/infostand/bot_background.png create mode 100644 src/assets/images/infostand/countown-timer.png create mode 100644 src/assets/images/infostand/disk-creator.png create mode 100644 src/assets/images/infostand/disk-icon.png create mode 100644 src/assets/images/infostand/pencil-icon.png create mode 100644 src/assets/images/infostand/rarity-level.png create mode 100644 src/assets/images/inventory/empty.png create mode 100644 src/assets/images/inventory/rarity-level.png create mode 100644 src/assets/images/inventory/trading/locked-icon.png create mode 100644 src/assets/images/inventory/trading/unlocked-icon.png create mode 100644 src/assets/images/loading/connecting-duck-spritesheet.png create mode 100644 src/assets/images/loading/connecting_duck_01.png create mode 100644 src/assets/images/loading/connecting_duck_02.png create mode 100644 src/assets/images/loading/connecting_duck_03.png create mode 100644 src/assets/images/loading/connecting_duck_04.png create mode 100644 src/assets/images/loading/connecting_duck_05.png create mode 100644 src/assets/images/loading/connecting_duck_06.png create mode 100644 src/assets/images/loading/connecting_duck_07.png create mode 100644 src/assets/images/loading/progress_habbos.gif create mode 100644 src/assets/images/modtool/chatlog.gif create mode 100644 src/assets/images/modtool/key.gif create mode 100644 src/assets/images/modtool/m_icon.png create mode 100644 src/assets/images/modtool/reports.png create mode 100644 src/assets/images/modtool/room.gif create mode 100644 src/assets/images/modtool/room.png create mode 100644 src/assets/images/modtool/user.gif create mode 100644 src/assets/images/modtool/wrench.gif create mode 100644 src/assets/images/mysterybox/chain_mysterybox_box_overlay.png create mode 100644 src/assets/images/mysterybox/key_overlay.png create mode 100644 src/assets/images/mysterybox/mystery_box.png create mode 100644 src/assets/images/mysterybox/mystery_box_key.png create mode 100644 src/assets/images/mysterytrophy/frank_mystery_trophy.png create mode 100644 src/assets/images/navigator/icons/info.png create mode 100644 src/assets/images/navigator/icons/room_group.png create mode 100644 src/assets/images/navigator/icons/room_invisible.png create mode 100644 src/assets/images/navigator/icons/room_locked.png create mode 100644 src/assets/images/navigator/icons/room_password.png create mode 100644 src/assets/images/navigator/models/model_0.png create mode 100644 src/assets/images/navigator/models/model_1.png create mode 100644 src/assets/images/navigator/models/model_2.png create mode 100644 src/assets/images/navigator/models/model_3.png create mode 100644 src/assets/images/navigator/models/model_4.png create mode 100644 src/assets/images/navigator/models/model_5.png create mode 100644 src/assets/images/navigator/models/model_6.png create mode 100644 src/assets/images/navigator/models/model_7.png create mode 100644 src/assets/images/navigator/models/model_8.png create mode 100644 src/assets/images/navigator/models/model_9.png create mode 100644 src/assets/images/navigator/models/model_a.png create mode 100644 src/assets/images/navigator/models/model_b.png create mode 100644 src/assets/images/navigator/models/model_c.png create mode 100644 src/assets/images/navigator/models/model_d.png create mode 100644 src/assets/images/navigator/models/model_e.png create mode 100644 src/assets/images/navigator/models/model_f.png create mode 100644 src/assets/images/navigator/models/model_g.png create mode 100644 src/assets/images/navigator/models/model_h.png create mode 100644 src/assets/images/navigator/models/model_i.png create mode 100644 src/assets/images/navigator/models/model_j.png create mode 100644 src/assets/images/navigator/models/model_k.png create mode 100644 src/assets/images/navigator/models/model_l.png create mode 100644 src/assets/images/navigator/models/model_m.png create mode 100644 src/assets/images/navigator/models/model_n.png create mode 100644 src/assets/images/navigator/models/model_o.png create mode 100644 src/assets/images/navigator/models/model_p.png create mode 100644 src/assets/images/navigator/models/model_q.png create mode 100644 src/assets/images/navigator/models/model_r.png create mode 100644 src/assets/images/navigator/models/model_snowwar1.png create mode 100644 src/assets/images/navigator/models/model_snowwar2.png create mode 100644 src/assets/images/navigator/models/model_t.png create mode 100644 src/assets/images/navigator/models/model_u.png create mode 100644 src/assets/images/navigator/models/model_v.png create mode 100644 src/assets/images/navigator/models/model_w.png create mode 100644 src/assets/images/navigator/models/model_x.png create mode 100644 src/assets/images/navigator/models/model_y.png create mode 100644 src/assets/images/navigator/models/model_z.png create mode 100644 src/assets/images/navigator/thumbnail_placeholder.png create mode 100644 src/assets/images/nitro/nitro-dark.svg create mode 100644 src/assets/images/nitro/nitro-light.svg create mode 100644 src/assets/images/nitro/nitro-n-dark.svg create mode 100644 src/assets/images/nitro/nitro-n-light.svg create mode 100644 src/assets/images/notifications/frank.gif create mode 100644 src/assets/images/pets/pet-package/gnome.png create mode 100644 src/assets/images/pets/pet-package/leprechaun_box.png create mode 100644 src/assets/images/pets/pet-package/petbox_epic.png create mode 100644 src/assets/images/pets/pet-package/pterosaur_egg.png create mode 100644 src/assets/images/pets/pet-package/val11_present.png create mode 100644 src/assets/images/pets/pet-package/velociraptor_egg.png create mode 100644 src/assets/images/prize/prize_background.png create mode 100644 src/assets/images/profile/icons/offline.png create mode 100644 src/assets/images/profile/icons/online.gif create mode 100644 src/assets/images/profile/icons/tick.png create mode 100644 src/assets/images/room-spectator/room_spectator_bottom_left.png create mode 100644 src/assets/images/room-spectator/room_spectator_bottom_right.png create mode 100644 src/assets/images/room-spectator/room_spectator_middle_bottom.png create mode 100644 src/assets/images/room-spectator/room_spectator_middle_left.png create mode 100644 src/assets/images/room-spectator/room_spectator_middle_right.png create mode 100644 src/assets/images/room-spectator/room_spectator_middle_top.png create mode 100644 src/assets/images/room-spectator/room_spectator_top_left.png create mode 100644 src/assets/images/room-spectator/room_spectator_top_right.png create mode 100644 src/assets/images/room-widgets/avatar-info/preview-background.png create mode 100644 src/assets/images/room-widgets/camera-widget/btn.png create mode 100644 src/assets/images/room-widgets/camera-widget/btn_down.png create mode 100644 src/assets/images/room-widgets/camera-widget/btn_hi.png create mode 100644 src/assets/images/room-widgets/camera-widget/cam_bg.png create mode 100644 src/assets/images/room-widgets/camera-widget/camera-spritesheet.png create mode 100644 src/assets/images/room-widgets/camera-widget/viewfinder.png create mode 100644 src/assets/images/room-widgets/dimmer-widget/dimmer_banner.png create mode 100644 src/assets/images/room-widgets/engraving-lock-widget/engraving-lock-spritesheet.png create mode 100644 src/assets/images/room-widgets/exchange-credit/exchange-credit-image.png create mode 100644 src/assets/images/room-widgets/furni-context-menu/monsterplant-preview.png create mode 100644 src/assets/images/room-widgets/mannequin-widget/mannequin-spritesheet.png create mode 100644 src/assets/images/room-widgets/playlist-editor/disk_2.png create mode 100644 src/assets/images/room-widgets/playlist-editor/disk_image.png create mode 100644 src/assets/images/room-widgets/playlist-editor/move.png create mode 100644 src/assets/images/room-widgets/playlist-editor/pause-btn.png create mode 100644 src/assets/images/room-widgets/playlist-editor/pause.png create mode 100644 src/assets/images/room-widgets/playlist-editor/playing.png create mode 100644 src/assets/images/room-widgets/playlist-editor/preview.png create mode 100644 src/assets/images/room-widgets/stickie-widget/stickie-blue.png create mode 100644 src/assets/images/room-widgets/stickie-widget/stickie-christmas.png create mode 100644 src/assets/images/room-widgets/stickie-widget/stickie-close.png create mode 100644 src/assets/images/room-widgets/stickie-widget/stickie-dreams.png create mode 100644 src/assets/images/room-widgets/stickie-widget/stickie-green.png create mode 100644 src/assets/images/room-widgets/stickie-widget/stickie-heart.png create mode 100644 src/assets/images/room-widgets/stickie-widget/stickie-juninas.png create mode 100644 src/assets/images/room-widgets/stickie-widget/stickie-pink.png create mode 100644 src/assets/images/room-widgets/stickie-widget/stickie-shakesp.png create mode 100644 src/assets/images/room-widgets/stickie-widget/stickie-spritesheet.png create mode 100644 src/assets/images/room-widgets/stickie-widget/stickie-trash.png create mode 100644 src/assets/images/room-widgets/stickie-widget/stickie-yellow.png create mode 100644 src/assets/images/room-widgets/thumbnail-widget/thumbnail-camera-spritesheet.png create mode 100644 src/assets/images/room-widgets/trophy-widget/trophy-spritesheet.png create mode 100644 src/assets/images/room-widgets/wordquiz-widget/thumbs-down-small.png create mode 100644 src/assets/images/room-widgets/wordquiz-widget/thumbs-down.png create mode 100644 src/assets/images/room-widgets/wordquiz-widget/thumbs-up-small.png create mode 100644 src/assets/images/room-widgets/wordquiz-widget/thumbs-up.png create mode 100644 src/assets/images/room-widgets/youtube-widget/next.png create mode 100644 src/assets/images/room-widgets/youtube-widget/prev.png create mode 100644 src/assets/images/stackhelper/slider-background.png create mode 100644 src/assets/images/stackhelper/slider-pointer.png create mode 100644 src/assets/images/toolbar/arrow.png create mode 100644 src/assets/images/toolbar/friend-search.png create mode 100644 src/assets/images/toolbar/icons/buildersclub.png create mode 100644 src/assets/images/toolbar/icons/camera.png create mode 100644 src/assets/images/toolbar/icons/catalog.png create mode 100644 src/assets/images/toolbar/icons/friend_all.png create mode 100644 src/assets/images/toolbar/icons/friend_head.png create mode 100644 src/assets/images/toolbar/icons/friend_search.png create mode 100644 src/assets/images/toolbar/icons/game.png create mode 100644 src/assets/images/toolbar/icons/habbo.png create mode 100644 src/assets/images/toolbar/icons/house.png create mode 100644 src/assets/images/toolbar/icons/inventory.png create mode 100644 src/assets/images/toolbar/icons/joinroom.png create mode 100644 src/assets/images/toolbar/icons/me-menu/achievements.png create mode 100644 src/assets/images/toolbar/icons/me-menu/clothing.png create mode 100644 src/assets/images/toolbar/icons/me-menu/cog.png create mode 100644 src/assets/images/toolbar/icons/me-menu/forums.png create mode 100644 src/assets/images/toolbar/icons/me-menu/helper-tool.png create mode 100644 src/assets/images/toolbar/icons/me-menu/my-rooms.png create mode 100644 src/assets/images/toolbar/icons/me-menu/profile.png create mode 100644 src/assets/images/toolbar/icons/me-menu/rooms.png create mode 100644 src/assets/images/toolbar/icons/me-menu/talents.png create mode 100644 src/assets/images/toolbar/icons/message.png create mode 100644 src/assets/images/toolbar/icons/message_unsee.gif create mode 100644 src/assets/images/toolbar/icons/modtools.png create mode 100644 src/assets/images/toolbar/icons/rooms.png create mode 100644 src/assets/images/toolbar/icons/sendmessage.png create mode 100644 src/assets/images/unique/catalog-info-amount-bg.png create mode 100644 src/assets/images/unique/catalog-info-sold-out.png create mode 100644 src/assets/images/unique/grid-bg-glass.png create mode 100644 src/assets/images/unique/grid-bg-sold-out.png create mode 100644 src/assets/images/unique/grid-bg.png create mode 100644 src/assets/images/unique/grid-count-bg.png create mode 100644 src/assets/images/unique/inventory-info-amount-bg.png create mode 100644 src/assets/images/unique/numbers.png create mode 100644 src/assets/images/wired/card-action-corners.png create mode 100644 src/assets/images/wired/icon_action.png create mode 100644 src/assets/images/wired/icon_condition.png create mode 100644 src/assets/images/wired/icon_trigger.png create mode 100644 src/assets/images/wired/icon_wired_around.png create mode 100644 src/assets/images/wired/icon_wired_left_right.png create mode 100644 src/assets/images/wired/icon_wired_north_east.png create mode 100644 src/assets/images/wired/icon_wired_north_west.png create mode 100644 src/assets/images/wired/icon_wired_rotate_clockwise.png create mode 100644 src/assets/images/wired/icon_wired_rotate_counter_clockwise.png create mode 100644 src/assets/images/wired/icon_wired_south_east.png create mode 100644 src/assets/images/wired/icon_wired_south_west.png create mode 100644 src/assets/images/wired/icon_wired_up_down.png create mode 100644 src/assets/styles/bootstrap/_accordion.scss create mode 100644 src/assets/styles/bootstrap/_alert.scss create mode 100644 src/assets/styles/bootstrap/_badge.scss create mode 100644 src/assets/styles/bootstrap/_breadcrumb.scss create mode 100644 src/assets/styles/bootstrap/_button-group.scss create mode 100644 src/assets/styles/bootstrap/_buttons.scss create mode 100644 src/assets/styles/bootstrap/_card.scss create mode 100644 src/assets/styles/bootstrap/_carousel.scss create mode 100644 src/assets/styles/bootstrap/_close.scss create mode 100644 src/assets/styles/bootstrap/_containers.scss create mode 100644 src/assets/styles/bootstrap/_dropdown.scss create mode 100644 src/assets/styles/bootstrap/_forms.scss create mode 100644 src/assets/styles/bootstrap/_functions.scss create mode 100644 src/assets/styles/bootstrap/_grid.scss create mode 100644 src/assets/styles/bootstrap/_helpers.scss create mode 100644 src/assets/styles/bootstrap/_images.scss create mode 100644 src/assets/styles/bootstrap/_list-group.scss create mode 100644 src/assets/styles/bootstrap/_mixins.scss create mode 100644 src/assets/styles/bootstrap/_modal.scss create mode 100644 src/assets/styles/bootstrap/_nav.scss create mode 100644 src/assets/styles/bootstrap/_navbar.scss create mode 100644 src/assets/styles/bootstrap/_offcanvas.scss create mode 100644 src/assets/styles/bootstrap/_pagination.scss create mode 100644 src/assets/styles/bootstrap/_placeholders.scss create mode 100644 src/assets/styles/bootstrap/_popover.scss create mode 100644 src/assets/styles/bootstrap/_progress.scss create mode 100644 src/assets/styles/bootstrap/_reboot.scss create mode 100644 src/assets/styles/bootstrap/_root.scss create mode 100644 src/assets/styles/bootstrap/_spinners.scss create mode 100644 src/assets/styles/bootstrap/_tables.scss create mode 100644 src/assets/styles/bootstrap/_toasts.scss create mode 100644 src/assets/styles/bootstrap/_tooltip.scss create mode 100644 src/assets/styles/bootstrap/_transitions.scss create mode 100644 src/assets/styles/bootstrap/_type.scss create mode 100644 src/assets/styles/bootstrap/_utilities.scss create mode 100644 src/assets/styles/bootstrap/_variables.scss create mode 100644 src/assets/styles/bootstrap/bootstrap-grid.scss create mode 100644 src/assets/styles/bootstrap/bootstrap-reboot.scss create mode 100644 src/assets/styles/bootstrap/bootstrap-utilities.scss create mode 100644 src/assets/styles/bootstrap/bootstrap.scss create mode 100644 src/assets/styles/bootstrap/forms/_floating-labels.scss create mode 100644 src/assets/styles/bootstrap/forms/_form-check.scss create mode 100644 src/assets/styles/bootstrap/forms/_form-control.scss create mode 100644 src/assets/styles/bootstrap/forms/_form-range.scss create mode 100644 src/assets/styles/bootstrap/forms/_form-select.scss create mode 100644 src/assets/styles/bootstrap/forms/_form-text.scss create mode 100644 src/assets/styles/bootstrap/forms/_input-group.scss create mode 100644 src/assets/styles/bootstrap/forms/_labels.scss create mode 100644 src/assets/styles/bootstrap/forms/_validation.scss create mode 100644 src/assets/styles/bootstrap/helpers/_clearfix.scss create mode 100644 src/assets/styles/bootstrap/helpers/_colored-links.scss create mode 100644 src/assets/styles/bootstrap/helpers/_position.scss create mode 100644 src/assets/styles/bootstrap/helpers/_ratio.scss create mode 100644 src/assets/styles/bootstrap/helpers/_stacks.scss create mode 100644 src/assets/styles/bootstrap/helpers/_stretched-link.scss create mode 100644 src/assets/styles/bootstrap/helpers/_text-truncation.scss create mode 100644 src/assets/styles/bootstrap/helpers/_visually-hidden.scss create mode 100644 src/assets/styles/bootstrap/helpers/_vr.scss create mode 100644 src/assets/styles/bootstrap/mixins/_alert.scss create mode 100644 src/assets/styles/bootstrap/mixins/_backdrop.scss create mode 100644 src/assets/styles/bootstrap/mixins/_border-radius.scss create mode 100644 src/assets/styles/bootstrap/mixins/_box-shadow.scss create mode 100644 src/assets/styles/bootstrap/mixins/_breakpoints.scss create mode 100644 src/assets/styles/bootstrap/mixins/_buttons.scss create mode 100644 src/assets/styles/bootstrap/mixins/_caret.scss create mode 100644 src/assets/styles/bootstrap/mixins/_clearfix.scss create mode 100644 src/assets/styles/bootstrap/mixins/_color-scheme.scss create mode 100644 src/assets/styles/bootstrap/mixins/_container.scss create mode 100644 src/assets/styles/bootstrap/mixins/_deprecate.scss create mode 100644 src/assets/styles/bootstrap/mixins/_forms.scss create mode 100644 src/assets/styles/bootstrap/mixins/_gradients.scss create mode 100644 src/assets/styles/bootstrap/mixins/_grid.scss create mode 100644 src/assets/styles/bootstrap/mixins/_image.scss create mode 100644 src/assets/styles/bootstrap/mixins/_list-group.scss create mode 100644 src/assets/styles/bootstrap/mixins/_lists.scss create mode 100644 src/assets/styles/bootstrap/mixins/_pagination.scss create mode 100644 src/assets/styles/bootstrap/mixins/_reset-text.scss create mode 100644 src/assets/styles/bootstrap/mixins/_resize.scss create mode 100644 src/assets/styles/bootstrap/mixins/_table-variants.scss create mode 100644 src/assets/styles/bootstrap/mixins/_text-truncate.scss create mode 100644 src/assets/styles/bootstrap/mixins/_transition.scss create mode 100644 src/assets/styles/bootstrap/mixins/_utilities.scss create mode 100644 src/assets/styles/bootstrap/mixins/_visually-hidden.scss create mode 100644 src/assets/styles/bootstrap/utilities/_api.scss create mode 100644 src/assets/styles/bootstrap/vendor/_rfs.scss create mode 100644 src/assets/styles/fonts.scss create mode 100644 src/assets/styles/icons.scss create mode 100644 src/assets/styles/index.scss create mode 100644 src/assets/styles/scrollbars.scss create mode 100644 src/assets/styles/slider.scss create mode 100644 src/assets/styles/utils.scss create mode 100644 src/assets/webfonts/Ubuntu-C.ttf create mode 100644 src/assets/webfonts/Ubuntu-b.ttf create mode 100644 src/assets/webfonts/Ubuntu-i.ttf create mode 100644 src/assets/webfonts/Ubuntu-ib.ttf create mode 100644 src/assets/webfonts/Ubuntu-m.ttf create mode 100644 src/assets/webfonts/Ubuntu.ttf create mode 100644 src/common/AutoGrid.tsx create mode 100644 src/common/Base.tsx create mode 100644 src/common/Button.tsx create mode 100644 src/common/ButtonGroup.tsx create mode 100644 src/common/Column.tsx create mode 100644 src/common/Flex.tsx create mode 100644 src/common/FormGroup.tsx create mode 100644 src/common/Grid.tsx create mode 100644 src/common/GridContext.tsx create mode 100644 src/common/HorizontalRule.tsx create mode 100644 src/common/InfiniteGrid.tsx create mode 100644 src/common/InfiniteScroll.tsx create mode 100644 src/common/Text.tsx create mode 100644 src/common/card/NitroCardContentView.tsx create mode 100644 src/common/card/NitroCardContext.tsx create mode 100644 src/common/card/NitroCardHeaderView.tsx create mode 100644 src/common/card/NitroCardSubHeaderView.tsx create mode 100644 src/common/card/NitroCardView.scss create mode 100644 src/common/card/NitroCardView.tsx create mode 100644 src/common/card/accordion/NitroCardAccordionContext.tsx create mode 100644 src/common/card/accordion/NitroCardAccordionItemView.tsx create mode 100644 src/common/card/accordion/NitroCardAccordionSetView.tsx create mode 100644 src/common/card/accordion/NitroCardAccordionView.tsx create mode 100644 src/common/card/accordion/index.ts create mode 100644 src/common/card/index.ts create mode 100644 src/common/card/tabs/NitroCardTabsItemView.tsx create mode 100644 src/common/card/tabs/NitroCardTabsView.tsx create mode 100644 src/common/card/tabs/index.ts create mode 100644 src/common/classNames.ts create mode 100644 src/common/draggable-window/DraggableWindow.tsx create mode 100644 src/common/draggable-window/DraggableWindowPosition.ts create mode 100644 src/common/draggable-window/index.ts create mode 100644 src/common/index.scss create mode 100644 src/common/index.ts create mode 100644 src/common/layout/LayoutAvatarImageView.tsx create mode 100644 src/common/layout/LayoutBackgroundImage.tsx create mode 100644 src/common/layout/LayoutBadgeImageView.tsx create mode 100644 src/common/layout/LayoutCounterTimeView.tsx create mode 100644 src/common/layout/LayoutCurrencyIcon.tsx create mode 100644 src/common/layout/LayoutFurniIconImageView.tsx create mode 100644 src/common/layout/LayoutFurniImageView.tsx create mode 100644 src/common/layout/LayoutGiftTagView.tsx create mode 100644 src/common/layout/LayoutGridItem.tsx create mode 100644 src/common/layout/LayoutImage.tsx create mode 100644 src/common/layout/LayoutItemCountView.tsx create mode 100644 src/common/layout/LayoutLoadingSpinnerView.tsx create mode 100644 src/common/layout/LayoutMiniCameraView.tsx create mode 100644 src/common/layout/LayoutNotificationAlertView.tsx create mode 100644 src/common/layout/LayoutNotificationBubbleView.tsx create mode 100644 src/common/layout/LayoutPetImageView.tsx create mode 100644 src/common/layout/LayoutPrizeProductImageView.tsx create mode 100644 src/common/layout/LayoutProgressBar.tsx create mode 100644 src/common/layout/LayoutRarityLevelView.tsx create mode 100644 src/common/layout/LayoutRoomObjectImageView.tsx create mode 100644 src/common/layout/LayoutRoomPreviewerView.tsx create mode 100644 src/common/layout/LayoutRoomThumbnailView.tsx create mode 100644 src/common/layout/LayoutTrophyView.tsx create mode 100644 src/common/layout/UserProfileIconView.tsx create mode 100644 src/common/layout/index.ts create mode 100644 src/common/layout/limited-edition/LayoutLimitedEditionCompactPlateView.tsx create mode 100644 src/common/layout/limited-edition/LayoutLimitedEditionCompletePlateView.tsx create mode 100644 src/common/layout/limited-edition/LayoutLimitedEditionStyledNumberView.tsx create mode 100644 src/common/layout/limited-edition/index.ts create mode 100644 src/common/transitions/TransitionAnimation.tsx create mode 100644 src/common/transitions/TransitionAnimationStyles.ts create mode 100644 src/common/transitions/TransitionAnimationTypes.ts create mode 100644 src/common/transitions/index.ts create mode 100644 src/common/types/AlignItemType.ts create mode 100644 src/common/types/AlignSelfType.ts create mode 100644 src/common/types/ButtonSizeType.ts create mode 100644 src/common/types/ColorVariantType.ts create mode 100644 src/common/types/ColumnSizesType.ts create mode 100644 src/common/types/DisplayType.ts create mode 100644 src/common/types/FloatType.ts create mode 100644 src/common/types/FontSizeType.ts create mode 100644 src/common/types/FontWeightType.ts create mode 100644 src/common/types/JustifyContentType.ts create mode 100644 src/common/types/OverflowType.ts create mode 100644 src/common/types/PositionType.ts create mode 100644 src/common/types/SpacingType.ts create mode 100644 src/common/types/TextAlignType.ts create mode 100644 src/common/types/index.ts create mode 100644 src/common/utils/CreateTransitionToIcon.ts create mode 100644 src/common/utils/FriendlyTimeView.tsx create mode 100644 src/common/utils/index.ts create mode 100644 src/components/achievements/AchievementsView.scss create mode 100644 src/components/achievements/AchievementsView.tsx create mode 100644 src/components/achievements/views/AchievementBadgeView.tsx create mode 100644 src/components/achievements/views/AchievementCategoryView.tsx create mode 100644 src/components/achievements/views/AchievementDetailsView.tsx create mode 100644 src/components/achievements/views/achievement-list/AchievementListItemView.tsx create mode 100644 src/components/achievements/views/achievement-list/AchievementListView.tsx create mode 100644 src/components/achievements/views/achievement-list/index.ts create mode 100644 src/components/achievements/views/category-list/AchievementsCategoryListItemView.tsx create mode 100644 src/components/achievements/views/category-list/AchievementsCategoryListView.tsx create mode 100644 src/components/achievements/views/category-list/index.ts create mode 100644 src/components/achievements/views/index.ts create mode 100644 src/components/avatar-editor-new/AvatarEditorView.scss create mode 100644 src/components/avatar-editor-new/AvatarEditorView.tsx create mode 100644 src/components/avatar-editor-new/views/AvatarEditorIcon.tsx create mode 100644 src/components/avatar-editor-new/views/AvatarEditorModelView.tsx create mode 100644 src/components/avatar-editor-new/views/figure-set/AvatarEditorFigureSetItemView.tsx create mode 100644 src/components/avatar-editor-new/views/figure-set/AvatarEditorFigureSetView.tsx create mode 100644 src/components/avatar-editor-new/views/figure-set/index.ts create mode 100644 src/components/avatar-editor-new/views/palette-set/AvatarEditorPaletteSetItemView.tsx create mode 100644 src/components/avatar-editor-new/views/palette-set/AvatarEditorPaletteSetView.tsx create mode 100644 src/components/avatar-editor-new/views/palette-set/index.ts create mode 100644 src/components/avatar-editor/AvatarEditorView.scss create mode 100644 src/components/avatar-editor/AvatarEditorView.tsx create mode 100644 src/components/avatar-editor/views/AvatarEditorFigurePreviewView.tsx create mode 100644 src/components/avatar-editor/views/AvatarEditorIcon.tsx create mode 100644 src/components/avatar-editor/views/AvatarEditorModelView.tsx create mode 100644 src/components/avatar-editor/views/AvatarEditorWardrobeView.tsx create mode 100644 src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetItemView.tsx create mode 100644 src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetView.tsx create mode 100644 src/components/avatar-editor/views/figure-set/index.ts create mode 100644 src/components/avatar-editor/views/index.ts create mode 100644 src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetItemView.tsx create mode 100644 src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetView.tsx create mode 100644 src/components/avatar-editor/views/palette-set/index.ts create mode 100644 src/components/camera/CameraWidgetView.scss create mode 100644 src/components/camera/CameraWidgetView.tsx create mode 100644 src/components/camera/index.ts create mode 100644 src/components/camera/views/CameraWidgetCaptureView.tsx create mode 100644 src/components/camera/views/CameraWidgetCheckoutView.tsx create mode 100644 src/components/camera/views/CameraWidgetShowPhotoView.tsx create mode 100644 src/components/camera/views/editor/CameraWidgetEditorView.tsx create mode 100644 src/components/camera/views/editor/effect-list/CameraWidgetEffectListItemView.tsx create mode 100644 src/components/camera/views/editor/effect-list/CameraWidgetEffectListView.tsx create mode 100644 src/components/camera/views/editor/effect-list/index.ts create mode 100644 src/components/camera/views/editor/index.ts create mode 100644 src/components/camera/views/index.ts create mode 100644 src/components/campaign/CalendarItemView.tsx create mode 100644 src/components/campaign/CalendarView.tsx create mode 100644 src/components/campaign/CampaignView.scss create mode 100644 src/components/campaign/CampaignView.tsx create mode 100644 src/components/catalog/CatalogView.scss create mode 100644 src/components/catalog/CatalogView.tsx create mode 100644 src/components/catalog/views/CatalogPurchaseConfirmView.tsx create mode 100644 src/components/catalog/views/catalog-header/CatalogHeaderView.tsx create mode 100644 src/components/catalog/views/catalog-icon/CatalogIconView.tsx create mode 100644 src/components/catalog/views/catalog-room-previewer/CatalogRoomPreviewerView.tsx create mode 100644 src/components/catalog/views/gift/CatalogGiftView.tsx create mode 100644 src/components/catalog/views/navigation/CatalogNavigationItemView.tsx create mode 100644 src/components/catalog/views/navigation/CatalogNavigationSetView.tsx create mode 100644 src/components/catalog/views/navigation/CatalogNavigationView.tsx create mode 100644 src/components/catalog/views/page/common/CatalogGridOfferView.tsx create mode 100644 src/components/catalog/views/page/common/CatalogRedeemVoucherView.tsx create mode 100644 src/components/catalog/views/page/common/CatalogSearchView.tsx create mode 100644 src/components/catalog/views/page/layout/CatalogLayout.types.ts create mode 100644 src/components/catalog/views/page/layout/CatalogLayoutBadgeDisplayView.tsx create mode 100644 src/components/catalog/views/page/layout/CatalogLayoutColorGroupingView.tsx create mode 100644 src/components/catalog/views/page/layout/CatalogLayoutDefaultView.tsx create mode 100644 src/components/catalog/views/page/layout/CatalogLayoutGuildCustomFurniView.tsx create mode 100644 src/components/catalog/views/page/layout/CatalogLayoutGuildForumView.tsx create mode 100644 src/components/catalog/views/page/layout/CatalogLayoutGuildFrontpageView.tsx create mode 100644 src/components/catalog/views/page/layout/CatalogLayoutInfoLoyaltyView.tsx create mode 100644 src/components/catalog/views/page/layout/CatalogLayoutPets2View.tsx create mode 100644 src/components/catalog/views/page/layout/CatalogLayoutPets3View.tsx create mode 100644 src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx create mode 100644 src/components/catalog/views/page/layout/CatalogLayoutRoomBundleView.tsx create mode 100644 src/components/catalog/views/page/layout/CatalogLayoutSingleBundleView.tsx create mode 100644 src/components/catalog/views/page/layout/CatalogLayoutSoundMachineView.tsx create mode 100644 src/components/catalog/views/page/layout/CatalogLayoutSpacesView.tsx create mode 100644 src/components/catalog/views/page/layout/CatalogLayoutTrophiesView.tsx create mode 100644 src/components/catalog/views/page/layout/CatalogLayoutVipBuyView.tsx create mode 100644 src/components/catalog/views/page/layout/GetCatalogLayout.tsx create mode 100644 src/components/catalog/views/page/layout/frontpage4/CatalogLayoutFrontPageItemView.tsx create mode 100644 src/components/catalog/views/page/layout/frontpage4/CatalogLayoutFrontpage4View.tsx create mode 100644 src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplaceItemView.tsx create mode 100644 src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplaceOwnItemsView.tsx create mode 100644 src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplacePublicItemsView.tsx create mode 100644 src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplaceSearchFormView.tsx create mode 100644 src/components/catalog/views/page/layout/marketplace/MarketplacePostOfferView.tsx create mode 100644 src/components/catalog/views/page/layout/pets/CatalogLayoutPetView.tsx create mode 100644 src/components/catalog/views/page/layout/vip-gifts/CatalogLayoutVipGiftsView.tsx create mode 100644 src/components/catalog/views/page/layout/vip-gifts/VipGiftItemView.tsx create mode 100644 src/components/catalog/views/page/widgets/CatalogAddOnBadgeWidgetView.tsx create mode 100644 src/components/catalog/views/page/widgets/CatalogBadgeSelectorWidgetView.tsx create mode 100644 src/components/catalog/views/page/widgets/CatalogBundleGridWidgetView.tsx create mode 100644 src/components/catalog/views/page/widgets/CatalogFirstProductSelectorWidgetView.tsx create mode 100644 src/components/catalog/views/page/widgets/CatalogGuildBadgeWidgetView.tsx create mode 100644 src/components/catalog/views/page/widgets/CatalogGuildSelectorWidgetView.tsx create mode 100644 src/components/catalog/views/page/widgets/CatalogItemGridWidgetView.tsx create mode 100644 src/components/catalog/views/page/widgets/CatalogLimitedItemWidgetView.tsx create mode 100644 src/components/catalog/views/page/widgets/CatalogPriceDisplayWidgetView.tsx create mode 100644 src/components/catalog/views/page/widgets/CatalogPurchaseWidgetView.tsx create mode 100644 src/components/catalog/views/page/widgets/CatalogSimplePriceWidgetView.tsx create mode 100644 src/components/catalog/views/page/widgets/CatalogSingleViewWidgetView.tsx create mode 100644 src/components/catalog/views/page/widgets/CatalogSpacesWidgetView.tsx create mode 100644 src/components/catalog/views/page/widgets/CatalogSpinnerWidgetView.tsx create mode 100644 src/components/catalog/views/page/widgets/CatalogTotalPriceWidget.tsx create mode 100644 src/components/catalog/views/page/widgets/CatalogViewProductWidgetView.tsx create mode 100644 src/components/catalog/views/targeted-offer/Offer.scss create mode 100644 src/components/catalog/views/targeted-offer/OfferBubbleView.tsx create mode 100644 src/components/catalog/views/targeted-offer/OfferView.tsx create mode 100644 src/components/catalog/views/targeted-offer/OfferWindowView.tsx create mode 100644 src/components/chat-history/ChatHistoryView.scss create mode 100644 src/components/chat-history/ChatHistoryView.tsx create mode 100644 src/components/floorplan-editor/FloorplanEditorContext.tsx create mode 100644 src/components/floorplan-editor/FloorplanEditorView.scss create mode 100644 src/components/floorplan-editor/FloorplanEditorView.tsx create mode 100644 src/components/floorplan-editor/common/ActionSettings.ts create mode 100644 src/components/floorplan-editor/common/Constants.ts create mode 100644 src/components/floorplan-editor/common/ConvertMapToString.ts create mode 100644 src/components/floorplan-editor/common/FloorplanEditor.ts create mode 100644 src/components/floorplan-editor/common/FloorplanResource.ts create mode 100644 src/components/floorplan-editor/common/IFloorplanSettings.ts create mode 100644 src/components/floorplan-editor/common/IVisualizationSettings.ts create mode 100644 src/components/floorplan-editor/common/Tile.ts create mode 100644 src/components/floorplan-editor/common/Utils.ts create mode 100644 src/components/floorplan-editor/views/FloorplanCanvasView.tsx create mode 100644 src/components/floorplan-editor/views/FloorplanImportExportView.tsx create mode 100644 src/components/floorplan-editor/views/FloorplanOptionsView.tsx create mode 100644 src/components/friends/FriendsView.scss create mode 100644 src/components/friends/FriendsView.tsx create mode 100644 src/components/friends/views/friends-bar/FriendBarItemView.tsx create mode 100644 src/components/friends/views/friends-bar/FriendsBarView.tsx create mode 100644 src/components/friends/views/friends-list/FriendsListRemoveConfirmationView.tsx create mode 100644 src/components/friends/views/friends-list/FriendsListRoomInviteView.tsx create mode 100644 src/components/friends/views/friends-list/FriendsListSearchView.tsx create mode 100644 src/components/friends/views/friends-list/FriendsListView.tsx create mode 100644 src/components/friends/views/friends-list/friends-list-group/FriendsListGroupItemView.tsx create mode 100644 src/components/friends/views/friends-list/friends-list-group/FriendsListGroupView.tsx create mode 100644 src/components/friends/views/friends-list/friends-list-request/FriendsListRequestItemView.tsx create mode 100644 src/components/friends/views/friends-list/friends-list-request/FriendsListRequestView.tsx create mode 100644 src/components/friends/views/messenger/FriendsMessengerView.tsx create mode 100644 src/components/friends/views/messenger/messenger-thread/FriendsMessengerThreadGroup.tsx create mode 100644 src/components/friends/views/messenger/messenger-thread/FriendsMessengerThreadView.tsx create mode 100644 src/components/game-center/GameCenterView.scss create mode 100644 src/components/game-center/GameCenterView.tsx create mode 100644 src/components/game-center/views/GameListView.tsx create mode 100644 src/components/game-center/views/GameStageView.tsx create mode 100644 src/components/game-center/views/GameView.tsx create mode 100644 src/components/groups/GroupView.scss create mode 100644 src/components/groups/GroupsView.tsx create mode 100644 src/components/groups/views/GroupBadgeCreatorView.tsx create mode 100644 src/components/groups/views/GroupCreatorView.tsx create mode 100644 src/components/groups/views/GroupInformationStandaloneView.tsx create mode 100644 src/components/groups/views/GroupInformationView.tsx create mode 100644 src/components/groups/views/GroupManagerView.tsx create mode 100644 src/components/groups/views/GroupMembersView.tsx create mode 100644 src/components/groups/views/GroupRoomInformationView.tsx create mode 100644 src/components/groups/views/tabs/GroupTabBadgeView.tsx create mode 100644 src/components/groups/views/tabs/GroupTabColorsView.tsx create mode 100644 src/components/groups/views/tabs/GroupTabCreatorConfirmationView.tsx create mode 100644 src/components/groups/views/tabs/GroupTabIdentityView.tsx create mode 100644 src/components/groups/views/tabs/GroupTabSettingsView.tsx create mode 100644 src/components/guide-tool/GuideToolView.scss create mode 100644 src/components/guide-tool/GuideToolView.tsx create mode 100644 src/components/guide-tool/views/GuideToolAcceptView.tsx create mode 100644 src/components/guide-tool/views/GuideToolMenuView.tsx create mode 100644 src/components/guide-tool/views/GuideToolOngoingView.tsx create mode 100644 src/components/guide-tool/views/GuideToolUserCreateRequestView.tsx create mode 100644 src/components/guide-tool/views/GuideToolUserFeedbackView.tsx create mode 100644 src/components/guide-tool/views/GuideToolUserNoHelpersView.tsx create mode 100644 src/components/guide-tool/views/GuideToolUserPendingView.tsx create mode 100644 src/components/guide-tool/views/GuideToolUserSomethingWrogView.tsx create mode 100644 src/components/guide-tool/views/GuideToolUserThanksView.tsx create mode 100644 src/components/hc-center/HcCenterView.scss create mode 100644 src/components/hc-center/HcCenterView.tsx create mode 100644 src/components/help/HelpView.scss create mode 100644 src/components/help/HelpView.tsx create mode 100644 src/components/help/views/DescribeReportView.tsx create mode 100644 src/components/help/views/HelpIndexView.tsx create mode 100644 src/components/help/views/ReportSummaryView.tsx create mode 100644 src/components/help/views/SanctionStatusView.tsx create mode 100644 src/components/help/views/SelectReportedChatsView.tsx create mode 100644 src/components/help/views/SelectReportedUserView.tsx create mode 100644 src/components/help/views/SelectTopicView.tsx create mode 100644 src/components/help/views/name-change/NameChangeConfirmationView.tsx create mode 100644 src/components/help/views/name-change/NameChangeInitView.tsx create mode 100644 src/components/help/views/name-change/NameChangeInputView.tsx create mode 100644 src/components/help/views/name-change/NameChangeView.tsx create mode 100644 src/components/help/views/name-change/NameChangeView.types.ts create mode 100644 src/components/hotel-view/HotelView.scss create mode 100644 src/components/hotel-view/HotelView.tsx create mode 100644 src/components/hotel-view/views/widgets/GetWidgetLayout.tsx create mode 100644 src/components/hotel-view/views/widgets/HotelViewWidgets.scss create mode 100644 src/components/hotel-view/views/widgets/WidgetSlotView.tsx create mode 100644 src/components/hotel-view/views/widgets/bonus-rare/BonusRareWidgetView.scss create mode 100644 src/components/hotel-view/views/widgets/bonus-rare/BonusRareWidgetView.tsx create mode 100644 src/components/hotel-view/views/widgets/hall-of-fame-item/HallOfFameItemView.tsx create mode 100644 src/components/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.scss create mode 100644 src/components/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.tsx create mode 100644 src/components/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.types.ts create mode 100644 src/components/hotel-view/views/widgets/promo-article/PromoArticleWidgetView.scss create mode 100644 src/components/hotel-view/views/widgets/promo-article/PromoArticleWidgetView.tsx create mode 100644 src/components/hotel-view/views/widgets/widget-container/WidgetContainerView.scss create mode 100644 src/components/hotel-view/views/widgets/widget-container/WidgetContainerView.tsx create mode 100644 src/components/index.scss create mode 100644 src/components/inventory/InventoryView.scss create mode 100644 src/components/inventory/InventoryView.tsx create mode 100644 src/components/inventory/views/InventoryCategoryEmptyView.tsx create mode 100644 src/components/inventory/views/badge/InventoryBadgeItemView.tsx create mode 100644 src/components/inventory/views/badge/InventoryBadgeView.tsx create mode 100644 src/components/inventory/views/bot/InventoryBotItemView.tsx create mode 100644 src/components/inventory/views/bot/InventoryBotView.tsx create mode 100644 src/components/inventory/views/furniture/InventoryFurnitureItemView.tsx create mode 100644 src/components/inventory/views/furniture/InventoryFurnitureSearchView.tsx create mode 100644 src/components/inventory/views/furniture/InventoryFurnitureView.tsx create mode 100644 src/components/inventory/views/furniture/InventoryTradeView.tsx create mode 100644 src/components/inventory/views/pet/InventoryPetItemView.tsx create mode 100644 src/components/inventory/views/pet/InventoryPetView.tsx create mode 100644 src/components/loading/LoadingView.scss create mode 100644 src/components/loading/LoadingView.tsx create mode 100644 src/components/main/MainView.tsx create mode 100644 src/components/mod-tools/ModToolsView.scss create mode 100644 src/components/mod-tools/ModToolsView.tsx create mode 100644 src/components/mod-tools/views/chatlog/ChatlogRecord.ts create mode 100644 src/components/mod-tools/views/chatlog/ChatlogView.tsx create mode 100644 src/components/mod-tools/views/room/ModToolsChatlogView.tsx create mode 100644 src/components/mod-tools/views/room/ModToolsRoomView.tsx create mode 100644 src/components/mod-tools/views/tickets/CfhChatlogView.tsx create mode 100644 src/components/mod-tools/views/tickets/ModToolsIssueInfoView.tsx create mode 100644 src/components/mod-tools/views/tickets/ModToolsMyIssuesTabView.tsx create mode 100644 src/components/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx create mode 100644 src/components/mod-tools/views/tickets/ModToolsPickedIssuesTabView.tsx create mode 100644 src/components/mod-tools/views/tickets/ModToolsTicketsView.tsx create mode 100644 src/components/mod-tools/views/user/ModToolsUserChatlogView.tsx create mode 100644 src/components/mod-tools/views/user/ModToolsUserModActionView.tsx create mode 100644 src/components/mod-tools/views/user/ModToolsUserRoomVisitsView.tsx create mode 100644 src/components/mod-tools/views/user/ModToolsUserSendMessageView.tsx create mode 100644 src/components/mod-tools/views/user/ModToolsUserView.tsx create mode 100644 src/components/navigator/NavigatorView.scss create mode 100644 src/components/navigator/NavigatorView.tsx create mode 100644 src/components/navigator/views/NavigatorDoorStateView.tsx create mode 100644 src/components/navigator/views/NavigatorRoomCreatorView.tsx create mode 100644 src/components/navigator/views/NavigatorRoomInfoView.tsx create mode 100644 src/components/navigator/views/NavigatorRoomLinkView.tsx create mode 100644 src/components/navigator/views/room-settings/NavigatorRoomSettingsAccessTabView.tsx create mode 100644 src/components/navigator/views/room-settings/NavigatorRoomSettingsBasicTabView.tsx create mode 100644 src/components/navigator/views/room-settings/NavigatorRoomSettingsModTabView.tsx create mode 100644 src/components/navigator/views/room-settings/NavigatorRoomSettingsRightsTabView.tsx create mode 100644 src/components/navigator/views/room-settings/NavigatorRoomSettingsView.tsx create mode 100644 src/components/navigator/views/room-settings/NavigatorRoomSettingsVipChatTabView.tsx create mode 100644 src/components/navigator/views/search/NavigatorSearchResultItemInfoView.tsx create mode 100644 src/components/navigator/views/search/NavigatorSearchResultItemView.tsx create mode 100644 src/components/navigator/views/search/NavigatorSearchResultView.tsx create mode 100644 src/components/navigator/views/search/NavigatorSearchView.tsx create mode 100644 src/components/nitropedia/NitropediaView.scss create mode 100644 src/components/nitropedia/NitropediaView.tsx create mode 100644 src/components/notification-center/NotificationCenterView.scss create mode 100644 src/components/notification-center/NotificationCenterView.tsx create mode 100644 src/components/notification-center/views/alert-layouts/GetAlertLayout.tsx create mode 100644 src/components/notification-center/views/alert-layouts/NitroSystemAlertView.tsx create mode 100644 src/components/notification-center/views/alert-layouts/NotificationDefaultAlertView.tsx create mode 100644 src/components/notification-center/views/alert-layouts/NotificationSearchAlertView.tsx create mode 100644 src/components/notification-center/views/bubble-layouts/GetBubbleLayout.tsx create mode 100644 src/components/notification-center/views/bubble-layouts/NotificationClubGiftBubbleView.tsx create mode 100644 src/components/notification-center/views/bubble-layouts/NotificationDefaultBubbleView.tsx create mode 100644 src/components/notification-center/views/confirm-layouts/GetConfirmLayout.tsx create mode 100644 src/components/notification-center/views/confirm-layouts/NotificationDefaultConfirmView.tsx create mode 100644 src/components/purse/PurseView.scss create mode 100644 src/components/purse/PurseView.tsx create mode 100644 src/components/purse/views/CurrencyView.tsx create mode 100644 src/components/purse/views/SeasonalView.tsx create mode 100644 src/components/right-side/RightSideView.scss create mode 100644 src/components/right-side/RightSideView.tsx create mode 100644 src/components/room/RoomView.scss create mode 100644 src/components/room/RoomView.tsx create mode 100644 src/components/room/spectator/RoomSpectatorView.scss create mode 100644 src/components/room/spectator/RoomSpectatorView.tsx create mode 100644 src/components/room/widgets/RoomWidgets.scss create mode 100644 src/components/room/widgets/RoomWidgetsView.tsx create mode 100644 src/components/room/widgets/avatar-info/AvatarInfoPetTrainingPanelView.tsx create mode 100644 src/components/room/widgets/avatar-info/AvatarInfoRentableBotChatView.tsx create mode 100644 src/components/room/widgets/avatar-info/AvatarInfoUseProductConfirmView.tsx create mode 100644 src/components/room/widgets/avatar-info/AvatarInfoUseProductView.tsx create mode 100644 src/components/room/widgets/avatar-info/AvatarInfoWidgetView.scss create mode 100644 src/components/room/widgets/avatar-info/AvatarInfoWidgetView.tsx create mode 100644 src/components/room/widgets/avatar-info/infostand/InfoStandWidgetBotView.tsx create mode 100644 src/components/room/widgets/avatar-info/infostand/InfoStandWidgetFurniView.tsx create mode 100644 src/components/room/widgets/avatar-info/infostand/InfoStandWidgetPetView.tsx create mode 100644 src/components/room/widgets/avatar-info/infostand/InfoStandWidgetRentableBotView.tsx create mode 100644 src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserRelationshipItemView.tsx create mode 100644 src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserRelationshipsView.tsx create mode 100644 src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserTagsView.tsx create mode 100644 src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserView.tsx create mode 100644 src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetAvatarView.tsx create mode 100644 src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetDecorateView.tsx create mode 100644 src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetFurniView.tsx create mode 100644 src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetNameView.tsx create mode 100644 src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetOwnAvatarView.tsx create mode 100644 src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetOwnPetView.tsx create mode 100644 src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetPetView.tsx create mode 100644 src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetRentableBotView.tsx create mode 100644 src/components/room/widgets/chat-input/ChatInputStyleSelectorView.tsx create mode 100644 src/components/room/widgets/chat-input/ChatInputView.scss create mode 100644 src/components/room/widgets/chat-input/ChatInputView.tsx create mode 100644 src/components/room/widgets/chat/ChatWidgetMessageView.tsx create mode 100644 src/components/room/widgets/chat/ChatWidgetView.scss create mode 100644 src/components/room/widgets/chat/ChatWidgetView.tsx create mode 100644 src/components/room/widgets/choosers/ChooserWidgetView.scss create mode 100644 src/components/room/widgets/choosers/ChooserWidgetView.tsx create mode 100644 src/components/room/widgets/choosers/FurniChooserWidgetView.tsx create mode 100644 src/components/room/widgets/choosers/UserChooserWidgetView.tsx create mode 100644 src/components/room/widgets/context-menu/ContextMenu.scss create mode 100644 src/components/room/widgets/context-menu/ContextMenuCaretView.tsx create mode 100644 src/components/room/widgets/context-menu/ContextMenuHeaderView.tsx create mode 100644 src/components/room/widgets/context-menu/ContextMenuListItemView.tsx create mode 100644 src/components/room/widgets/context-menu/ContextMenuListView.tsx create mode 100644 src/components/room/widgets/context-menu/ContextMenuView.tsx create mode 100644 src/components/room/widgets/doorbell/DoorbellWidgetView.tsx create mode 100644 src/components/room/widgets/friend-request/FriendRequestDialogView.scss create mode 100644 src/components/room/widgets/friend-request/FriendRequestDialogView.tsx create mode 100644 src/components/room/widgets/friend-request/FriendRequestWidgetView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureBackgroundColorView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureBadgeDisplayView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureCraftingView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureDimmerView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureExchangeCreditView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureExternalImageView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureFriendFurniView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureGiftOpeningView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureHighScoreView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureInternalLinkView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureMannequinView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureMysteryBoxOpenDialogView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureMysteryTrophyOpenDialogView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureRoomLinkView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureSpamWallPostItView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureStackHeightView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureStickieView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureTrophyView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureWidgets.scss create mode 100644 src/components/room/widgets/furniture/FurnitureWidgetsView.tsx create mode 100644 src/components/room/widgets/furniture/FurnitureYoutubeDisplayView.tsx create mode 100644 src/components/room/widgets/furniture/context-menu/EffectBoxConfirmView.tsx create mode 100644 src/components/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx create mode 100644 src/components/room/widgets/furniture/context-menu/MonsterPlantSeedConfirmView.tsx create mode 100644 src/components/room/widgets/furniture/context-menu/PurchasableClothingConfirmView.tsx create mode 100644 src/components/room/widgets/furniture/playlist-editor/DiskInventoryView.tsx create mode 100644 src/components/room/widgets/furniture/playlist-editor/FurniturePlaylistEditorWidgetView.tsx create mode 100644 src/components/room/widgets/furniture/playlist-editor/SongPlaylistView.tsx create mode 100644 src/components/room/widgets/mysterybox/MysteryBoxExtensionView.scss create mode 100644 src/components/room/widgets/mysterybox/MysteryBoxExtensionView.tsx create mode 100644 src/components/room/widgets/object-location/ObjectLocationView.tsx create mode 100644 src/components/room/widgets/pet-package/PetPackageWidgetView.scss create mode 100644 src/components/room/widgets/pet-package/PetPackageWidgetView.tsx create mode 100644 src/components/room/widgets/room-filter-words/RoomFilterWordsWidgetView.tsx create mode 100644 src/components/room/widgets/room-promotes/RoomPromotesWidgetView.tsx create mode 100644 src/components/room/widgets/room-promotes/views/RoomPromoteEditWidgetView.tsx create mode 100644 src/components/room/widgets/room-promotes/views/RoomPromoteMyOwnEventWidgetView.tsx create mode 100644 src/components/room/widgets/room-promotes/views/RoomPromoteOtherEventWidgetView.tsx create mode 100644 src/components/room/widgets/room-promotes/views/index.ts create mode 100644 src/components/room/widgets/room-thumbnail/RoomThumbnailWidgetView.tsx create mode 100644 src/components/room/widgets/room-tools/RoomToolsWidgetView.tsx create mode 100644 src/components/room/widgets/user-location/UserLocationView.tsx create mode 100644 src/components/room/widgets/word-quiz/WordQuizQuestionView.tsx create mode 100644 src/components/room/widgets/word-quiz/WordQuizVoteView.tsx create mode 100644 src/components/room/widgets/word-quiz/WordQuizWidgetView.tsx create mode 100644 src/components/toolbar/ToolbarMeView.tsx create mode 100644 src/components/toolbar/ToolbarView.scss create mode 100644 src/components/toolbar/ToolbarView.tsx create mode 100644 src/components/user-profile/UserProfileVew.scss create mode 100644 src/components/user-profile/UserProfileView.tsx create mode 100644 src/components/user-profile/views/BadgesContainerView.tsx create mode 100644 src/components/user-profile/views/FriendsContainerView.tsx create mode 100644 src/components/user-profile/views/GroupsContainerView.tsx create mode 100644 src/components/user-profile/views/RelationshipsContainerView.tsx create mode 100644 src/components/user-profile/views/UserContainerView.tsx create mode 100644 src/components/user-settings/UserSettingsView.tsx create mode 100644 src/components/wired/WiredView.scss create mode 100644 src/components/wired/WiredView.tsx create mode 100644 src/components/wired/views/WiredBaseView.tsx create mode 100644 src/components/wired/views/WiredFurniSelectorView.tsx create mode 100644 src/components/wired/views/actions/WiredActionBaseView.tsx create mode 100644 src/components/wired/views/actions/WiredActionBotChangeFigureView.tsx create mode 100644 src/components/wired/views/actions/WiredActionBotFollowAvatarView.tsx create mode 100644 src/components/wired/views/actions/WiredActionBotGiveHandItemView.tsx create mode 100644 src/components/wired/views/actions/WiredActionBotMoveView.tsx create mode 100644 src/components/wired/views/actions/WiredActionBotTalkToAvatarView.tsx create mode 100644 src/components/wired/views/actions/WiredActionBotTalkView.tsx create mode 100644 src/components/wired/views/actions/WiredActionBotTeleportView.tsx create mode 100644 src/components/wired/views/actions/WiredActionCallAnotherStackView.tsx create mode 100644 src/components/wired/views/actions/WiredActionChaseView.tsx create mode 100644 src/components/wired/views/actions/WiredActionChatView.tsx create mode 100644 src/components/wired/views/actions/WiredActionFleeView.tsx create mode 100644 src/components/wired/views/actions/WiredActionGiveRewardView.tsx create mode 100644 src/components/wired/views/actions/WiredActionGiveScoreToPredefinedTeamView.tsx create mode 100644 src/components/wired/views/actions/WiredActionGiveScoreView.tsx create mode 100644 src/components/wired/views/actions/WiredActionJoinTeamView.tsx create mode 100644 src/components/wired/views/actions/WiredActionKickFromRoomView.tsx create mode 100644 src/components/wired/views/actions/WiredActionLayoutView.tsx create mode 100644 src/components/wired/views/actions/WiredActionLeaveTeamView.tsx create mode 100644 src/components/wired/views/actions/WiredActionMoveAndRotateFurniView.tsx create mode 100644 src/components/wired/views/actions/WiredActionMoveFurniToView.tsx create mode 100644 src/components/wired/views/actions/WiredActionMoveFurniView.tsx create mode 100644 src/components/wired/views/actions/WiredActionMuteUserView.tsx create mode 100644 src/components/wired/views/actions/WiredActionResetView.tsx create mode 100644 src/components/wired/views/actions/WiredActionSetFurniStateToView.tsx create mode 100644 src/components/wired/views/actions/WiredActionTeleportView.tsx create mode 100644 src/components/wired/views/actions/WiredActionToggleFurniStateView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionActorHasHandItem.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionActorIsGroupMemberView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionActorIsOnFurniView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionActorIsTeamMemberView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionActorIsWearingBadgeView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionActorIsWearingEffectView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionBaseView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionDateRangeView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionFurniHasAvatarOnView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionFurniHasFurniOnView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionFurniHasNotFurniOnView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionFurniIsOfTypeView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionFurniMatchesSnapshotView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionLayoutView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionTimeElapsedLessView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionTimeElapsedMoreView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionUserCountInRoomView.tsx create mode 100644 src/components/wired/views/triggers/WiredTriggerAvatarEnterRoomView.tsx create mode 100644 src/components/wired/views/triggers/WiredTriggerAvatarSaysSomethingView.tsx create mode 100644 src/components/wired/views/triggers/WiredTriggerAvatarWalksOffFurniView.tsx create mode 100644 src/components/wired/views/triggers/WiredTriggerAvatarWalksOnFurni.tsx create mode 100644 src/components/wired/views/triggers/WiredTriggerBaseView.tsx create mode 100644 src/components/wired/views/triggers/WiredTriggerBotReachedAvatarView.tsx create mode 100644 src/components/wired/views/triggers/WiredTriggerBotReachedStuffView.tsx create mode 100644 src/components/wired/views/triggers/WiredTriggerCollisionView.tsx create mode 100644 src/components/wired/views/triggers/WiredTriggerExecuteOnceView.tsx create mode 100644 src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyLongView.tsx create mode 100644 src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyView.tsx create mode 100644 src/components/wired/views/triggers/WiredTriggerGameEndsView.tsx create mode 100644 src/components/wired/views/triggers/WiredTriggerGameStartsView.tsx create mode 100644 src/components/wired/views/triggers/WiredTriggerLayoutView.tsx create mode 100644 src/components/wired/views/triggers/WiredTriggerScoreAchievedView.tsx create mode 100644 src/components/wired/views/triggers/WiredTriggerToggleFurniView.tsx create mode 100644 src/events/catalog/CatalogEvent.ts create mode 100644 src/events/catalog/CatalogInitGiftEvent.ts create mode 100644 src/events/catalog/CatalogPostMarketplaceOfferEvent.ts create mode 100644 src/events/catalog/CatalogPurchaseFailureEvent.ts create mode 100644 src/events/catalog/CatalogPurchaseNotAllowedEvent.ts create mode 100644 src/events/catalog/CatalogPurchaseOverrideEvent.ts create mode 100644 src/events/catalog/CatalogPurchaseSoldOutEvent.ts create mode 100644 src/events/catalog/CatalogPurchasedEvent.ts create mode 100644 src/events/catalog/CatalogSetRoomPreviewerStuffDataEvent.ts create mode 100644 src/events/catalog/CatalogWidgetEvent.ts create mode 100644 src/events/catalog/SetRoomPreviewerStuffDataEvent.ts create mode 100644 src/events/catalog/index.ts create mode 100644 src/events/guide-tool/GuideToolEvent.ts create mode 100644 src/events/guide-tool/index.ts create mode 100644 src/events/help/HelpNameChangeEvent.ts create mode 100644 src/events/help/index.ts create mode 100644 src/events/index.ts create mode 100644 src/events/inventory/InventoryFurniAddedEvent.ts create mode 100644 src/events/inventory/index.ts create mode 100644 src/events/room-widgets/index.ts create mode 100644 src/events/room-widgets/thumbnail/RoomWidgetThumbnailEvent.ts create mode 100644 src/events/room-widgets/thumbnail/index.ts create mode 100644 src/hooks/UseMountEffect.tsx create mode 100644 src/hooks/achievements/index.ts create mode 100644 src/hooks/achievements/useAchievements.ts create mode 100644 src/hooks/avatar-editor/index.ts create mode 100644 src/hooks/avatar-editor/useAvatarEditor.ts create mode 100644 src/hooks/avatar-editor/useFigureData.ts create mode 100644 src/hooks/camera/index.ts create mode 100644 src/hooks/camera/useCamera.ts create mode 100644 src/hooks/catalog/index.ts create mode 100644 src/hooks/catalog/useCatalog.ts create mode 100644 src/hooks/catalog/useCatalogPlaceMultipleItems.ts create mode 100644 src/hooks/catalog/useCatalogSkipPurchaseConfirmation.ts create mode 100644 src/hooks/chat-history/index.ts create mode 100644 src/hooks/chat-history/useChatHistory.ts create mode 100644 src/hooks/events/index.ts create mode 100644 src/hooks/events/useEventDispatcher.tsx create mode 100644 src/hooks/events/useMessageEvent.tsx create mode 100644 src/hooks/events/useNitroEvent.tsx create mode 100644 src/hooks/events/useUiEvent.tsx create mode 100644 src/hooks/friends/index.ts create mode 100644 src/hooks/friends/useFriends.ts create mode 100644 src/hooks/friends/useMessenger.ts create mode 100644 src/hooks/game-center/index.ts create mode 100644 src/hooks/game-center/useGameCenter.ts create mode 100644 src/hooks/groups/index.ts create mode 100644 src/hooks/groups/useGroup.ts create mode 100644 src/hooks/help/index.ts create mode 100644 src/hooks/help/useHelp.ts create mode 100644 src/hooks/index.ts create mode 100644 src/hooks/inventory/index.ts create mode 100644 src/hooks/inventory/useInventoryBadges.ts create mode 100644 src/hooks/inventory/useInventoryBots.ts create mode 100644 src/hooks/inventory/useInventoryFurni.ts create mode 100644 src/hooks/inventory/useInventoryPets.ts create mode 100644 src/hooks/inventory/useInventoryTrade.ts create mode 100644 src/hooks/inventory/useInventoryUnseenTracker.ts create mode 100644 src/hooks/mod-tools/index.ts create mode 100644 src/hooks/mod-tools/useModTools.ts create mode 100644 src/hooks/navigator/index.ts create mode 100644 src/hooks/navigator/useNavigator.ts create mode 100644 src/hooks/notification/index.ts create mode 100644 src/hooks/notification/useNotification.ts create mode 100644 src/hooks/purse/index.ts create mode 100644 src/hooks/purse/usePurse.ts create mode 100644 src/hooks/rooms/engine/index.ts create mode 100644 src/hooks/rooms/engine/useFurniAddedEvent.ts create mode 100644 src/hooks/rooms/engine/useFurniRemovedEvent.ts create mode 100644 src/hooks/rooms/engine/useObjectDeselectedEvent.ts create mode 100644 src/hooks/rooms/engine/useObjectDoubleClickedEvent.ts create mode 100644 src/hooks/rooms/engine/useObjectRollOutEvent.ts create mode 100644 src/hooks/rooms/engine/useObjectRollOverEvent.ts create mode 100644 src/hooks/rooms/engine/useObjectSelectedEvent.ts create mode 100644 src/hooks/rooms/engine/useUserAddedEvent.ts create mode 100644 src/hooks/rooms/engine/useUserRemovedEvent.ts create mode 100644 src/hooks/rooms/index.ts create mode 100644 src/hooks/rooms/promotes/index.ts create mode 100644 src/hooks/rooms/promotes/useRoomPromote.ts create mode 100644 src/hooks/rooms/useRoom.ts create mode 100644 src/hooks/rooms/widgets/furniture/index.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurnitureBackgroundColorWidget.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurnitureBadgeDisplayWidget.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurnitureContextMenuWidget.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurnitureCraftingWidget.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurnitureDimmerWidget.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurnitureExchangeWidget.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurnitureExternalImageWidget.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurnitureFriendFurniWidget.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurnitureHighScoreWidget.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurnitureInternalLinkWidget.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurnitureMannequinWidget.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurniturePlaylistEditorWidget.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurniturePresentWidget.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurnitureRoomLinkWidget.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurnitureSpamWallPostItWidget.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurnitureStackHeightWidget.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurnitureStickieWidget.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurnitureTrophyWidget.ts create mode 100644 src/hooks/rooms/widgets/furniture/useFurnitureYoutubeWidget.ts create mode 100644 src/hooks/rooms/widgets/index.ts create mode 100644 src/hooks/rooms/widgets/useAvatarInfoWidget.ts create mode 100644 src/hooks/rooms/widgets/useChatInputWidget.ts create mode 100644 src/hooks/rooms/widgets/useChatWidget.ts create mode 100644 src/hooks/rooms/widgets/useDoorbellWidget.ts create mode 100644 src/hooks/rooms/widgets/useFilterWordsWidget.ts create mode 100644 src/hooks/rooms/widgets/useFriendRequestWidget.ts create mode 100644 src/hooks/rooms/widgets/useFurniChooserWidget.ts create mode 100644 src/hooks/rooms/widgets/usePetPackageWidget.ts create mode 100644 src/hooks/rooms/widgets/usePollWidget.ts create mode 100644 src/hooks/rooms/widgets/useUserChooserWidget.ts create mode 100644 src/hooks/rooms/widgets/useWordQuizWidget.ts create mode 100644 src/hooks/session/index.ts create mode 100644 src/hooks/session/useSessionInfo.ts create mode 100644 src/hooks/useLocalStorage.ts create mode 100644 src/hooks/useSharedVisibility.ts create mode 100644 src/hooks/wired/index.ts create mode 100644 src/hooks/wired/useWired.ts create mode 100644 src/index.scss create mode 100644 src/index.tsx create mode 100644 src/react-app-env.d.ts create mode 100644 src/workers/IntervalWebWorker.ts create mode 100644 src/workers/WorkerBuilder.ts create mode 100644 tsconfig.json create mode 100644 vite.config.mjs create mode 100644 yarn.lock diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 0000000..3e5809a --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,11 @@ +# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries +# You can see what browsers were selected by your queries by running: +# npx browserslist + +last 1 Chrome version +last 1 Firefox version +last 1 Edge major versions +last 2 Safari major versions +last 2 iOS major versions diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..695c05d --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,110 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "settings": { + "react": { + "pragma": "React", + "version": "18.0.0" + } + }, + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended" + ], + "plugins": [ + "@typescript-eslint", + "react" + ], + "rules": { + "linebreak-style": [ + "off" + ], + "quotes": [ + "error", + "single" + ], + "@typescript-eslint/indent": [ + "error", + 4, + { + "SwitchCase": 1 + } + ], + "array-bracket-spacing": [ + "error", + "always" + ], + "brace-style": [ + "error", + "allman" + ], + "template-curly-spacing": [ + "error", + "always" + ], + "no-multi-spaces": [ + "error" + ], + "@typescript-eslint/object-curly-spacing": [ + "error", + "always", + { + "arraysInObjects": true, + "objectsInObjects": false + } + ], + "@typescript-eslint/ban-types": [ + "error", + { + "types": { + "String": true, + "Boolean": true, + "Number": true, + "Symbol": true, + "{}": false, + "Object": false, + "object": false, + "Function": false + }, + "extendDefaults": true + } + ], + "no-switch-case-fall-through": [ + "off" + ], + "jsx-quotes": [ + "error" + ], + "react/prop-types": [ + "off" + ], + "react/jsx-curly-spacing": [ + "error", + { + "when": "always", + "children": true + } + ], + "react/jsx-equals-spacing": [ + "error" + ], + "react/jsx-newline": [ + "error", + { + "prevent": true + } + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..154341f --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +/dist +/tmp +/out-tsc +/node_modules +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* +/.sass-cache +/connect.lock +/coverage +*.log +.git +.DS_Store +Thumbs.db + +# Nitro +/build +*.zip +.env +public/renderer-config* +public/ui-config* diff --git a/README.md b/README.md new file mode 100644 index 0000000..7b6ffc1 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# Nitro React v2.1 + +## Prerequisites + +- [Git](https://git-scm.com/) +- [NodeJS](https://nodejs.org/) >= 18 + - If using NodeJS < 18 remove `--openssl-legacy-provider` from the package.json scripts +- [Yarn](https://yarnpkg.com/) `npm i yarn -g` + +## Installation + +- First you should open terminal and navigate to the folder where you want to clone Nitro +- Clone Nitro + - `git clone https://git.krews.org/nitro/nitro-react.git` +- Install the dependencies + - `yarn install` + - This may take some time, please be patient +- Rename a few files + - Rename `public/renderer-config.json.example` to `public/renderer-config.json` + - Rename `public/ui-config.json.example` to `public/ui-config.json` +- Set your links + - Open `public/renderer-config.json` + - Update `socket.url, asset.url, image.library.url, & hof.furni.url` + - Open `public/ui-config.json` + - Update `camera.url, thumbnails.url, url.prefix, habbopages.url` + - You can override any variable by passing it to `NitroConfig` in the index.html + +## Usage + +- To use Nitro you need `.nitro` assets generated, see [nitro-converter](https://git.krews.org/nitro/nitro-converter) for instructions +- See [Morningstar Websockets](https://git.krews.org/nitro/ms-websockets) for instructions on configuring websockets on your server + +### Development + +Run Nitro in development mode when you are editing the files, this way you can see the changes in your browser instantly + +``` +yarn start +``` + +### Production + +To build a production version of Nitro just run the following command + +``` +yarn build:prod +``` + +- A `dist` folder will be generated, these are the files that must be uploaded to your webserver +- Consult your CMS documentation for compatibility with Nitro and how to add the production files diff --git a/index.html b/index.html new file mode 100644 index 0000000..18b1a79 --- /dev/null +++ b/index.html @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + Nitro + + + +
+ + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..4910ee8 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "nitro-react", + "version": "2.2", + "homepage": ".", + "private": true, + "scripts": { + "start": "vite", + "build": "vite build", + "build:prod": "npx browserslist@latest --update-db && yarn build", + "eslint": "eslint src --ext .ts,.tsx" + }, + "dependencies": { + "@tanstack/react-virtual": "3.2.0", + "react": "^18.2.0", + "react-bootstrap": "^2.2.2", + "react-dom": "^18.2.0", + "react-icons": "^5.0.1", + "react-slider": "^2.0.6", + "react-youtube": "^7.13.1", + "use-between": "^1.3.5" + }, + "devDependencies": { + "@types/node": "^20.11.30", + "@types/react": "^18.2.67", + "@types/react-dom": "^18.2.22", + "@types/react-slider": "^1.3.6", + "@typescript-eslint/eslint-plugin": "^7.3.1", + "@typescript-eslint/parser": "^7.3.1", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.57.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-jsx-a11y": "^6.8.0", + "eslint-plugin-react": "^7.34.1", + "eslint-plugin-react-hooks": "^4.6.0", + "sass": "^1.72.0", + "typescript": "^5.4.2", + "vite": "^5.1.6" + } +} diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..634eb063ec992a9de0818597bd6ea3c25820c563 GIT binary patch literal 9604 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 aBw&+;F6aQQkV{elxN-fKZavyL=Klcpaw0GQ literal 0 HcmV?d00001 diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..33cf6c6ab19d882ccc9e2f05ea256021a12f72ce GIT binary patch literal 29298 zcmXV1cRbbq_kZ8vy4Sq6>}zCmZDn6GGKxZ>Yb1M=kSp%BWu+7$87WCd5oKMwAtQ=p zT%)LrOI$be_xAbz9{#wv@B4nA*E#2P&hz;^ubXOTW6sJXzytsQ>nRJ9vj6}Ee}w`_ z1o*NUIl2SBz^@ot8v#IF7W1Av1NgU?r^Q)o0Ejvc0K^0UIJ$(m1OV5u0Pq_R04MVR zfG?!*?HOJ04TOi4xe0K1^i$mW^e*@x#;{Y?ri?#f>_}?r!%5AJs6dx;iObYhB7ZRQJ~$>Aq*rg$0U`H2>*R{QRI~Q zsNQzG$?pxz9JaMYc#5PLk-2*2+k>fx52hY`Tc57qo<5iRelaFe{A8KmM9%yEly~&Q zfz**N&d&FOy-a)m#)=A}aVZEqVvmO^J5hmUISr_h&*KHLtmzPAGJs{@V@^Nh3ppP4 z_$C6U&WBlHKw|RwnpvED%Dgx#geFPd2GFIi zEdV6O@75=VkodM^O`>#w3e>`8rWz*~99x)Zk6^KE{U52!XGA4|1hinUQZ5;qBL^ev zk)IOTVU0kmq3^x+-!~|tM)YmGRY})peGN?h{Q}ugOhS`VxU69!@H+L*O*ox_(n>yl zK6ul!2y%eQX@i~5Ugs-qdj}Isa8e6DaUhotc?54u5IktRzTZjyOE#zwIJBI2Dq}Jt z0Rb=J3V#Dn2&&BjE-~z4|7h$D{ApT06mXIX`#@(NMsh>ZxGJV*mpDU|r`C8dtR0yi z93mPDr?(sbs3|#%dU$o0d06Jieytv*d*r1Kx)61H-K2dZDaMzgOPw*M+si2WE-IUN zW&>#4B)ZuFIu6|vq56|8SYuYidmjOZ&}GY|JD|nGN6xB#e=avRahGJ!eRqGtZ}U$q zuoppoRzGN;QYsEw8_5@5$HH>~8I7QOQiA+))uVyscG746T8kyi-GD@y`>FsFQqkA-2dhO{D+Zpyau+H98toGI3;HEwduN9%zTt%txo`u;_rIQ(@jya?v-rH*ZAqV{8w;;9urR$67VZsClk=t39bPU<&Ic%#BOlS zh}BIP{mjIod_o0ilu7n8KLdaqJIB@K2>cr zSbKclr1@}2e!Cs*Yy>q-fqE=n#3ZPCUrL}})|?nS-03IXWWBz}C4Nl?E@skEd+o(A zA5Dz9OEwg~v>J%a!U4~7d%=)0-T)G0(Ba27HVQHf=>+oX$@YaTR0}J5r~2{D9b5{0 z13;dUw7iU$HKaqSuSS(u*|CheNujB?1P+ne8Ve9~so40p^GHZlnBaIYMDKdc`+2f0 z1lNA&FyWm57zOSmfafHs#;~ajqP&f$RbF|;i*pf71fL3ow<#WMU?_aP33M&DF733I zL!1{U3c%bVM(qCSVcb8hMd9HijzKOTpt8?^spWHZdHKuKA%SFTKhxcw_mQka)L8hE zfc7^#0pQeic*$G`ZSeD1vaw?xY}yd?&f-W6V*+~25OmM+-hukQOZq@`l}bS@Sr2$R zq92Rt-a3OcO!4kp0`%Dff+)}3(%CR3W?+7nK3HejyQrdn3f6cSiNqW605~r`&}5&p z%VE+9SINAwhyefijL^g;njtd#)RAepWv?rnJ)gCyvd9;#ZaCBp{=|3c;PTZaFcYhS z?C2()M4tMx-8A$AGf_KCK2I4BUAq_0R5Q&G_IRHc%=avpFr2!wJ$d(R)CwD3Y(>iu z8G7@`z3(e}58q^wX*u_Y8PcKSspmW2;Ujr`-QG-x8k2sIbAAYN`iz{B%zPOn z0qu;#0GRx{XDA)qm)eELjmkfN9jC!?DPN8X*0rs{&`^B}^@89Q6i7bf?MChCrL2BK@rT7BD9a49_CJ{Xo9 ze_u`i7)tqCKNg=Q7K8RFY005oP)!r|a}G!TR}Xx6=LU9r!||#sKPW-OWReOnf-m+T z`3zN5ynNxJv~OTwJy?DMR9?XLSKeosznZ5D4X2{qY2s_DmME@ie)usl0`*v#QdUi1w-X3tL% zP;hN!?fboCL%GH;HC1A&H~d4&L=D}44ZsU1w|^8^BT?P^YwzClt&t++u;Sst178ud z!JM6sCuGv1j-sPxheB7SC;BSXdy>+H_xUfyeMY9;%Xk#5<}T%=(CF@tV7?sN-ozED ze2@4n3O9;6@>LM=9j6nU7Dsid%n=v_JU*`w}Ro3v<~*{Yc`?`r91=)J2G z@;uflN0%G3-%jS<{Z5)eM6FnaK7}9m+uH`#-t^i-Lzvy4y*jyMlrn(<*Zy~ivHodm z$~ik|42sV3^aHmeJcs=8K%cqaW%1N^BIF$GR+QNPU-tyOPBndfmqlr%gUrT@6)m2S zTL0UGZsL{jGN>_L;6CyTL=-+6pLn08`=4V3T7*d57GtrK`}*Qt=B5!ckj#f!e$%l# znvn?kVU-ePS7rMU{poZu?4A8`0)}_{miGg?#Vl1L(n~@-o^9l)9~~=1eQ@2!%^NN`;&EJ ziwuB0NJ6T&;*D3v`-TkZ>_GeAZ%a*i@}1ZRiv~6C#NU=C_%K1ymc#8gbU&cmfLT$) zrAFhMP)#tWqT8HY?5liy7d+G`gV*;DaSgdeh8Bf@r;Op zq-ScrHNIcQNfL2n^*7#&b-jq?V=Sq&H?*qssZI8+t~Mai@H=@ zMEz422)Kbeb!++P&FC0DT*UoD-npMg^+mT3@pHt@M0g2^VW2;%r8>*ie?K7Ak8LVN zTf3t>9Z9il(dik9&fCzMCpX;y_n)T=T{lh^F;Yb#?U}>(d)gD7gg-#giDcSq@}79u zCgtrIa%6GD?L4b3nkqccfLB=gF-lUU#^CRh4dSWok)DiWGAmbkbOz^4^mLdh)>n*Hhxj?<7DB zkq_ynv5J(s6fJH@b!J-m)&#}Fsrl=hT)n?Pau+#l>tDEVU`}a%9ZSUth?8mTaqa7O zBX7(&iY=FD!X7BWvOHWQy~=AP0GG!X?qON?AW}43 zQu@k}%C#S$-;1uM?2e=;Y=vg(?>3A-a9 zjoL16{e7-5x38pmec_ez)7B#H3c#Ew3Oy8|bJX%Ji^DtncXV~K3&1uh)C8l;P($&2 z4O8pVCs2EVlg)Lj4P*IB0tM+`8#e6MxhP}A+*dE%La#{zCbKC)UW(`gHtMC>B(?Xl zlwI;6ADylIzP(c-_NMqs`3$8niTvuvA%PO{+0FKY3t}2l)WJIPm#hP~f#o9m?Jraf zrL9NHFRv#3OCvGJyVnB`&v4FgE1}miGF{1HTQx;HyyLNe(5E{p8P#>oq*&(uYC27v zIz!r5!RD4>!*@OQI5lNO@NK%$_?>Sh z3Oti^o`*L0h_F4*US|q61UWB8@>FOjoaN5Vt2Z5Rm6^?m+M%)~6s$j7g=0;MGkOE> z^AcnGmdIx^uROKvnHIX$hG)b^XGD}S%rm18SgBqUe|Vl7Lk+pPVCh}(11TyN8!Q|UbXQ6&EGEP7QLA3cim2tT0w;e?6|{d?2uAvSifnyBM?}+?@xuEgpD1{w#EPk8 zD$bj7IaYLGXU!h$hvS;q<%b<#_M)=IIf2sK33}#bZ@de;jUigkA1Tj=Qetnd+ak+X z3WK|*kJ;=xR5~$4JIZgoKc-7sS1o6m%2z~x+haFcFny!1gNY^5ts_fu*|2DO?uk!t~y~yasRk(ERaoO`mtm6`adS8P!`syrn}4zn%+LJFe%i zH>kq)xYBEGzebWRBT+8*Pp3Eu+j_RZZznF_AQ0dL0y5p5vi;`9vcW075I&v{3ct?w ztIEN+JGBP*$Ra)8^Iuo5Yn=uTPxR_iYnE5C6KfkfR9pskm5rBhP_bYIV8$I&SfVCz zqVV8>j})~L8&lK}$ZJJbjC_OV@U~yz1$kA<2S^AzdHDLlolj&@$fI7lj%k}7E<2B{ zpbOc;G+5TKU&60V|HG9P4jB$)G!tDf#ap;4Ll(M!r}P_0KvO<{`6m_ps!m|}1zFVi zml1OOqC|%NUEYfKLQ%s`MZJPRd1kBH9IBj01bUFSCHSmBQsbZO7*6>-FhlqbLPZ47?dC_XPbj@t7~CuoJZ zxjNREW;=t(8Q2?ebOYSHwtf)D=Izlj1?ZDba#|abZ>h8%aRVWSB8qN8pzqiLqgX&3 zHJRL}N&h%Oaw{+M5|n~4L24rzE_^VIglT6rW5=v?sDflJ{!6bK%~0Pn$Ns^gO&>Lk zU!Z#ABH8A*>Q^4OYA`8@9AcV}*C9=l)VrMiM^wzCpUef=j)s_rOH-Ncc7n0bZ5w0cdz2*Yy4aRLB zus43uG50z_`zZs5^6ICq_eH*|yx-5UTxHA2?QE*WjH%`nOOQe~EcZ+x_H-RypJj}# zSlJKR`+=@~F}-%X;#eA4jf@*V(>HnBA0M3lZ=uaH5;@-v4kg$PnzdK7>LZF~Vs)sOAlqMmm1oXA zlE`)kv%z96kf=0!>U2Y?Fr9_h46^L;qF{@G*n9?sFB)fD4x~Rv;Io}6?cH}B{`AC1 z0;*Y8!7ZQ?O|A)jFu?>+AAZ%t&rm|7HJY`~L*akAuhjneTPW%#5mI@^d4skCuSNFC zP#O6jmB$E@O)2L-{P^e~@Mu!zd{z)Y&PA&GgzvJj>be62z-H0{3!^1$mvZY z2>i22WvxTDoR-gZ@)@-{PdqCMVx~*pD_qKBou(Ai`*Kh8fp2DD7^*!$F8Kxam7dE; zxyfJF@G;%(*5wbds#u>iAKhG{;{&a<>c+PoH5u`V;S?>9uAKa{cPE*c3v^~~v5j$O z90>{h5}LbPJ1)zeO*~{b;hIlTt)U;ypSi;L&cxank28{LThA*>S7)@oMai9q?%tpn zAP@o}F@v+h8m7HRYC|5H;=ypz_o==(p1&zxv#sx5zA1lD2i>r1TH*|ICh5!A28=dw zeDXPuK5`G!YXTcsVWRQ}1};7bc8WHx;sRsn!LsDSH1yf}roLbxdCH}PIRaJ_`>ZAq z`iiq;6NP`W9(;cs*zNe8j|r~HGXJMA%ceUYYa2hXf*qglA2dG2_Oq(2{WjVWWoQMw zO?_VOKWI}NA<<5Do8*4cpw1f2hWghMitcoA-92oM_f-RT_O3u=&>${S80v05^-#zo zeyJB@l0KvUk#5o&HT6&n=1CW#Zw;S~ySA1*ZElbn>KH#GRi`2sdB^4E1di)RF25dm z&3(^WpmF#jT%@Ev@;*bx<99GnS*j~MkFU>ckA-Urq-iYO3_~a{e%@@44rS2B9eZxh zPJBntC0#Y!(bh+zvpkl09M zFSr231@e%;e7Hm2U5`4vV6%OZRT&zrN!RE>*ntt?XC_`Z=6^Q-ydI5WAzUsD`IEg> zwXTV5Cxsv0@M6VN2NKry9@7d=Ad7_{k<8jPAeTO!xe64|( z5U6EIxC||31i`z`=g5feqz4j}07^~Tk7l)UFVv-FBZAt7&iJWL)xLMOy!w4c=@YP| z)fJ+C|2fHOsAI*mrc-OV+z=WWz)K0Hth?R}|NCB73F?>Bs>*S1GI0>vZh5k3s+BAc z%<^77Tx0+RC}6oNh3mPkZNjy=n#Q#v>4(^f{osn5weMIi`we+dxD_RQ;7s_;h}FNhaiSGE zTK(8aodw)8m6MRoFaI=nN%KH`+eKhJ?QUp%9wdE+;XogoM-!CrGh=QD`BGr?Y&32C zV?c4818W6PGjuXEHE}DVk@pv3Pv!L=&K8jPVzE>Qw--)*}*Q=2PY9L>So{B+$W z2uz=YPiBmQRjPHlPswulSq#Nf_R-*F$~oRlG)?IA4Ox#DU;t(xemqY4E=cv0My`p# zy_9WmiJbRV8Hus*==>c=iImEie>JHo#idw%hOag10O*7UuplY!7ojM0iGX&~M;Ru0-I} z6T8BG?0z`)cVA%#B4RNn?3?lWUQIxjLPTT4*%&K43XO3p0 z!+X`YY<0Q6VStO+38(qf0J|fkR(O83-WKkZ5OUk@0@-isyM;Ow1)Oo=RU##FN)Ze) z9z2|8-1F28^}oG2-zYBp8md=yUB-?}ws(nyuoY#7O=Wii%#LYl;`v61k#uBd)Y{3}TpD9tM@y86$5 zGEocQEIpGxrzTzWYVipwV)fFL?Tu$s*l%y#lsTk;q+!R;ZfQ=8An?j-S`Q{sL)G0J z)$fZ?lq~lI0~F&)&`7$NJgRK53>2mk0TY!vAiXiCsknq_6Unxl>4^A_j8XAB1B)9W0$^$>uAorwJ!YsX0VjuJO7Li`%y!!VPZ|2lT*O8NzEf zSg6+`?ayI8d-GAXWD;NMHU97(T#>vu7KlnEx*RPUP8(6QD>0#{-A*-~vG)sbcw+a%46ooWh5d|E{K{73KCdd~(Hy>w!O* zmLps%2N3;K_Ol$~R`t+J#}f#$49gGxBtd!Kpa15{s01~!$&TW(&e#u2Ta;xyuF{rp zR>uh~D-HK!#0m|>!HMg08i<`t*k zzw_$eeX*}`aH7wJnABtbv1lk#CyuFXU+rQ#r!76cT7t-c9>!J2=OQ$E5T-|cn0bJI}daXR$ z4<=g4HDV?58-423EE4l)Dz*?-AxJZm19D@+WhK+Ot}{!Q{%_sw2Pk z(WMm{rskExFGAgO8d3^Lbi6tfKK$EtPVaG?%|3l=j|b1JdVL z{K3GLcQ*U`l32t8ChL@QpkSV##x%rvTqTBaJco8QVO&g#GEa_W-s^);caAd$*bv+~ zJjt51S4P}YoOGBVDNj0LT3C^nTV@WEKf7h2HN+|6lt&%`j4!*< zF$oihEQH-?2wz+XR^+Q`dTp#oyIC%BF15n|m(qJpr`HD9y&LFTV_L-t)*GcdX&TWr ziRt_LFW-6Jno}uIHMW*yE8X`26ILA0O+|og+Wvxp*i=*(go7~Y-a1jH{n0dlVB7{SZ?%-K>O$b;CvLna|>g38=a8Aihe#O^Gc zlQt^B7Pw>vJ)cq)sug13J)WhWs`CeYfROu;YRj?UZJlhIUjll~gP*RB3B%u~UFfzR zVzXR;?hb3pI6|4r2R);)D_h_!?V~yAA^ESbvdJVr=((SDeXotB$T(kVYU9vj1I)|* zaEd)#{u}%2r>(r9Ft9$H5Hn0vB@NEc$0VY^m9x@?cn>1*u@h+=W9H!c*`eQw$`ma= zjLGXajC8w+G_)4T&b^qXTSF#@_u(uY`@=X%10w(umVcYQ@ zDY%6^SxPP@?5)Uu(_}BcGSM1oUfe>#AaN>-q*wZqTvZI_@Ns=}FHYlDlf-uhYUCR9 zvaEuOB|)V#o6*pVZFdzM1Rlf790fr0G!VnfybT-o)ERNhG6a2S%7TQX2S%E!U`!4k zAll%%J-8;HnTKN03n3H!a_V6933$nENrn&Q)6OQKH9A^>CO#l)#LadVtR3GZfQ|{0 zN{*(2iP|g=#OzAWqQqGf43Mq2*E^J?a{IrJEM6fQK-z+5rcCMY{C#Vl)`C^|rRA}u z(*$tHna{PwP;BK!3_CKFyYPa@P%-nKq^8U4;mM6N`5uB2Xt_ zKy$}5H}>W$DEv81K}OP4Z>{Q1GjT`3yPbTm%+}qC*;n~V%28xE^PYCTpV-G;Jg9N@ zYJw$jCY+aPFn;!louz7SkT8~^vGd@pd{}UDwIcTiex*+x@6GY67IgzUBmA4o#ExW4 z&WKyT-9LPn`N4WhA-FHb&dsSC^mBfQg*!Mk=eqz0B21;>Phcs;( zQ@~p;-oe63Ev|g|Eo4gpcf8&Re7Nz9N;TN0m zt(<0e`pu9{|JsmhGUhf5$Aw}~@eQ{hnODQA?xm6I>iHgB0dM}K_P31=b$n3Bdv?>= zGO)FNlfK$oKEp}~pxo8jem?jsJR-*D)At}5gUu<_ln!Nc#6tHn@v3}%*3;HoX4iPN z@6g0I(!o|LgMwY^t!cL@7Uc&JBn~`RZEEsC0OR7N)#LRhLA!15ni@@;4^>t5Wry{d z;@LYMJbKc~NiurIJaMF7mYo36p&&Q{98KrxZRJ@aKmE5gXBNY~wq|fk@Zb83Ob`=*miB{G})diC^ooP#DC z2z(9Y<$Osq%;xQjtZ|P;1=IfxwBO`nAgn0^=HM7@IvcRa=IPb5sr=K)N&`eLW<{*l zU5qIY&2s%)N8&53W0_=hwGTPCegpEkc+$C>lWb4+1sWi32CDqm=T18RU5Pw9g%4w~ z|CD#hcXGG1jO$S6M|nr1jDV{>Hl&6bF!d5V4IWX_%%R}~K7|ULRH!qQZ5dwqM(_W_5P;Clf`bzsS;*HmshpnoYx!+YxDeL*WhlQX@ z<*g|#5|G+Z1ZkJZNOUD@PEz~6{f){p{W}uN7#19_HYmq3WEU54IpCKK0h7^9+$Z0w z=erZ(Rj4@-N+FQ5)?E*^u?+oYeJt42D|6cvci_!m(`&;jY{u;52YlQJA4%TgvJfdE zb0h;X-!=dof?dC3q5F(dU13EIRU=46wU}ts1;hCu_D#unUOq(V*O=H%m%xW6`l!qN zG3?8SjkO^Ht{+&%b6kN0+|-9fDw@a%Xjzn0=bP2y`1?)!eANhgF-#)6YdwD<9$6jd z@yzG4I?zS(=tUGVJ8%a~%!{+#>^uk`?BhUzT~dE z^VUdlGF@E98o7KoNM(+9#-BWC=B&gL_LS#aaUjx6B+1?YanX{X(NpYV0SSS!LMl<5 zz%ell8!vU@>j=3Z8=LYGIbZjNOJPtYuQcBio5GCz!JpF0Wr^J@mM(YIUpVC3>Y9@! zCO!wWZ&?EVad5+=)C41j!SGgSA%teuDMpEHSE{_6T(FMrR1mSr>y+o*S-)*8_!}Q|Rq=XtfUsu~qn!|M2&6WThRcVb zh|t^bRGh@mytL4OsL=Q&rI9xCvkAvq!a~)QSmu6_66JJx{fh|*#9BB+?6>j8d5II-!td<%lF)bmoQS6!l?LHrpPc)uO1bT=v`l(C!5 z3w5iD@^DCx`HGtZf3Z?t^MOv^f3-3LbO0y;IdP%fYc0<43n7ZE61d)mlo_O?5xvUF z6r1?mk$mjy^~S@e!_qsbun^ki21GTVqTY~ZAx%!;)0t8gvd8>Vue_g`cwvaj$xLWX zRbJtEhTvhSZZw*}az~8|VOLfbZSNO9oa}yhsk7w*Y)nWkxMlR~BbueaCywY5%#ryO zI;fhC=rBn^VTMYHd%L0T=xw!eGmSw!d{Au(Hx{VRKMuR8L!sYgf{n)0N=okmaqN%_ z76gt@aY2v8H8@4$n}&y-L5Jn6+u;n90L}S0y!;oYMC-`+9$EqwIf`>%$WvhpY0K;a3qmOvJlHN=6dcmkBo z^6z(R|FF5geVo7JIjiRE8|Zwb4_(#SEoh0=ZrASBS1XNhi!&-$~^7~-jKc70LbroosZwmJ};#kNBEYdEHzKFxT= z?G!=kpEP`F0$z@D-TCXog#QFWR1N9j*%}_L&vm4=GJ#mpGQS0_GgGa7tN+GLmGenw z<h`oQ6`sS2h$-cf5MGD7Y^pT=a`-vvsN`G~ye( z=^@QdV^xYNoKIdI)?D_LY%9x-m&nmcy8w+;Uolk|-zvIrYB{Y9d^ zuJllxo4gV_>?xm3A2MAZkdC=!2CwE)lZp68a8}6Vya(I@4_n!Zo#)@?jozM475ryZ zS-0KP8YD!>nqSi!9wvFLdAvyAOxHsO@B8QJkm_eoVoN#6xN`up5opVyiRZJTh{xvy z5N+|l*<$#$7T&*XvUXsNj@_JGUApq4h_B&kur$kRlJSfqKi-u;j3r8<*dr8q5BMsC zA{J&_)TTwOx;CxbXf~yC{V{uXe0DGRFSgSVOVS^LLUEv$gG) z2i$~9P%M$s{pzyA@feIBA-%apBqTlx0$Ze6eg}#vaY2$`B)+I^N zX>r4!Q3WDNuo+VM2}$UAD_sN_91zr9J3_3(=@vl1?Su|dcsO{B`#bg5RW66!!9JbH z%R|g6SNS$icRd*6MmQ-Z4W3(4aA7BUMv4MwKr{W!QMTvbqz^u3ocYwZ^;fS#$qo6` zUoCx-^@^LLS42bJKcM(E>A%(l;rk438gWRiQE6TdM7n<&PW5zQ>0Sv=4skqWspWa3 zW?-eE)6U1o#EB@?&iSs(QoCK_J_~)j=NNwjR{V9fo)2truxq}`rN^>?3QQ{e9@BLF z9Or6VlvFk_PD+L)QPCYNwGS(?{C!=hDTnVrn1-WSW<(kg2hTcz=N)m(#QOuFDHivK zxXEuZkRdV7IlR)z%=~X3hdtjH?MRThYZ(Tm@_PQF=5ElKq*8tsd%}A~h+KnZ+GIK8 zWn~CU;@iMJJ-|d*X7pZ;24^WgwX%;=@t~DoPbA`RT4IO#7rV}I7nyXshow({L$XCJ z6|o0mE&qbuS0z8!Yl-JW#~a?yG|q)&R>18>i*T-|ZeTDhyz2|O%w%^pf__v>f+IJ} zOWi&(8vp6p*~F#|!xDJ5H6px&q8PHo$OM~Aze?bc2W(({VEuU|4!JFHeu2Nc6AxBA zLC^&Ba^Ei7>A6l2G%?mgrKI^r^+E6(u2)%*sD_L;o7) zCmx}2aE~1At)GaJ1ns_JlLkj4T`4S$KcMFto966v!s^nSx5jA`pS75~BFKrx?-)X0 zCzDEEXsw)6&o`zgnT;OdgO}ntAt#R9&pbSTSvRERwS+3$8sBltkZMey-L?m8OE}4I zK`GF0;QCb-8)%K0NPOgZoU+@EBV_Q9UF%i!`MAB#PXq5(iqMgBH@4sw9zXXFb(o!@ zk*Z)FJyNbqOylBKPiB%*XLj|h{S~9V?!k?? zjReSYMf)==bjphwb0E|L)?mB+ewNU0n4aUCs~K zb+MN9*p5lxZ->v}V9T20CL4pj!*w|0tXIATzru4%{2S~NIpYAmDxBA|C>fA(V8F&w z?2P({56Klx7BOcM>kQeBwQ`)0_mvDqQE(H?zTV(-l};h!oBqzaQ+iA?jdY4GVUw6i zfn*!qbo92$QWrylKBIHVHRN#-qJ%-{SR*w$rCP;D)**i2V-sL!Z6Q3IVXMxQDilBN z8S<__M&PDSP^xaqwtPx~;1qPZm!T6m5%t7^xV~F*4Xz||PPKS;Up+#yM^Vb2P<;3w|q(l~12U@MVfA@@7GlCb`M4Or(gvA$CU- zprBI@Y&leVjNliNxv|4{{yuxOG*uW}IKz@**_~JB7nK8I_}TWv{8(M^F#OMz*fd9I zg$TfE2&x+S3E9CN5$k$Q;1FlR#RsajIq#6n#|k`bB00vFw5Wt@g#{<61@q~!;OSKf zb0}gL`5#z^mq8lhaH^g&s3&6KVdPb}Lb4V~aLc->F6#>G=B(1bl6rc@zomktx@H%E z`(dV$&mSuSPsUF`PRruTaY})c^mY>T;=Xih~c}Nc618@QiKLrr_O85Shy|uS}yC@w} zCfY|KlrH8Duey%rpOp`YLAo7Ag!~ZdY3}ZiIG8H9^%HU;UJeid*%HK}{?kf#M!0UG za%M*sr~=e=4-4uw3eR-#`7w*Dc2X6wO}82Qh0yJ zt%;KyA1Okt0Qa!$D{(eaL3-p{*FsZKQLd)CBP^shJXl{M+~RW{(bz$K5prNr=T-Hf z`S2pM219KeGqClat60U{7d&AziGp6Y%EnPZr!T!=mZ;{OKJ?rJ4S>Ed5n)zAGaU7?^T*#<6z6sNUJ- zgSZ3;3ob1e$NL;^rw0h6zzqXsAs0!>Bm(I>(Rp%cTTDW^duOR2u1;rmnH1L_z+xA) z#Yy%E}YAh!o!}rqIMhc2>vx(5Iv0}=ced9ti{x>Fam%!l5 z8Zdp(NcazXc+w>ehXD{qn25VIzzfqdVcxWW&BzCSK6q$eFGI(`(#$Cr>}JVhea!Ty zap6^z_CL8qG!WQ21Zt_E+@Ye*Q0s01j6UG$r09lB_tRjuXFu(&^(J;mV&e(Sugx#@ ztz6&DZZzb6P-Kk_RW6LyTe&wXDj^41wkAkr{6rTqiU|NdNbwPK7bZ)a&lwC`v-EiS< zH72ixJyW-NedeAsG{@2n>Llkdy2dF2MYDnmSD&OVf_XSp#8x-@c$97tJC^7ua(Y%I ze#Ift$R!(WGzssNO5k_d$yTwDT$`anw(3YGiW69))fd;nV2Fiv1>-n7@!5W+o%(9K zr#2&*S5;C9xUJ79o-6E0?9iT0t&RV=)o@E!wVu^1h}7CwpvF!ng1E#Aur*753uq$2 z)?SC>cS*@?^`^l;HuJO1Vd9&_F{_ubse{r>9R&U*dpQAHbA!`*!0w}ffv;q5v8l}@o44E^cz>mPyAT}hNdrd ztB{jHFqZ`O)M~3v&k(7THu>ND*BjSwC5Y&*W0lTL-df~8J@1}yMt9wg2UQx*(ocT! zeRjE{LL=m^%LRzRqiT$gyaP%s5{2h_8}>5oi2BQ0^4#sD+*?{#6%kqL&4`_x^kMvR zwnt^sb0{NUFm(&vh=}=f#wnoG+Ec;%ZC^C6VW<rBkY8Im?_PKb%mj>z ztV*>7e@9}6!m8pp?z$Py;L9d;GQK~B>stHVaxkw@9>w|!yVhPu@~p8yC_zaJyb;dm%=U6*x4UXofvaJfLhOD zRg24o-!eHuBX3neK}?i!B*x!LXcQYz)`qosdK+}EQD3wW%sRs>yY6bvmzFRma^yc6 zp29Zquo;XA>3FO8{2^6$o&$usT7lA|=(*fRBmR;@DUssqwFv|W;@NAQP3DD6P_?q- zFL|>hLVC7Kd6%Ycec(iRw0%;BH+h{}>>~z};l`@G%E%KZ*`zj@=X%Xxk^Gp|Fn&kL z^s+M_|2=U?dbT^;)1F}UbYqPHTYL>UK>*h-k@^%Ec-nfq6r|z#nO=ubwdS}!-fgHs zj}?1g=pab^38kLeE7#MmtG?tii|Eh^&5;>|FCE)5Ads=rS0YRe>??HqN|2D_YoT2?X!y2^FmRj!vr|wi76G1t zVuPxY*TTG4TO^GPwAp7xtaCMGi}poq$J2w_<#&z+ir)G#q!2JM4;5?7%LQoMK`hiS zvc%Fl(8o!)$9r-=ccKG1*UMvu?*oTU^F|t;j*K1UD_HM)D6x>L>Hbiu=x^b5Z5<5 z|32pGNLrYWZ}J01%9r4dJ=2YdV~8bz^}jM~%swn3eii_CI?wxXy{2`Mxv8L?^`(@l z%_)ahzpgD^;z`6uv$F(&P4A~)7Vtbs`)4!Kbe-ze7G&vPQY>4?lISFovI2HEVzb_-& zh0CD-D$F3GZ-FsX>n8i^6GzQf8Kp9vRz2#p^j`^}zqoR!4mc8>0We z4-4GIu3|#Fudl~P3dIO%E#IZH?4l}H{0SABt;0%gDO=c^HCashu?nJY0PG}V$qPGs zpd%SjeB65U?PNPq=xR|ra}x{{D0iA}25Pnaag2?^{O%ay9j_N05SG&Vr8LzwG?5KT zo9{W@pm|(c)_*}sMwG=i=!T$Z=namnYoE*xmCsDge!Qn_^5WN0;x|UhN?)r+$XArq z($vDK^qyJORR~)4OvUpT9FnTegAnDsZ_EznU&3cah`uf?%PAmt(Z)+gWx5>V0&<=; zmjnyhtbU>=7+&j|(Lt2sRu~6BNyh;s{A$@^Slxl^yxRWWYe3dn#UFqAK5lteFvY)! zY>k<;X@sF*nDe%WlBcIp(%@H9?g)A|{b|1<2?E@FH4xH^3rhlL#b%{ax?p@HV{h8& z%}CVR5MSen2m5*5QpBDDXk@w%Q*jjV zI3ASfoiHpO!5tUL>{J(0xC?*!+;rTELo35NPz2FUFSgA=&o{e=2xPY0S@9#%^p#-> zyyjFJqPP?Y5^QkX3bQv~DMQT5CZQqhhI~QvJ}SENeC2f9smZ%T$;lk5(R|$3=Tu|% zxvP8A+f?$}1T$Me%v_yyER2;qkR?QyAw+2-w{}9^iywFW#EVjENZWIzHBJE& zs4^Y=%*ZiTSWYPu5jT~&ILu8Rl^x}AbU2^DG55Hu>pvhT|D^<1Kz-b$-G~=lHRA96 z%g$}~PPC8IN6aN|KW)uR8qtGaT#bhf8kOIn`Avgg!ZbYIb81REYY~uvU`-_frhca2 zjt5JTef>o#BEe+>W_ge0kgw<7Am$a=B~k*e3T;E91-GHxA761<*8Xs#|2kw?eEC&i zytusfh{fGj1@yA@ak38+Q*(x&m z>UK$Uh`kznVR~B(gpQ)VsptC=axx14XP^3&>x@4o-6h7m?e)O_RIphdYjJR0-APUy zW+|-ZR{?!X({ogO|hxbz-^vtu}_c_?0XXbZA2tsg0u`pJfUyGmu{#KT6DfUc&In1ET!m_bSRI- zikD2-MdBdB^$UT1%u*e&m&BfDA}1q2p6GHpEaC|ZVR>bA>yP6na>| zkF*XknRs=GGIdq9Rw46^@M|ps;U1fg_ABAgHp_km95)@|c1bAf;n#}KEfV2ji) zf6|z)KNFY#z zTkbgsVBbA&d{V|1pIOK%oV9FnyOoc(W!@62bF~nPjHv#T3y8HA*BhB=kZNDbYOX58 zT1{I&XAEJ3XR9j`6d8Ay_@|yWWKjn+ux1yiXXUu@a69BKd#Rm+#4(@Sts)T zR&V33$-SO~Ft<(vNKE3bJr3NOZ^J_Hg^6CSYpgO(v=s&{ zCEy}17A+i4v?b-QR0Ygx*|*>1x&Xr)xU<9`6+jsKNndK^o7z3>!nyxPP5_6;OQm5Y z@}rZy)vfF^Opx6uCpCTI@(5jNxlX!|2VBwRmaLU~E@C_D2TE57tydPg$fJp6T`4Y4 z$HJP#DIe4Jo#*iYEcvt&BK+*#?k>B3m!s2oK}5P~ed*p2_KPJ8_sLs(#dJa}-}1P+ zLxcW#&iiwB9(lXjM_fXy*CznyWt!_g!9iBKU^*n`os}0bN{KV^)=^0Nuu3Gur&fPkcH(xPgDoj*a--7e z-Q0L(Swa4wV)YqA_W3Kgq)pZ>Q2GU9S&BWrpTO>%rdfXMu2g@OmZgg%2-w2<(J08) zxO1p!rg=-M);g-#S;CR9w`+*gtCw7wExuXsIJoID_m(c*B=Z*W%LnAw<+QnWM4`HN zFBiZ5)RnY2Q_s-!J}4_&26j#Gw06f2G7rH`TDp%)+b@-@Ma)Xn&HT81!bxwy_DG$D zClLgUc&@TL_NuylPZGg~rI!m6TIcx|6Z;a~tDYbgH zx9`iJa9v@ROK?7$3~)>KR&rkFdu`@)9duShr+2W;hPiJxDf$UZvptqCJI85HjG!q9 z2!|Owejg)mk8#u+Sx*|p`wk7W7$o_ZU9Nn27OwOkfR2Csd#r}y`Rkvz31R-m&HoWu zr@;*`FI6!^HQq#-0`f~@)L28)N|()=d# z;I70V@HT@;?-IE4wgby^Y7o`L%13cw^6>8r4RRqP@F_MKa$RA};HvpXqf9+;hz2Gc zR3TXf$O7%>K@@aZemq)CD(zf%qeoux7n@9%9>c|MqK}fi3+cXxs^9;- z9E%GSv+LZ~skP_F;5Sv}tg2jDFaKZ|8pqqvMpzVRk*Bw!m;Me-A@rBioa5QKoEHkX z370u37l*4LcR5b$jGy+8`3R|JWH|?{`rNf*BnEf$0{3YAosk|QQl_`7jyTjCEQJd^ zbc9X$#xXuCxIwW(gQt{*wVJxfzSs@j^^9(@8?N`-@s7|S3bcLsh(E!)46Wd^K{yWq zr-biD?Ap@l2|<3oxi6VqS0zbD$Fj4os;xDtp~4Be2|kC6ZX89%!U)&pIEft`V`2Id zG1Z3c)A-Q@yP00K60lb9m|4oE$)nfc4(u?3jzi?wiObp{C3upc2)iR8*aX*ar7i8p zP)-ov-VTAvIt=W3?mggnG0ip@&-PtCWOwt4d@0tr+`JPsnVKl& zcmA+$>O@j36GZvMF6HG)s@9v%o4!w79pI?A^+=p@7?<6kzf|Clb!_UK=awM6H2ehJ zZFP@yQQe@GD(B0Bk_cR2=eK!~@9Lpx@954MEZ`)7tR;xR?kTE%-YNP*Jl1$N_*`I% zjsTJ%xWZwNV{W6b%ZW+#+qtc`L=9PMT0xVucl_1KOe81*jO%XVFZ#wLD&qG~BiE=E z9+?51GaK`Jf`s5ia}!-X@MbAOf*gkRMjfd{KdhQ34N zynj-}Sz?jcTN`rS&44agl*@O;bCA4!(};dGxmwE|`Vs>asjJxJ280L~LzHGZ$stbu zbZ1e=LqG&-&qzzkG-GdAQkxe~)v^jKqlpg1yzWhX4xG{=M#!5`HJ=ixtSZY$H zXD>l5LFd2*m>iFm{3_@7%KcK;*Me%nm=R7{Ug>VxWN#tU{6E=gXQuhfqw?8VS~z#F zf$epVLj=~xM+{o~kJBACbXv*kEv^=NtqOkSqnlzfJ@?L^YYW%z%f_1h3gj*kIx$ z?{C-Swe*P?&4V*f7vgbW=!TSb@-MEUyU`Pqubdv8zfFhZU@h>8vJGq9k~^-R-YDLd z-qaGCZ?`~0>>f5PZOa^~+*x-NO%yuCPD#2xK(W50vpiRHrj8;@yn^T7k_s%*7(=h+ zU9qO5MmBs;O5L&gU|4J!&Rx%r5(CQpe3gpHizP~9i7RWgGf2m+6A3nJ>n>{qhvioy z>5;CN@l6A?Pf&YmdwtKn3Ap}BN_88D$Y7lvyXep`Vv@zb%(fELUEBcBYFc~G+Yq+T zOHb|hV6}rvdLOY=RqW-_Ov{rl6mpe5;FTOjc@q7IWpJZ{jSStA_xc{ODlLruBL*;J z(A`o;d)0;f0y(C*<8nj4JUahTxwK76I2n55S5ULV-wNs8-2k93w6u!(87h3(&< zGD)t`30#o8DjC)Fb?`}uY57cV`*V9CNGO(@9y`t@FbcHiBRr3$l-_L(vw?Up31n{q(paK!+WfMJbn~KO4KMpbdc0+7o9@W_$yXJMWI;3-^p_o!_~smb z1G`v@yttw|jwW-Bs4JfuA)21cxezB0EPnxoZSJ*}@mc^I1NkZD+mD=5mHp=vZsB3} zjVSUW9>xc}51aGov^uG=%k_Ee?j?Sg-no+C?A8XJQI`{K^&=@ikLM5j@`sg9+E&VV=WP1fTv(eJz>h?I<)V4< z(&RgnQ~L;qSX;OoTx5w}fF`R_9{J=Vj}qHB=hR1cfA`vs3NJUh;?AbI8a=D}{4SiZ zzmDQbRCg$m;yU5V_VbmrJAu2-%{@t^d{&BhmK*(+FRfAtP0*Q9poZcV_`%4CW#9Tre{1GV?}r z5VH$3)((;0#Y5AVgF1O)C(wRH0b=Jc?yl3vhTWd^d{cTZ$r7uT%Q6i&G#~Dsr5qT$ z!X^>xb<@aWd1pll^g|E;Mr7{!iGZKFHXl1`a{1@dfeDh`$)Hg2rWZ?Hw9S+SRk=Ew z))?sVeR!-$` zkYzzeDdIa0wU%x69cE7tP!E&2atKeseJN0^GY>%I->~eE^#S6eFEh8^4!JAwk@c7c zaaLomABbg`RIg2qFJo(0W_c>aB-Q=#-DeX|=IAh!vK|BHo_kS4t}H8Yp_iW|x}MF_ zWuu_igKQ235VLK)?)2Hz#Ag>e7$hp5Y)wk=s4IVQ=fYCI00+@BpMPH-Jf!LmQoH2j z7gv@|-Y2HeZSlPe9K=e(ewlpFS=dD=qxhQ;MN%tPo`IC=%RJ`a-$X5ywjYrw9a@JiRw5p)6 zZB5_j;^R8bqK`LeND6rEkzVoKQwT#1=zESPWv9X75$(QNX3q7BENvTtH5275jj*MZ zRY-N_oU#r=gLV}1PEJ;G>Gpaw$E2S`Kh?#Vk)W~E7|vkW3(LHooY+}qC8<8f;)8qg z*TE=9cgXPChWIf4#UuwyC4QAZSv+=ayg5^wlK@+RmE(PTnGMx^4VTWq7Io-%|Ig5h z?-fp6Y>nh6B&FLut!ykLywd5rkqm71O7}-9J6)0zhqfi=9*_1aFG#D~D!cz#><7GcxUP3 z{*`4oL}4Oe$0X>^q^Bz#;=Dl}h>jBzj-X%3e}x`s9^djaECpEzxu<>HB9_~oe71Z8 zRA-BK@w0aW6WX1w*0qVxv1^N@fm&7#C}l-CLOO7DBX)Pb*pS<+k2qaKgWl`I)3TR3 zTm?6+1`Cv4BSjcRizC!09O~pv#(!kj5Alxsjs+H{!AiGz-3;3+9zDp6Q*CJ13 zx+s7Y`$L(8lC19k`Dy;EPm2$OeR*m!bDH86(or@4TM~T;rYc@i73xI{=`ALBm0#e1 zh(BCCK61DGg1>wAbmknS&$S=oz6pIt0zrW7=~|4Dg`ccvjqKAmr20AG-0V+ zX^eGaXupdQlCKtxte~45(lhm{hTIE;3}Egssbev=IdLqUsqfhS_^t~Ix@)nI zHG-r5n1CM8WL!!Y;>o_?&>+1f+xB~%i8pqpNT&X0@uD5T?;QcZ#5JNh?fn!8A#|0 z$YH+$fF+M?s8;$r6d$#fGd^2<2OHavQCsbS`Q(l;BPk7?d+^rhzaPPIyaeP{1ZdS_ zzb>!F5PgGL7 zF$rwMC9n-UTUi*pfNNi!;qUozkx-ib(p6B7BH$riy67NrkFHk`zHkLRnML^;2xU#CY#tc^UQffX#jMr+?Ym4c?SuxiJs}iO|JCc9LH(j zl3W?yZyKN;lN0FFW^6yn>uEFdk z7Rhs**)x{$nYB;Lx@}n5=)*lJ4-fLMt@)t~bU35x} z-i2NY!D;7jaPG^PxvCwmr%c^JFS01>9`AfzhzAERzmV?Bx$^VBvfRI*$+_bmyIDUO z8-&g6bJMbshjS0IVAErw&BHbG=3)UV&IP!7)H~(g3OQ<_l58H)Pi@}*F~8TfmD?`) za!K5zl=`F|$^(^czniSIta3gOj!@*et=kX!5xks>;kbA#AO6!U-R~fi9?`mHnw{VTJ z7C^p$h)|(c()-K699-8$xznOMk+@~U)Rl?qP?4XfaU3Dsn6)^9>iJRAco+E?W=zv3 zZ-QRPY0a_%xv8As9X;=quZAZ)_TKqJB+o0av~BuR#g1A~@;T|HzfM z{KIY%-Dy!762Lj`SvM^22Vv%)el;}IrRnd$CWb@;Spyj)^NnKYoC2t?ISEq0pBaeK znUndld`!8H*+F!fc?HB%N4-do;#CYCf{>UsK~ms(+px@cV!&0PM^gfM?~hXAvpX&_ z39`!Ih%Rf<-=}MXe=FBAw!h3jC5e|tu3b>Ddh=zZ>ou1>tc_<^c)17lzQYF`E_ePY zbl*yB3>k|i@Q)>}>@?e#xKG@!Bz!p~70t22K?jCE^O5WKk}-|NCtqDSH7dO-Y9 z0(7vUa-R58=-Xdh$(wku>7=ZyL4++244z93Wm&PJQda)*^%|OdDh$ELva4A-e{ePD z3IyhE0*yBvtx5@Q`sllHkUmPNGd6NJ(Xcti|C6l3lbWyeT7QmS7Z^R)M=+%T$wB%U zzYPhhQTkg3w1Gv$Znltr;^A=ed8%zPk{)|Ch$KTw2?$A)9g)?rE0F`}fyRRoKw{H3 zSqbdtVxqru8X{e0f#2zXf+lZjf^lBa7YB-eZ8#iB--eh-tf019=7eT{S&DyrE(|!q zyDihr$taLQF!b)z`F3j8+Fy*wnZnCu9v{_gk&sM#|I@nGI+Y2%aur3FJ1sh}QfKFl zD`bLrX*c)32ZOWii0%0srrodftPPt_a)Nf+;9Ge!kFq;|_%Umf1kD-kQw|N{D=yd* zm#>$bn|RewmOtYPp#yp;eC#`{3zzeqo#3~D%DcPyXfhx5Z4w|O0j3kTJ5bVN#@5WB zcbvv;H;Al9*NfQ9K4YaH5uFnBkX{Z#ZPVD_)AA2WfaY73d?#BLVR!qIQ|^{zi^Nkt z%wJdhVZ6B1kulTftHz__y)VbZbn-Ze)~GkcdKM$F3hIGap>MuQ^>a^VSa$*0S}TS_ z-Re?@AP2_b32rQDdbYZ9<+7Yz$5}nAU+Zq|+_92nJVV7B7rKjF$jv9!?zi$kH3V&o z_bxNaSY8QCqX=ETi??Of1HQ@ovp1acx)vXpgxypsK4; z$&l}|84i8jUs`cCS|dFM0scQ#zAGO0#va@EdIe2p(iA`0+PEh2P{)3C`vA_QCG9@b zc6$*!T7w&tJ|%SnoRCy7qlPP!DUH_1%u20D<>me8&G^h@?w=&KUu}9JyiGy7BV@vu zZe3Om2fP$E#5aVJb74Hxj*NBBB&7vhpHX$Ma!FoF%U?N6#?a2h?)yH-ytM$J*^+(O z3G$1kb>{;DA}UTgYg~L!Z&^VUYJ+x-lq`9+aof@?vHMSeE`ecor(yIAyDj?qLAiyF zLt$p@iTqbn_|+)Abcvpqsr5q>hn!WmZrCW?{FcU|U2j#(kN);uxhP#JlpDviIA7V< zZ2_Xk`&;;%*fM9L^n+HZrw0#$7qkh6b!*yq&$E=W@i+fbq#sN?LkUOk|I0T;{NXa5 zRih=sD20sD_F63>UuI)qC_O+1+&=+Y2^5~INXGqkv5BF+a|lJoF=Bis=849pBjo%h zk2p$X?lY1sP|CA1yG1Cm-?M8S0r)4n;8Kn@j zFm4{6#=R>s17QTrTplSg&o-PI23-8+*w6}ja?6o0xhb=}`e~gI$4ko~+Je0G9b8^} zW>gyb%Pi{@TltT$lbOlEqzjWE7jZ3@2r%P0dKXnnxz{ar|{t02XdtKb|dwbBbkFeGn+*0a#^MLpqXDQxC z{Evm+43-{5VL?~F-5(+Wfq5CpW1?Vx9L$i6yEGqz-;BL9-(=OtJyjRQKD{n-NVb01 zt$e!*no9r2V~gCBb9JI`^SmFwQL6!ZL^p&&cd)V{@Tk#{%JG0G3Cu?8R<3($8>8fb z42N@uH@T3g2+AP2Uj)~Hp1tr~pp{Jzt_d}r;+Lu}>*o1$x^{oGRp@5C;w#<%y8S8K z=!Mi6sSuuBz0BbSJv%pfPzFrTADA{bPi@NB7^x)&w7Mc&>!i79ASz6Kq= zlgwCwOF@S06ItSaLUFh1+Z{t+9)B8BC*5NMpU#?VN{=|x{ZD2{OabMW|(TLr*J) zWlL%qzSLm=*CHPxm(EY0aYIMQI{RydN+`J9?J0m6CAE0?hnHTs{NPdm;Af0Y{nckM zU5&mQ`{;*o{teQg;7gn&DUD@dHZ$Hq3ZuW?cv4P|XT65%&iDHcw;aBC5e^PL zW{P&}FuKv#|1+ARP9N~^BE5?MWNsu4?gDv+lYkdx%C zM3JjZ;N=--1dd?&O7d1Y+A?A|ko4e0{lu>|sk{g14Od%Oe`uy%sZ(r)>?meerB1}y zA}k!CVr~xN)qH+LI7xpc{f=xclX-UAzN8{4pF;tZi9?eDi%Oypb~oUslSG7&P!rrZ2uZlR3%ap}<9g zw!RVyREJ&E?0;4{*`~1)2_hUO8hjvS(8!Le(uU7iL|B?=8D|_xh2;iI>lgt=`(B*} zRc^WjotysS*{GB8fUL>569`nr*YQT`)Vn_hZi7nqJnbcGC8-eL4@HpWklB#zhIMVNptzDTr{%LWgqlX!nx*YEpT6Yfg(#B+j9M&XwhqcuTQD zrap43NWK&Lmrl!>p6;qvb_ad%SSHiWO*Ajj_tAB5b{^dIYUy3b7`mlJSB)Z!&aIygTPUJ6w3eAh0m$)aXet05#PGABtku{jSViz;X zLsdoG1@M`d`k;36BrfzeQY3@*AqpyHQ}D{@2VXeJ+ve}g;O`74m%$QPcMYX}AL4vV zYCHUkVD*j$q)Rd-%yj~;FW(>KnWQOoK#8+&NR$$Io!{K3=QlikC!90-ei8HUBE3qv zzojR7;=l|l?-CkG#5lu$eNG8=pZUsPOZW;)Kk@yXwuvS{^Cx10cq*=5fgU5fenA^R zT?#J;D-qehEQJ=?`+&{qFBc3PlGq&vk~gocOC^V!1O5P;I* zqjTmre{gp!&`iUfw9Q~8j`3&QjhpEA6AYe+Vuqo;N`ehV_eM;ZmNo((^s`6&m^Cz# z_KScjzo^NiRjhyhY_8e&`%FvyUhaH)A3UnkKWOnAc6nU*5sgy{xrK^f$`}u0K1=Yy z3x*o1c?Y#zx>8wmyNPXOph+liCXf0idi>L9ZWrn6sfpx&1o1C&+DCUs!7x#fAP)dl zv3%6V($+w{R_X1gtXh=>)bzQ_Gq%y@aT+abL{{=RmNQm-O9oZ>{5$`G_3#a*65#`l+zPAMj488>*dA%m3R6OM9n&9>sf?`8?-BI=zDeq*~JtH6*FPAZ_hv* z-)*wjMw5NcS36qpZm{>yMzT*ucHmQA_ue#XaOb(vEyLGY{RQQiy;Wp;w)pSRSic07 zw&v-KLTXQN?4PB^HwrgGTipjJZ-}5p>vnYryXVY z#*NM=%hj3?3T`CE$ftUN|d%ni1Xs+sqdKqPi|NdIzJDW%2 zh&|Z)4x>o_PvxoMRA&qvSQk>Yj$YF}1f&h}0?pDD<4$v@C&pH~8`s$Im}=_A`;@A> zplrQZQ;NFdW5)106&U}ApMlI-EKBihdG;f6g1&iAv&Gla%vpD!0CYxw12vx@o}F}i zL(V+z8uVHX*#ZOO#`kJ&EiQS!Tuk8KWPNQ^m_U_+-jdweGH^sQaujj1Vz9^3!7lTF zC0r&+{z4E^l^ROYVirZ0Cn$W>jQA$%RZ3)R#gyn!mMKg0tmf2Z+sB}_>qG}2Dxe1yRH`PhX-W0%<%Ztfi*)0kNQ~h)z;L-3GrlN-`36TpHC zGNlo4WPKM^FBgFEXW?=V4e5TlWtxp5iS6C3ti|+OZ85}V#wz(ti-@s!Se|cAI6;Uw zhE)X6t<0Dj)mma=pj@;MymzmZ!hiJmmfEL=4qHgx3C<6%i#TT`xhzK*+u7|LO7HW{8=0WiK#aEdI> zVhug!JC|q6L{J dLYqe=f|9B_VT8;P}kFt1<(-@of+RA{ZP@D;HhXWGSKeleC9SjUAAUxGJhmM z35to%iELmy4`|?E_E(=}0BZ0ZpzXc4XR^rcJ#(DLsdAL%d6JNwuG4D|FV%8o;<|@X zDNDE!>T#f_ln4Y?M>jJcp)#&=p5p7^BA3zrAg{|NHG}e$1Ls{XzP#|#TpqdsxrocD zlTm;wRUrgtxk!cs>W2AorO)P_*i&@YdyV;NltBh|QQrL=UV2$}EHQ@t$Vc~>-j|Ss zb&P=R+idk@{Iy zLr#~GQW`;%VnzQeLj7K13igx~c*c&W<%Oi!a*Mw>ZsC$lS`H6Ol3NL{J*?IU!+oOC z2%zv!_ubFQB3jA@fKEA>+;1qY9H(p;Or%6ritm%wH zi(OI;Up>so6z=g{!hF<=d1w2Mb((3gNdQ8V(Dyispx+-Vip;dPZ?qZ}y zO?np3Tkj%(u~c}7Fb3tyF;jjw%|=WjhCx_@dITV@EuN^uz%s381b6TI<!JtMz8BTna5AIh04kdo@a8y5}Y>Fsx7N#GAPKBM4 zMlIF1Cn6^%xNqJU2A`A3w?NWQc2wYzVh=1C-1JKz5o7(*>#R6Fd}r28nSjL1y2C>| zhcM#Eu5NvStauohLldPRnh=+%d(Yv=j+lH9$joSQ*H%o|FTj5@&u9&|(fT400y}E_`zv?JiF&d`> zM>$CR(x74Bh(fjtO?M1Z>UQ<WfJP4Kt&vF95l@&{oz-aS7o-vSx+S~(}kEa~{9dx0{7S#D(~cc?c` z+)eO{_}MAA^{R3%eJ6|?B)8shv^qyD-z$+`2@3^ciit>EL4n5B5}AK_VA||*TDy?l z=S7p{U2XgKm#?V8rjhZ2w~xmh79-npc-#Yoq$Y~-n-_P&C`)l+Qt*#{miX?*v(bYt zogpNExjp_B4T=oLM+-lJcBxMaPi2lmvnAcU?JS~Qfh!>_%b9;CkLkqM%Y?s353IAe zQqmCvkKMP&Ep$gO!a$glgD4T2v-A6K3+oE+1ZAaHa06-I=`Kaeu7Z3Mtw1Np00dtj zwR=xTQh;qN;n~d7-+7cbHZk)j4Du2@5|62GaEe>TtpzFe~7x@1I&{N zs#MJ50`e&1_+Qx;_XO1=x-*Qu#QAB%ma2A^z2e1>cX>|G3P#P3nag?b0&?*kLF-Wk zkmwz-FabG9Apc-JMZeF7<|X(h0igPFNz6_wf!A1-*OHMv$Kj^UO_Qt8KN4Z9om)vx z0ZJ&yjx|<*07g*Vf9>FO{j5?%V+cwv)PsOrfxDlLJh)vFn4T=&|*u7k; z+UsTy_TAieB4So)e?GN9myRQI`B&@lrMnR-NSMyW-mqd{m>iks%1qMi zIm$WWQjdj+ks-`J8j10hQh)h@2jc_ytU7@SZ^J&ersyd&XkAw?Lic0QcO9)_f$3{2`FCNIhL13~( z!295^G(HY*2RLVT69i;s53`avK-V3-+=Y~B{Gf7-zgjNif z-HU}zGh`Hn=3#=ZIIB5BLYt)X>yCq@i`_u#S@k_K23=5glC>4UHoj8W-;sMgBhr1ci9} z`$qr&4-ngxdkYS5zT)H(andtN1|JsU>wn2dCL$W|Bjb;c@PZ&h32l{|J@mAa(p$TC z{np=3LM;2a)V#O^_Y2w-pjiZE%!%4v-YokYon+pPjlAye@0RFhdq+H4eoPHo5D|!2 UHPyKZP61h4oG`C5^|<G(N-r2OLR*R5k${o^%6vj-XbD; zjdD+Z|J^_Cb6*eV*>ZgI&Aju@%y;7SbTvqcn210i5UG}?ssV8Q^zWAd8~FRNHq`;R z+@*E@K%iILAW&Eo2y_L!74{PZ@)ZSve%XLPGH*d3de3)X z^yPpL@SZ=>PzBxn`zdTMPY2$?_tDZ(!{5RoC*TnyCyw6$fmpw3sVW%;%N-8I`_Unx%zluHBgx&IifbW_Pd_6-nX{Mro+bsT^*ou!j(3R^`!`;> z!gQ#npwGJRIsJb7^7+luYY^4{i)id{6s4tj#?EsF(gTI1uT*FV zJ&zdX?pgz?OK2%ZXgg7{YJEqDM66jP_JgHrSGanWv!Ti+1mKV>J3$F$&-IPrb&)$T z{TC-H<>N+;`fgcH`pglz7}e4<*n+ z(jb41Pit2fnruWh@bWE1<%NA#?R?MrpIry%l+KS0TfGn z>6z!NHle*5Ba;T{vPEmhi>}x#p&c-i9wrmdj=nssMX}Mc$KkXI54(2JS4rD*-VU>- zSt$(}Y@iG7Bu(G^O6kF}qSqa&kM6+&pIxYIoXN27?!JC^A$2}zf%uvp%Sl#e4ktNK z>nWOCsl8uF^2JqHm#WTNfbnG2q3G-Dsc7T1cXs1KFt;DIa(*cyA+dvNO`Y|*7xTIX z>T6cUtB-$u6@ZVXJ|jL;e}u1P<8hrbf6Sm@-d}+Ux#oy}xZZ`uMSUfI$q|PGAsH!_U~ULztwHWyDs=joe9ayeH;4A>pqwFU*!JEdlZ;NuGtKmxN#N1gwK!B z#XrP)vA}dEsdu~dRh>cUXDtVTp}YLJLF@3j*LT(O2X4|_t;=KWbE8X6)=;pS#F)a!Bl6#l1<>vCllUaQD!6U6OdshE!4j`_Emkk07sF9wYF}KV)FnEtz0TH78uJp8u41n*?uX{% zksZs#vE-@JOjql`p}O4@%Vp!OhmiiztDLJH9y%ziasNY-k2IJxd|A?@k%6A62({^| zdWBFmQ!`cUSlh6fjD!kd>%28k*pPKevA!^M^zA8gl`q$EERG|Zifl(4(R%Y^=jkLd zZCYA)9zM>AjLI}IO&NaHhz0gr z)s{(p*BP}u&v7@0;^(?iES;9>isuo4kHp)(VoDppUrL`-yzWNKsqFX4vx8GMwHPZo zgTBk$p+edUt`?*u)E9BI)WO8aWr`&6@AkY2Yy>Hk403PjzLdro^slEs`V!lEKg~Sx zo(R(JGEuEX_VIDsZ{G(;H42l>t{c_+?NU7jooQxUy~g1Z0%TlC&V~);OaXgQO0iGL zen?RT={{d(m3cY{3Y*}S6&G9hRL5}88LaY5Uoh`)yU@%xHoE1hIiS|?`RK`V(le^=+gn`xE z4N$p{7sBiw(vYn%{JvA!OjtG?`J3j|dm;{1n5~*J(iaJN7TqmJZO={pg9jN`p%X44 zh?w_^=XC6h{0X7YTX7(3a*!?-@9I6ES=UI{abnyxnrKs z?()p0CSC>g^;gxFvj{uRt$zsvvsq7GGSg`B_+V#7)dayD~I%$Vrj{l;Yt43e$hm%r zJ}h7J%>cGJYHhYXU(#vYSqv&eWg}r9KF%9A@Ab=PD45#X&E_o5L?`93pzpak#8I2@8i&8G{3po)Qxg0Bl{6RstaP|09&sAtG!(si;$OWXb zC%c}nN&C$RAVUovk9-YCG~cj;wZFmJrF>bn<@uLdSOAE|vC`2S;Q)cflQw<#Fl2K*<5iWJIkrKaN*g|4as_ z5(4wmNVZo$kj8H$918+1d5fMA93Q_RZuRB)Kwuq6^BDp(I==P~ zjn&tH*nq601s+a%V%Kr8{nEdqjez?xDA84euhEiluM&}@r0}7f~#cfqI=WA{rR7fV)*?e ziY{S>Sh_g6TaG$AhcJd4_Uxf&-XI8JjEv}%(B<574jSX7yYm+BZ38|mS6_HsB&Sxi zauE!5`l@}v{y9URdy_1fEEa9sFegZt&}}omfnvQqD&Kq%TG}GZux2iQpO^fTKmv|l- z;VPLE;@o96WJTMBkB1NUpSO*Uj_x>P$@$u{wyPIc0;s8}sj4E1Rwj#8shuev&3?n? zRQEgT_E{IarBV(8$|1C8>e%-6^CLO}MNwi_R#s9uG;(uuopQhLo|2#g@!-GQ;L)rpY281%C>EZLFI=J!b*Rv`sMM5K2raO zRLzpoQe2QIN&3*HSj+GoOiWjL=q*q;Kk>M^nrTjbc$0tEB z-X|Q<5L$b{S*q+P|Di_|#<3ae&8qCFnw)recpVpO#3vd#(6R}qVaJBaos-L;ZW~bw zf{P$|AZp@Fnc5Dl3ml1{UEQCT1kDyu!wpY8$VGv;n3$NTXi6=!1r0pT&I*Y3!G9RZ zlbWz5h|sGvQo-t zR7TdDOY=&70Ok6F7Zw&|A3jVye$fmjG5|NoJp>~Bo1%QuxnYhWrrreDJJ*X-{9}qA zf%m^r3nZADur;zt9X_r>p#<{j9M9S-dX*@mEQFD}aStfjG*nU3{F&M<76lkYRS{Uc zSJcNrO^6s@k+I>8ove(^k+1cW2Sji-JHz4$h9vcb-mjvGlRLu<#f}YgPU0@&Ou1za zVh`vpN`1Pf&@0olmQlDV=ES@R+@c;}mF0aA5s@(lW)k#UD0jYYxk2TISIbY5hYOO; zm!S`&vx0}d+7^Do8Ql-Ztiy}eOepy0V%MAF;^HPICY;1`H90|HWzK+yCY)%9qJL&! zfxfUY@Z02Cc9*z-GuqZ?Cnog&-Bq<^V=74%2ZTAD=jm5t-I(6z@YY2&HMMS=x{8Ya z-P3tj$;fWS$xmJKe4rE}PuJM~mfzF~qEs1kJ@p(M91)A3^sMF56Vu`AeMxp0L^nwb z8%E$>P7PPVX^5pr9+4JOW@Zz>#9k?*?ze&~TGCno{J05o|l%LiYHgDCC+ zDm&u%NUTG-NKceu*gh!kE05aS_-+fLMS7c@V5=XUDtSYkh(r+4 z5mrbb+T!EMY25@7nxzKyCayt2a{J!j2pKP$uynCn6S<^Bsiq`5mOb~E9{<~0w7xfx z+^FgId{esRlbc=-G&t>Id1>N}@@01{$eHjHeMB4rJlTqsECT=ob-?$O#>MlsnEfDN z)?|Ff4Zqe!SIJ_-hx!4}neg5*+f)!|eZ%WFEF9)W0OclVY)~lTAOFP5;=jebfZ@SV z(@x^=#hlr>KXc%jLo7KJiza?c;z2H34U zS~-UO8keDM+~2Mq4#~nPWvuv`V5g)AuM=FD|A(uy1&`vNG||kZrKLcf-19T9`EK-R zN*ZBjnsx5aN4l~|;_o5+nMNTYJO(Rb&36?LnSz1>qvpe2%G>N*C^!CpsI_eUK0Euy z_N{V}C{vPdIrU`;7Z+E!V%LrjQ&OLtLs0Mf;gf7(x8;s}e*wB#?Cj;(%8LjUAut}( zQqkb-tckU?^{7X>2w)r???aXwA7P7TkoDXzVBdRwfJS6zWJD=HK#partP9X3+-ym8 z62_uo0%LVw%}Bme_*j{F{*hCU$ZCS%;p5{b+bKl>lFyK(W=~%6t$U=h+O)h%94UH{m6K4|C-} z$w>aeqWx7UU9MS;pG^L$XTW5cPO`%7#ntIKu;n3_gzLA;)H%?i!r4RXK;yz% zf|Jl2anu821(B1J1Em7s01^UUYuPSX_ZQPrI)pXD$xQtG7Q3-4plsXpn2&-2cUWUWXTP(pLrg6# zNA`mvS=y#WX>wS`y%?MrJG^U)Cn^{Kh4C}Ya+m}5kfhGT%?&{mJDb(OIA($*GP~gx zWl8({`*%N!lLM@=(M9$G6esUk%CyE`P-Do*$!q8LL?TioG-UGKJ^n$#=O+bXy?nLCMWU0Q?2#Gi@k-WM|W*=(Ov$~^Nl8iDda*!UzMe&tW!!(xX+xoQ*Lo8G3Tz((WpQu0v-9)k zteMhRuUc-p3(dlchb-}?p zg^ebHyjsKi=NukWjtxbXdZvl!k(*MWdXmdutjxVYZU#4|C3#-mDY>46488w;oWp?6 zRtBL{6(HM)b8ehZBny&9j%^PGJa=i7;Teb!92(7od0(CEs7{VaWddNV!tli#R2ud(1MPoE=koS(%cA=y3t+MsK=MdROH1#2 z1-G}iYb3L73X$c2zxs|}5rXg;@Q9XBh^6+|{(#co_s_+}PEaYI-22gToT|ZU+8Gke zMgf%n7Sd**9vmD5_;VA|IqhO;8})~ewcRgUaJIHBKTZGQbMBr_G(v>dF+tV8ux8J1v4&gr=yzTf0fHyi<1g) zAAs#KBt9UfDdhzd+HiP1dbQt7z|zt(!EhZIcwUUY3*5?iabaorBF2U^D6jx9Cn)F| zU^wc)FgtFx_hEv)*xbxN_#dvRf!MSRYG*iVT<1SI-`(CgHss9U(j!^LBqV$R)!{(~ zrTNS2?>PS4zZbuJ6u!ootoFuNe$Jq9iO@@F96BIqkWgM$mQ~&Nv_MYUd)Tj~Sy}Hh zuKhK8ZAV83&;p;oXmTYg-U1dWHDF9qZ(T&3rK&EpN$2Y(^uC{P;*Cc>NixxuSel;p z{XJW+$J?|2ji1GJo>Xp^`4EU@4=>p#rIOy~O0u^Ol5bRrkto9|i&Fo@#6%J@GT~cB z#5xlb)7k!Vt-;}rq76g;Wg{QkM?gl{$a0`DbTH4>#gNYXT39-ZV*!H70dS|rc@|kexwBcj#>U2QMaANYYBPilCza`F!96q@ z9ZPD-w{oCG3>qM{=I<>2$1^JOUp602G%Fqtt72IL4nHdfw9mND`Z3iUGQ zbzox2b>HDtZD zGT-$ZS_IbY={@{*4`71!C{dA9>$jH1TE=2!hScO-%tHTU{kT>Y#8*8QQPVrAF!*m5Yg~3 z%p@_xFrp$oUJ78I6>gZTHmLNPz5n~qpRLxc_4e222(WY-IsA<%0yp)IN|Bx+4Cel$ z8NT?%x3{+!IL2_5L{0x{6Bd5E=oJivFXVDF`_eBY+dd#5K#L>vl(M%%5Cb2u_ww=* z%hWDvG~MtDw92g4`*xWYbOvZ;$d&Wx5XVqE@UcX}lA59y++zZMi^@gSNmDzeb!27JpJhA?Zb8`b9P4}Y;L{xbYIdy`- zL<`{RavRxszVupLXTkNe96nt&tVH zTP2MXEegmGJ{h>#a~0bfvjZ$5P#IDXd^QK1lUU4I$EmKx@f1*8#f?Vt%9NO<=#~Qw zrMbB|kk>hC6wLvEPu@v;mo+v@7@xC`0xnsq9%g@}Q`FqdP}8z<{&&ysHC>*VP=QYe zr~m$vn~O_Tb@lK-aNHvxGzmDB-P^~R85w}bGBPtSZ*Jc~JzFPYo{nqM{c z)MAZfpjG-lHIq=G}h0&NHajGY>(R+?m9O-ILVrmiOT?$_J-{xDX|(NdM|^isq)?pvgfOp<~2*gI(z zil}ZI0=!5d&VcWyr>EtS9$b669v=H%X`|Vknc6HeK0kNLkyZl*BK0PT#>%-p5ft}q z>rH;`?gIICF)F(cu$d!M`|zgzc8$KX-zd;Rc;<9>#@N6c=m`$3KI=W#5+xyyMZ3AV z1ubJ<*0yJ%#jcOCJ1ycMUekS>kr6(ceDNO__L!yIe>A?NU!Eee0?6?dZlR)r)`k_2 z8Zl>+tIlt~#hs7(zUlR^gETe^W(r%i1)Tnx2CCg}U3xb#@pC$S zB?|T8hZF>cMn*N(ogE)Pe%n9KgO<_$xsU?b2^=+{y$}e5q@?5rRj{-m)SWULGH&ux zOG`^fXQVHQS%9Dahr7Q!X?=Hh_divl`&*nWFB5RUh87Ka3Y$R{ZJEX})~$m-2GV?&*=tOHWU| zsKDO;_bD;^<{b`=_7!n2b?zDPz?;iQ&BVvv*2h7{&dUL~fW$<_#Dqj22|biB6otq< zl9rJW6%Z8#yh#kNh5oM_+&t`^9RvUG8(s<09{@KPzcMuOF|hS#^Yrp?bar)M^9l5H zU~~5Lu>*ku-mM>z5Wr!)yn|1NhP5^gKsX%4!gj>e9Mn(JX>h37RKvv4_Bb33hHOK# nQ~f=|J>VYvp)k!tZDG(adP<)oWr<8+36PeWuIdM6n~47bq6=ZD literal 0 HcmV?d00001 diff --git a/public/browserconfig.xml b/public/browserconfig.xml new file mode 100644 index 0000000..b3930d0 --- /dev/null +++ b/public/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..788a7f12b42a2b1d9b7766676ff5e8e69cc19b89 GIT binary patch literal 1017 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstU$g(vPY0F z14ES>14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>rUEcBGBP$a zG&VOk3=Y&bGc(fHH%^SzTrx}A*u==v($LpKzrIxM`8m<~eM(jN>Y)L;lk3&buUGhY zUGndB$@SBf=XR)?ngR_m@^Uu3y-xlAX_fycl>VJi{Cq%p_jD}_GZPC7i@3P>MRWRo zJl^pC_3r-{3;%CZ`G3UY^MlRVS!o6a29}nV`T6C1m0cyavyN|XPG@_+vL zmYbWaudly-`}QwizWn>~@&D?$|A)HXfA|m{9&T=Ko}Hb&bLYFxWp>$m^> zyZa#{^&WfRv$SbI1&m4F?k;ZPyMDd}a@b2eeO=j~G4pe%n{lN1{RIlW^mK6yk+__k zkdTrjCMqgCT|itkH7zkUIpMhgD^sDTW@h060d;X^^A3GAWo`BK3_=eRAF!V6*4ENo zy<(MTclRg522+m}*%?`xuU{}tZD9GL@`abDx5wA#?3V*fRnNqxgQ9 zT|C^poZnwRUw#Ewe!RW;|Aqq#5>|9gxUk_v$B7j$X56soaHw%g3M%r-imGC}a^cLH zH*@an`O|dBX9C0K74tFz3q3O_;xFERr``ez<nb!jVMV;EJ?LWE=mPb z3`Pb;e` zn=64LMdW8!fJtL=TH1ybWTTusoB(JmRNf7d`&b)&72>O_cxpR|7dmG|7~+h*V~!j%9E2${uk9$mU5mg z4~xVI?LQ<}3oi*PQNTQ?lkn(!NVnW7xX*If`>I)HS4VPKq9cYnI@Fwbax<1|Hm^Q! z#9U%THnz_Zw~Xa>p%MiNtjOij!eOyi!s9KXNvWE#9J{H=!PTIXb&zX2-`Y&fYW^vD zf28MTHU;t}Vlu0J$YRc1|4B9>SyQ4pNRzGj2IGxE8oZl*{C7|Kf&O|ojuxOiG|OK; zO*|l}f=#odMpF%$c{RR64TxLjMQRNQ!lz?-9Y=>>%MG?pscpEY_xaq(#VcsKo6_mC z&$@>0#&;ZBaCkcR5S=zmRLD6}X3S5F>xIc1TQ|-x)ecOl2V`sqK}5^mHd;s5B^zF` zJ`c5(B)vWbBSM`@p4IkFFr@DngpNGtfOmZK?jK22u^KuBx+P=iL#o+4P-(l!=?D)A z5bxPVTsRJ9@}T#GJTsVO_Dy$+c(uyRvjGa~c%8o%VQpIZwpG5n$qujh*X^O+O76~y z>(|NMjpJE9ta#?rup;jQIN4L>;&-{M+3crwq}o!vNlxiawlFzFu=qR&dqL(LmF%SY zV5R5lQ%g41o^Vr@^NF-R;{3gdJ)Gtz0~p$)#P5U)PS5DDm-r2#^4;H zHr(-&nHqvn*gL*5Eh!u7o>pC*+1H!fI+VU^Xh)(>osN20ZE$uHa7gFiyb?BuSJC_R7QX;`RM$;p{&j;Ln){A&rR9$P!9Z-`bibn^qsIoq6`| zgSdF;64`l8`?0;e2V8}w2TwH|NEGAZU-Gx!$;`p6CKyLXe*ED?>{d9Q+qN=qP|J3< zhxnfUGuYqXiL{*ttUOR*Lfdms`v#+xW(X>pz+a_gUeJTkS!gFAT_>Pfr@d3(t%#RQd3q|#@ukeH?Ht$ zeH%w6lY0h=&uU_ncaVfofXJ5$4zEA~9$L);n z2Sr`JTrCobf)dL}InrKLX6vD8zmE;wGn6KTbV$Jsk7x$ti%CW=-_z4mzfmQO)JU0B zldX>B24QuBzUfNq=kZj$Y8+-#>{z{-bMWX1D?R%(=>;VT=E#bWIW5`LpYC$E|GD#B zf2g2+ba#f%Ibz%*1TLt(dps$ zuWgE3q#xUWPV}RPhtMOiVM!5E0G4P=OEdH-vy;|7Xbkq0E!NulI2w&bqy62OEB`}? ziw~zoUi$w8V+7{BlyE-H$B#}4Nkt|k#YfUEL?G#x5+aba1bP?%7b_RmR1^|@jg5!B zMtE+E6aYmjn};c@qtwYI2&g)ez_jWOhoU$>$PvNBU>~nfw@-eA=~_!N2g@3V=xccE QGN}Y`BYF~A@YE~+0>ge95&!@I literal 0 HcmV?d00001 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..34ba1b3d4ace82c0c28e6093993d5d3fe9a47d23 GIT binary patch literal 15086 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 ka>#WtcNG!P^Edoo) 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 literal 0 HcmV?d00001 diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/public/safari-pinned-tab.svg b/public/safari-pinned-tab.svg new file mode 100644 index 0000000..dc7ced3 --- /dev/null +++ b/public/safari-pinned-tab.svg @@ -0,0 +1,154 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 0000000..6264b89 --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1,20 @@ +{ + "start_url": "/", + "name": "Nitro", + "short_name": "Nitro", + "icons": [ + { + "src": "android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/src/App.scss b/src/App.scss new file mode 100644 index 0000000..2000ade --- /dev/null +++ b/src/App.scss @@ -0,0 +1,102 @@ +$toolbar-me-zindex: 90; +$chatinput-zindex: 80; +$toolbar-zindex: 70; +$rightside-zindex: 69; +$notification-center-zindex: 68; +$toolbar-memenu-zindex: 60; +$roomtools-zindex: 50; +$context-menu-zindex: 40; +$infostand-zindex: 30; +$quiz-zindex: 21; +$chat-zindex: 20; +$highscore-zindex: 19; + +$grid-bg-color: #cdd3d9; +$grid-border-color: $muted; +$grid-active-bg-color: #ececec; +$grid-active-border-color: $white; + +$toolbar-height: 55px; + +$achievement-width: 375px; +$achievement-height: 405px; + +$avatar-editor-width: 620px; +$avatar-editor-height: 374px; + +$catalog-width: 630px; +$catalog-height: 400px; + +$inventory-width: 528px; +$inventory-height: 320px; + +$navigator-width: 420px; +$navigator-height: 440px; + +$chat-input-style-selector-widget-width: 210px; +$chat-input-style-selector-widget-height: 200px; + +$user-profile-width: 470px; +$user-profile-height: 460px; + +$nitro-widget-custom-stack-height-width: 275px; +$nitro-widget-custom-stack-height-height: 220px; + +$nitro-widget-exchange-credit-width: 375px; +$nitro-widget-exchange-credit-height: 150px; + +$nitro-widget-crafting-width: 500px; +$nitro-widget-crafting-height: 300px; + +$chat-history-width: 300px; +$chat-history-height: 300px; + +$friends-list-width: 250px; +$friends-list-height: 300px; + +$help-width: 450px; +$help-height: 290px; + +$nitropedia-width: 400px; +$nitropedia-height: 400px; + +$messenger-width: 500px; +$messenger-height: 370px; + +$marketplace-post-offer-width: 430px; +$marketplace-post-offer-height: 250px; + +$camera-editor-width: 600px; +$camera-editor-height: 500px; + +$camera-checkout-width: 350px; + +$room-info-width: 325px; + +$nitro-group-creator-width: 383px; +$nitro-mod-tools-width: 175px; + +$nitro-group-manager-width: 390px; +$nitro-group-manager-height: 355px; + +$nitro-chooser-width: 200px; +$nitro-chooser-height: 200px; + +$nitro-doorbell-width: 300px; +$nitro-doorbell-height: 200px; + +$nitro-guide-tool-width: 250px; + +$nitro-floor-editor-width: 760px; +$nitro-floor-editor-height: 500px; + +$nitro-calendar-width: 850px; +$nitro-calendar-height: 400px; + +.nitro-app { + width: 100%; + height: 100%; +} + +@import './common'; +@import './components'; diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..1c8e72c --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,102 @@ +import { GetAssetManager, GetAvatarRenderManager, GetCommunication, GetConfiguration, GetLocalizationManager, GetRoomCameraWidgetManager, GetRoomEngine, GetRoomSessionManager, GetSessionDataManager, GetSoundManager, GetStage, GetTexturePool, GetTicker, HabboWebTools, LegacyExternalInterface, LoadGameUrlEvent, NitroLogger, NitroVersion, PrepareRenderer } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { GetUIVersion } from './api'; +import { Base } from './common'; +import { LoadingView } from './components/loading/LoadingView'; +import { MainView } from './components/main/MainView'; +import { useMessageEvent } from './hooks'; + +NitroVersion.UI_VERSION = GetUIVersion(); + +export const App: FC<{}> = props => +{ + const [ isReady, setIsReady ] = useState(false); + + useMessageEvent(LoadGameUrlEvent, event => + { + const parser = event.getParser(); + + if(!parser) return; + + LegacyExternalInterface.callGame('showGame', parser.url); + }); + + useEffect(() => + { + const prepare = async (width: number, height: number) => + { + try + { + if(!window.NitroConfig) throw new Error('NitroConfig is not defined!'); + + const renderer = await PrepareRenderer({ + width, + height, + autoDensity: true, + backgroundAlpha: 0, + preference: 'webgl', + resolution: window.devicePixelRatio + }); + + await GetConfiguration().init(); + + GetTicker().maxFPS = GetConfiguration().getValue('system.fps.max', 24); + NitroLogger.LOG_DEBUG = GetConfiguration().getValue('system.log.debug', true); + NitroLogger.LOG_WARN = GetConfiguration().getValue('system.log.warn', false); + NitroLogger.LOG_ERROR = GetConfiguration().getValue('system.log.error', false); + NitroLogger.LOG_EVENTS = GetConfiguration().getValue('system.log.events', false); + NitroLogger.LOG_PACKETS = GetConfiguration().getValue('system.log.packets', false); + + const assetUrls = GetConfiguration().getValue('preload.assets.urls').map(url => GetConfiguration().interpolate(url)) ?? []; + + await Promise.all( + [ + GetAssetManager().downloadAssets(assetUrls), + GetLocalizationManager().init(), + GetAvatarRenderManager().init(), + GetSoundManager().init(), + GetSessionDataManager().init(), + GetRoomSessionManager().init(), + GetRoomCameraWidgetManager().init() + ] + ); + + await GetRoomEngine().init(); + await GetCommunication().init(); + + // new GameMessageHandler(); + + if(LegacyExternalInterface.available) LegacyExternalInterface.call('legacyTrack', 'authentication', 'authok', []); + + HabboWebTools.sendHeartBeat(); + + setInterval(() => HabboWebTools.sendHeartBeat(), 10000); + + GetTicker().add(ticker => GetRoomEngine().update(ticker)); + GetTicker().add(ticker => renderer.render(GetStage())); + GetTicker().add(ticker => GetTexturePool().run()); + + setIsReady(true); + + // handle socket close + //canvas.addEventListener('webglcontextlost', () => instance.events.dispatchEvent(new NitroEvent(Nitro.WEBGL_CONTEXT_LOST))); + } + + catch(err) + { + NitroLogger.error(err); + } + } + + prepare(window.innerWidth, window.innerHeight); + }, []); + + return ( + + { !isReady && + } + { isReady && } + + + ); +} diff --git a/src/api/GetRendererVersion.ts b/src/api/GetRendererVersion.ts new file mode 100644 index 0000000..bb9e461 --- /dev/null +++ b/src/api/GetRendererVersion.ts @@ -0,0 +1,3 @@ +import { NitroVersion } from '@nitrots/nitro-renderer'; + +export const GetRendererVersion = () => NitroVersion.RENDERER_VERSION; diff --git a/src/api/GetUIVersion.ts b/src/api/GetUIVersion.ts new file mode 100644 index 0000000..bdbe922 --- /dev/null +++ b/src/api/GetUIVersion.ts @@ -0,0 +1 @@ +export const GetUIVersion = () => '2.2.0'; diff --git a/src/api/achievements/AchievementCategory.ts b/src/api/achievements/AchievementCategory.ts new file mode 100644 index 0000000..906d8da --- /dev/null +++ b/src/api/achievements/AchievementCategory.ts @@ -0,0 +1,40 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { AchievementUtilities } from './AchievementUtilities'; +import { IAchievementCategory } from './IAchievementCategory'; + +export class AchievementCategory implements IAchievementCategory +{ + private _code: string; + private _achievements: AchievementData[]; + + constructor(code: string) + { + this._code = code; + this._achievements = []; + } + + public getProgress(): number + { + return AchievementUtilities.getAchievementCategoryProgress(this); + } + + public getMaxProgress(): number + { + return AchievementUtilities.getAchievementCategoryMaxProgress(this); + } + + public get code(): string + { + return this._code; + } + + public get achievements(): AchievementData[] + { + return this._achievements; + } + + public set achievements(achievements: AchievementData[]) + { + this._achievements = achievements; + } +} diff --git a/src/api/achievements/AchievementUtilities.ts b/src/api/achievements/AchievementUtilities.ts new file mode 100644 index 0000000..55180e6 --- /dev/null +++ b/src/api/achievements/AchievementUtilities.ts @@ -0,0 +1,97 @@ +import { AchievementData, GetLocalizationManager } from '@nitrots/nitro-renderer'; +import { GetConfigurationValue } from '../nitro'; +import { IAchievementCategory } from './IAchievementCategory'; + +export class AchievementUtilities +{ + public static getAchievementBadgeCode(achievement: AchievementData): string + { + if(!achievement) return null; + + let badgeId = achievement.badgeId; + + if(!achievement.finalLevel) badgeId = GetLocalizationManager().getPreviousLevelBadgeId(badgeId); + + return badgeId; + } + + public static getAchievementCategoryImageUrl(category: IAchievementCategory, progress: number = null, icon: boolean = false): string + { + const imageUrl = GetConfigurationValue('achievements.images.url'); + + let imageName = icon ? 'achicon_' : 'achcategory_'; + + imageName += category.code; + + if(progress !== null) imageName += `_${ ((progress > 0) ? 'active' : 'inactive') }`; + + return imageUrl.replace('%image%', imageName); + } + + public static getAchievementCategoryMaxProgress(category: IAchievementCategory): number + { + if(!category) return 0; + + let progress = 0; + + for(const achievement of category.achievements) + { + progress += achievement.levelCount; + } + + return progress; + } + + public static getAchievementCategoryProgress(category: IAchievementCategory): number + { + if(!category) return 0; + + let progress = 0; + + for(const achievement of category.achievements) progress += (achievement.finalLevel ? achievement.level : (achievement.level - 1)); + + return progress; + } + + public static getAchievementCategoryTotalUnseen(category: IAchievementCategory): number + { + if(!category) return 0; + + let unseen = 0; + + for(const achievement of category.achievements) ((achievement.unseen > 0) && unseen++); + + return unseen; + } + + public static getAchievementHasStarted(achievement: AchievementData): boolean + { + if(!achievement) return false; + + if(achievement.finalLevel || ((achievement.level - 1) > 0)) return true; + + return false; + } + + public static getAchievementIsIgnored(achievement: AchievementData): boolean + { + if(!achievement) return false; + + const ignored = GetConfigurationValue('achievements.unseen.ignored'); + const value = achievement.badgeId.replace(/[0-9]/g, ''); + const index = ignored.indexOf(value); + + if(index >= 0) return true; + + return false; + } + + public static getAchievementLevel(achievement: AchievementData): number + { + if(!achievement) return 0; + + if(achievement.finalLevel) return achievement.level; + + return (achievement.level - 1); + } +} diff --git a/src/api/achievements/IAchievementCategory.ts b/src/api/achievements/IAchievementCategory.ts new file mode 100644 index 0000000..a049d46 --- /dev/null +++ b/src/api/achievements/IAchievementCategory.ts @@ -0,0 +1,7 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; + +export interface IAchievementCategory +{ + code: string; + achievements: AchievementData[]; +} diff --git a/src/api/achievements/index.ts b/src/api/achievements/index.ts new file mode 100644 index 0000000..a3d44b7 --- /dev/null +++ b/src/api/achievements/index.ts @@ -0,0 +1,3 @@ +export * from './AchievementCategory'; +export * from './AchievementUtilities'; +export * from './IAchievementCategory'; diff --git a/src/api/avatar/AvatarEditorAction.ts b/src/api/avatar/AvatarEditorAction.ts new file mode 100644 index 0000000..064d6df --- /dev/null +++ b/src/api/avatar/AvatarEditorAction.ts @@ -0,0 +1,7 @@ +export class AvatarEditorAction +{ + public static ACTION_SAVE: string = 'AEA_ACTION_SAVE'; + public static ACTION_CLEAR: string = 'AEA_ACTION_CLEAR'; + public static ACTION_RESET: string = 'AEA_ACTION_RESET'; + public static ACTION_RANDOMIZE: string = 'AEA_ACTION_RANDOMIZE'; +} diff --git a/src/api/avatar/AvatarEditorGridColorItem.ts b/src/api/avatar/AvatarEditorGridColorItem.ts new file mode 100644 index 0000000..dee3dae --- /dev/null +++ b/src/api/avatar/AvatarEditorGridColorItem.ts @@ -0,0 +1,65 @@ +import { ColorConverter, IPartColor } from '@nitrots/nitro-renderer'; + +export class AvatarEditorGridColorItem +{ + private _partColor: IPartColor; + private _isDisabled: boolean; + private _isHC: boolean; + private _isSelected: boolean; + private _notifier: () => void; + + constructor(partColor: IPartColor, isDisabled: boolean = false) + { + this._partColor = partColor; + this._isDisabled = isDisabled; + this._isHC = (this._partColor.clubLevel > 0); + this._isSelected = false; + } + + public dispose(): void + { + this._partColor = null; + } + + public get partColor(): IPartColor + { + return this._partColor; + } + + public get color(): string + { + return ColorConverter.int2rgb(this._partColor.rgb); + } + + public get isDisabled(): boolean + { + return this._isDisabled; + } + + public get isHC(): boolean + { + return this._isHC; + } + + public get isSelected(): boolean + { + return this._isSelected; + } + + public set isSelected(flag: boolean) + { + this._isSelected = flag; + + if(this.notify) this.notify(); + } + + public get notify(): () => void + { + return this._notifier; + } + + public set notify(notifier: () => void) + { + this._notifier = notifier; + } +} diff --git a/src/api/avatar/AvatarEditorGridPartItem.ts b/src/api/avatar/AvatarEditorGridPartItem.ts new file mode 100644 index 0000000..45e9472 --- /dev/null +++ b/src/api/avatar/AvatarEditorGridPartItem.ts @@ -0,0 +1,336 @@ +import { AvatarFigurePartType, GetAvatarRenderManager, IAvatarImageListener, IAvatarRenderManager, IFigurePart, IFigurePartSet, IGraphicAsset, IPartColor, NitroAlphaFilter, NitroContainer, NitroSprite, TextureUtils } from '@nitrots/nitro-renderer'; +import { FigureData } from './FigureData'; + +export class AvatarEditorGridPartItem implements IAvatarImageListener +{ + private static ALPHA_FILTER: NitroAlphaFilter = new NitroAlphaFilter({ alpha: 0.2 }); + private static THUMB_DIRECTIONS: number[] = [ 2, 6, 0, 4, 3, 1 ]; + private static DRAW_ORDER: string[] = [ + AvatarFigurePartType.LEFT_HAND_ITEM, + AvatarFigurePartType.LEFT_HAND, + AvatarFigurePartType.LEFT_SLEEVE, + AvatarFigurePartType.LEFT_COAT_SLEEVE, + AvatarFigurePartType.BODY, + AvatarFigurePartType.SHOES, + AvatarFigurePartType.LEGS, + AvatarFigurePartType.CHEST, + AvatarFigurePartType.CHEST_ACCESSORY, + AvatarFigurePartType.COAT_CHEST, + AvatarFigurePartType.CHEST_PRINT, + AvatarFigurePartType.WAIST_ACCESSORY, + AvatarFigurePartType.RIGHT_HAND, + AvatarFigurePartType.RIGHT_SLEEVE, + AvatarFigurePartType.RIGHT_COAT_SLEEVE, + AvatarFigurePartType.HEAD, + AvatarFigurePartType.FACE, + AvatarFigurePartType.EYES, + AvatarFigurePartType.HAIR, + AvatarFigurePartType.HAIR_BIG, + AvatarFigurePartType.FACE_ACCESSORY, + AvatarFigurePartType.EYE_ACCESSORY, + AvatarFigurePartType.HEAD_ACCESSORY, + AvatarFigurePartType.HEAD_ACCESSORY_EXTRA, + AvatarFigurePartType.RIGHT_HAND_ITEM, + ]; + + private _renderManager: IAvatarRenderManager; + private _partSet: IFigurePartSet; + private _partColors: IPartColor[]; + private _useColors: boolean; + private _isDisabled: boolean; + private _thumbContainer: NitroContainer; + private _imageUrl: string; + private _maxColorIndex: number; + private _isValidFigure: boolean; + private _isHC: boolean; + private _isSellable: boolean; + private _isClear: boolean; + private _isSelected: boolean; + private _disposed: boolean; + private _isInitalized: boolean; + private _notifier: () => void; + + constructor(partSet: IFigurePartSet, partColors: IPartColor[], useColors: boolean = true, isDisabled: boolean = false) + { + this._renderManager = GetAvatarRenderManager(); + this._partSet = partSet; + this._partColors = partColors; + this._useColors = useColors; + this._isDisabled = isDisabled; + this._thumbContainer = null; + this._imageUrl = null; + this._maxColorIndex = 0; + this._isValidFigure = false; + this._isHC = false; + this._isSellable = false; + this._isClear = false; + this._isSelected = false; + this._disposed = false; + this._isInitalized = false; + + if(partSet) + { + const colors = partSet.parts; + + for(const color of colors) this._maxColorIndex = Math.max(this._maxColorIndex, color.colorLayerIndex); + } + } + + public init(): void + { + if(this._isInitalized) return; + + this._isInitalized = true; + + this.update(); + } + + public dispose(): void + { + if(this._disposed) return; + + this._renderManager = null; + this._partSet = null; + this._partColors = null; + this._imageUrl = null; + this._disposed = true; + this._isInitalized = false; + + if(this._thumbContainer) + { + this._thumbContainer.destroy(); + + this._thumbContainer = null; + } + } + + public update(): void + { + this.updateThumbVisualization(); + } + + private analyzeFigure(): boolean + { + if(!this._renderManager || !this._partSet || !this._partSet.parts || !this._partSet.parts.length) return false; + + const figureContainer = this._renderManager.createFigureContainer(((this.partSet.type + '-') + this.partSet.id)); + + if(!this._renderManager.isFigureContainerReady(figureContainer)) + { + this._renderManager.downloadAvatarFigure(figureContainer, this); + + return false; + } + + this._isValidFigure = true; + + return true; + } + + private renderThumb(): NitroContainer + { + if(!this._renderManager || !this._partSet) return null; + + if(!this._isValidFigure) + { + if(!this.analyzeFigure()) return null; + } + + const parts = this._partSet.parts.concat().sort(this.sortByDrawOrder); + const container = new NitroContainer(); + + for(const part of parts) + { + if(!part) continue; + + let asset: IGraphicAsset = null; + let direction = 0; + let hasAsset = false; + + while(!hasAsset && (direction < AvatarEditorGridPartItem.THUMB_DIRECTIONS.length)) + { + const assetName = ((((((((((FigureData.SCALE + '_') + FigureData.STD) + '_') + part.type) + '_') + part.id) + '_') + AvatarEditorGridPartItem.THUMB_DIRECTIONS[direction]) + '_') + FigureData.DEFAULT_FRAME); + + asset = this._renderManager.getAssetByName(assetName); + + if(asset && asset.texture) + { + hasAsset = true; + } + else + { + direction++; + } + } + + if(!hasAsset) continue; + + const x = asset.offsetX; + const y = asset.offsetY; + let partColor: IPartColor = null; + + if(this._useColors && (part.colorLayerIndex > 0)) + { + const color = this._partColors[(part.colorLayerIndex - 1)]; + + if(color) partColor = color; + } + + const sprite = new NitroSprite(asset.texture); + + sprite.position.set(x, y); + + if(partColor) sprite.tint = partColor.rgb; + + container.addChild(sprite); + } + + return container; + } + + private async updateThumbVisualization(): Promise + { + if(!this._isInitalized) return; + + let container = this._thumbContainer; + + if(!container) container = this.renderThumb(); + + if(!container) return; + + if(this._partSet) + { + this._isHC = (this._partSet.clubLevel > 0); + this._isSellable = this._partSet.isSellable; + } + else + { + this._isHC = false; + this._isSellable = false; + } + + if(this._isDisabled) this.setAlpha(container, 0.2); + + this._imageUrl = await TextureUtils.generateImageUrl(container); + + if(this.notify) this.notify(); + } + + private setAlpha(container: NitroContainer, alpha: number): NitroContainer + { + container.filters = [ AvatarEditorGridPartItem.ALPHA_FILTER ]; + + return container; + } + + private sortByDrawOrder(a: IFigurePart, b: IFigurePart): number + { + const indexA = AvatarEditorGridPartItem.DRAW_ORDER.indexOf(a.type); + const indexB = AvatarEditorGridPartItem.DRAW_ORDER.indexOf(b.type); + + if(indexA < indexB) return -1; + + if(indexA > indexB) return 1; + + if(a.index < b.index) return -1; + + if(a.index > b.index) return 1; + + return 0; + } + + public resetFigure(figure: string): void + { + if(!this.analyzeFigure()) return; + + this.update(); + } + + public get disposed(): boolean + { + return this._disposed; + } + + public get id(): number + { + if(!this._partSet) return -1; + + return this._partSet.id; + } + + public get partSet(): IFigurePartSet + { + return this._partSet; + } + + public set partColors(partColors: IPartColor[]) + { + this._partColors = partColors; + + this.update(); + } + + public get isDisabled(): boolean + { + return this._isDisabled; + } + + public set thumbContainer(container: NitroContainer) + { + this._thumbContainer = container; + + this.update(); + } + + public get imageUrl(): string + { + return this._imageUrl; + } + + public get maxColorIndex(): number + { + return this._maxColorIndex; + } + + public get isHC(): boolean + { + return this._isHC; + } + + public get isSellable(): boolean + { + return this._isSellable; + } + + public get isClear(): boolean + { + return this._isClear; + } + + public set isClear(flag: boolean) + { + this._isClear = flag; + } + + public get isSelected(): boolean + { + return this._isSelected; + } + + public set isSelected(flag: boolean) + { + this._isSelected = flag; + + if(this.notify) this.notify(); + } + + public get notify(): () => void + { + return this._notifier; + } + + public set notify(notifier: () => void) + { + this._notifier = notifier; + } +} diff --git a/src/api/avatar/AvatarEditorThumbnailsHelper.ts b/src/api/avatar/AvatarEditorThumbnailsHelper.ts new file mode 100644 index 0000000..6be9b9b --- /dev/null +++ b/src/api/avatar/AvatarEditorThumbnailsHelper.ts @@ -0,0 +1,189 @@ +import { AvatarFigurePartType, AvatarScaleType, AvatarSetType, GetAssetManager, GetAvatarRenderManager, IFigurePart, IGraphicAsset, IPartColor, NitroAlphaFilter, NitroContainer, NitroSprite, TextureUtils } from '@nitrots/nitro-renderer'; +import { FigureData } from './FigureData'; +import { IAvatarEditorCategoryPartItem } from './IAvatarEditorCategoryPartItem'; + +export class AvatarEditorThumbnailsHelper +{ + private static THUMBNAIL_CACHE: Map = new Map(); + private static THUMB_DIRECTIONS: number[] = [ 2, 6, 0, 4, 3, 1 ]; + private static ALPHA_FILTER: NitroAlphaFilter = new NitroAlphaFilter({ alpha: 0.2 }); + private static DRAW_ORDER: string[] = [ + AvatarFigurePartType.LEFT_HAND_ITEM, + AvatarFigurePartType.LEFT_HAND, + AvatarFigurePartType.LEFT_SLEEVE, + AvatarFigurePartType.LEFT_COAT_SLEEVE, + AvatarFigurePartType.BODY, + AvatarFigurePartType.SHOES, + AvatarFigurePartType.LEGS, + AvatarFigurePartType.CHEST, + AvatarFigurePartType.CHEST_ACCESSORY, + AvatarFigurePartType.COAT_CHEST, + AvatarFigurePartType.CHEST_PRINT, + AvatarFigurePartType.WAIST_ACCESSORY, + AvatarFigurePartType.RIGHT_HAND, + AvatarFigurePartType.RIGHT_SLEEVE, + AvatarFigurePartType.RIGHT_COAT_SLEEVE, + AvatarFigurePartType.HEAD, + AvatarFigurePartType.FACE, + AvatarFigurePartType.EYES, + AvatarFigurePartType.HAIR, + AvatarFigurePartType.HAIR_BIG, + AvatarFigurePartType.FACE_ACCESSORY, + AvatarFigurePartType.EYE_ACCESSORY, + AvatarFigurePartType.HEAD_ACCESSORY, + AvatarFigurePartType.HEAD_ACCESSORY_EXTRA, + AvatarFigurePartType.RIGHT_HAND_ITEM, + ]; + + private static getThumbnailKey(setType: string, part: IAvatarEditorCategoryPartItem): string + { + return `${ setType }-${ part.partSet.id }`; + } + + public static clearCache(): void + { + this.THUMBNAIL_CACHE.clear(); + } + + public static async build(setType: string, part: IAvatarEditorCategoryPartItem, useColors: boolean, partColors: IPartColor[], isDisabled: boolean = false): Promise + { + if(!setType || !setType.length || !part || !part.partSet || !part.partSet.parts || !part.partSet.parts.length) return null; + + const thumbnailKey = this.getThumbnailKey(setType, part); + const cached = this.THUMBNAIL_CACHE.get(thumbnailKey); + + if(cached) return cached; + + const buildContainer = (part: IAvatarEditorCategoryPartItem, useColors: boolean, partColors: IPartColor[], isDisabled: boolean = false) => + { + const container = new NitroContainer(); + const parts = part.partSet.parts.concat().sort(this.sortByDrawOrder); + + for(const part of parts) + { + if(!part) continue; + + let asset: IGraphicAsset = null; + let direction = 0; + let hasAsset = false; + + while(!hasAsset && (direction < AvatarEditorThumbnailsHelper.THUMB_DIRECTIONS.length)) + { + const assetName = `${ FigureData.SCALE }_${ FigureData.STD }_${ part.type }_${ part.id }_${ AvatarEditorThumbnailsHelper.THUMB_DIRECTIONS[direction] }_${ FigureData.DEFAULT_FRAME }`; + + asset = GetAssetManager().getAsset(assetName); + + if(asset && asset.texture) + { + hasAsset = true; + } + else + { + direction++; + } + } + + if(!hasAsset) continue; + + const x = asset.offsetX; + const y = asset.offsetY; + + const sprite = new NitroSprite(asset.texture); + + sprite.position.set(x, y); + + if(useColors && (part.colorLayerIndex > 0) && partColors && partColors.length) + { + const color = partColors[(part.colorLayerIndex - 1)]; + + if(color) sprite.tint = color.rgb; + } + + if(isDisabled) container.filters = [ AvatarEditorThumbnailsHelper.ALPHA_FILTER ]; + + container.addChild(sprite); + } + + return container; + } + + return new Promise(async (resolve, reject) => + { + const resetFigure = async (figure: string) => + { + const container = buildContainer(part, useColors, partColors, isDisabled); + const imageUrl = await TextureUtils.generateImageUrl(container); + + AvatarEditorThumbnailsHelper.THUMBNAIL_CACHE.set(thumbnailKey, imageUrl); + + resolve(imageUrl); + } + + const figureContainer = GetAvatarRenderManager().createFigureContainer(`${ setType }-${ part.partSet.id }`); + + if(!GetAvatarRenderManager().isFigureContainerReady(figureContainer)) + { + GetAvatarRenderManager().downloadAvatarFigure(figureContainer, { + resetFigure, + dispose: null, + disposed: false + }); + } + else + { + resetFigure(null); + } + }); + } + + public static async buildForFace(figureString: string, isDisabled: boolean = false): Promise + { + if(!figureString || !figureString.length) return null; + + const thumbnailKey = figureString; + const cached = this.THUMBNAIL_CACHE.get(thumbnailKey); + + if(cached) return cached; + + return new Promise(async (resolve, reject) => + { + const resetFigure = async (figure: string) => + { + const avatarImage = GetAvatarRenderManager().createAvatarImage(figure, AvatarScaleType.LARGE, null, { resetFigure, dispose: null, disposed: false }); + const texture = avatarImage.processAsTexture(AvatarSetType.HEAD, false); + const sprite = new NitroSprite(texture); + + if(isDisabled) sprite.filters = [ AvatarEditorThumbnailsHelper.ALPHA_FILTER ]; + + const imageUrl = await TextureUtils.generateImageUrl({ + target: sprite + }); + + sprite.destroy(); + avatarImage.dispose(); + + if(!avatarImage.isPlaceholder()) AvatarEditorThumbnailsHelper.THUMBNAIL_CACHE.set(thumbnailKey, imageUrl); + + resolve(imageUrl); + } + + resetFigure(figureString); + }); + } + + private static sortByDrawOrder(a: IFigurePart, b: IFigurePart): number + { + const indexA = AvatarEditorThumbnailsHelper.DRAW_ORDER.indexOf(a.type); + const indexB = AvatarEditorThumbnailsHelper.DRAW_ORDER.indexOf(b.type); + + if(indexA < indexB) return -1; + + if(indexA > indexB) return 1; + + if(a.index < b.index) return -1; + + if(a.index > b.index) return 1; + + return 0; + } +} diff --git a/src/api/avatar/AvatarEditorUtilities.ts b/src/api/avatar/AvatarEditorUtilities.ts new file mode 100644 index 0000000..c7e2468 --- /dev/null +++ b/src/api/avatar/AvatarEditorUtilities.ts @@ -0,0 +1,277 @@ +import { GetAvatarRenderManager, IPartColor } from '@nitrots/nitro-renderer'; +import { GetClubMemberLevel, GetConfigurationValue } from '../nitro'; +import { AvatarEditorGridColorItem } from './AvatarEditorGridColorItem'; +import { AvatarEditorGridPartItem } from './AvatarEditorGridPartItem'; +import { CategoryBaseModel } from './CategoryBaseModel'; +import { CategoryData } from './CategoryData'; +import { FigureData } from './FigureData'; + +export class AvatarEditorUtilities +{ + private static MAX_PALETTES: number = 2; + + public static CURRENT_FIGURE: FigureData = null; + public static FIGURE_SET_IDS: number[] = []; + public static BOUND_FURNITURE_NAMES: string[] = []; + + public static getGender(gender: string): string + { + switch(gender) + { + case FigureData.MALE: + case 'm': + case 'M': + gender = FigureData.MALE; + break; + case FigureData.FEMALE: + case 'f': + case 'F': + gender = FigureData.FEMALE; + break; + default: + gender = FigureData.MALE; + } + + return gender; + } + + public static hasFigureSetId(setId: number): boolean + { + return (this.FIGURE_SET_IDS.indexOf(setId) >= 0); + } + + public static createCategory(model: CategoryBaseModel, name: string): CategoryData + { + if(!model || !name || !this.CURRENT_FIGURE) return null; + + const partItems: AvatarEditorGridPartItem[] = []; + const colorItems: AvatarEditorGridColorItem[][] = []; + + let i = 0; + + while(i < this.MAX_PALETTES) + { + colorItems.push([]); + + i++; + } + + const setType = GetAvatarRenderManager().structureData.getSetType(name); + + if(!setType) return null; + + const palette = GetAvatarRenderManager().structureData.getPalette(setType.paletteID); + + if(!palette) return null; + + let colorIds = this.CURRENT_FIGURE.getColorIds(name); + + if(!colorIds) colorIds = []; + + const partColors: IPartColor[] = new Array(colorIds.length); + const clubItemsDimmed = this.clubItemsDimmed; + const clubMemberLevel = GetClubMemberLevel(); + + for(const partColor of palette.colors.getValues()) + { + if(partColor.isSelectable && (clubItemsDimmed || (clubMemberLevel >= partColor.clubLevel))) + { + let i = 0; + + while(i < this.MAX_PALETTES) + { + const isDisabled = (clubMemberLevel < partColor.clubLevel); + const colorItem = new AvatarEditorGridColorItem(partColor, isDisabled); + + colorItems[i].push(colorItem); + + i++; + } + + if(name !== FigureData.FACE) + { + let i = 0; + + while(i < colorIds.length) + { + if(partColor.id === colorIds[i]) partColors[i] = partColor; + + i++; + } + } + } + } + + let mandatorySetIds: string[] = []; + + if(clubItemsDimmed) + { + mandatorySetIds = GetAvatarRenderManager().getMandatoryAvatarPartSetIds(this.CURRENT_FIGURE.gender, 2); + } + else + { + mandatorySetIds = GetAvatarRenderManager().getMandatoryAvatarPartSetIds(this.CURRENT_FIGURE.gender, clubMemberLevel); + } + + const isntMandatorySet = (mandatorySetIds.indexOf(name) === -1); + + if(isntMandatorySet) + { + const partItem = new AvatarEditorGridPartItem(null, null, false); + + partItem.isClear = true; + + partItems.push(partItem); + } + + const usesColors = (name !== FigureData.FACE); + const partSets = setType.partSets; + const totalPartSets = partSets.length; + + i = (totalPartSets - 1); + + while(i >= 0) + { + const partSet = partSets.getWithIndex(i); + + let isValidGender = false; + + if(partSet.gender === FigureData.UNISEX) + { + isValidGender = true; + } + + else if(partSet.gender === this.CURRENT_FIGURE.gender) + { + isValidGender = true; + } + + if(partSet.isSelectable && isValidGender && (clubItemsDimmed || (clubMemberLevel >= partSet.clubLevel))) + { + const isDisabled = (clubMemberLevel < partSet.clubLevel); + + let isValid = true; + + if(partSet.isSellable) isValid = this.hasFigureSetId(partSet.id); + + if(isValid) partItems.push(new AvatarEditorGridPartItem(partSet, partColors, usesColors, isDisabled)); + } + + i--; + } + + partItems.sort(this.clubItemsFirst ? this.clubSorter : this.noobSorter); + + // if(this._forceSellableClothingVisibility || GetNitroInstance().getConfiguration("avatareditor.support.sellablefurni", false)) + // { + // _local_31 = (this._manager.windowManager.assets.getAssetByName("camera_zoom_in") as BitmapDataAsset); + // _local_32 = (_local_31.content as BitmapData).clone(); + // _local_33 = (AvatarEditorView._Str_6802.clone() as IWindowContainer); + // _local_33.name = AvatarEditorGridView.GET_MORE; + // _local_7 = new AvatarEditorGridPartItem(_local_33, k, null, null, false); + // _local_7._Str_3093 = _local_32; + // _local_3.push(_local_7); + // } + + i = 0; + + while(i < this.MAX_PALETTES) + { + colorItems[i].sort(this.colorSorter); + + i++; + } + + return new CategoryData(name, partItems, colorItems); + } + + public static clubSorter(a: AvatarEditorGridPartItem, b: AvatarEditorGridPartItem): number + { + const clubLevelA = (!a.partSet ? 9999999999 : a.partSet.clubLevel); + const clubLevelB = (!b.partSet ? 9999999999 : b.partSet.clubLevel); + const isSellableA = (!a.partSet ? false : a.partSet.isSellable); + const isSellableB = (!b.partSet ? false : b.partSet.isSellable); + + if(isSellableA && !isSellableB) return 1; + + if(isSellableB && !isSellableA) return -1; + + if(clubLevelA > clubLevelB) return -1; + + if(clubLevelA < clubLevelB) return 1; + + if(a.partSet.id > b.partSet.id) return -1; + + if(a.partSet.id < b.partSet.id) return 1; + + return 0; + } + + public static colorSorter(a: AvatarEditorGridColorItem, b: AvatarEditorGridColorItem): number + { + const clubLevelA = (!a.partColor ? -1 : a.partColor.clubLevel); + const clubLevelB = (!b.partColor ? -1 : b.partColor.clubLevel); + + if(clubLevelA < clubLevelB) return -1; + + if(clubLevelA > clubLevelB) return 1; + + if(a.partColor.index < b.partColor.index) return -1; + + if(a.partColor.index > b.partColor.index) return 1; + + return 0; + } + + public static noobSorter(a: AvatarEditorGridPartItem, b: AvatarEditorGridPartItem): number + { + const clubLevelA = (!a.partSet ? -1 : a.partSet.clubLevel); + const clubLevelB = (!b.partSet ? -1 : b.partSet.clubLevel); + const isSellableA = (!a.partSet ? false : a.partSet.isSellable); + const isSellableB = (!b.partSet ? false : b.partSet.isSellable); + + if(isSellableA && !isSellableB) return 1; + + if(isSellableB && !isSellableA) return -1; + + if(clubLevelA < clubLevelB) return -1; + + if(clubLevelA > clubLevelB) return 1; + + if(a.partSet.id < b.partSet.id) return -1; + + if(a.partSet.id > b.partSet.id) return 1; + + return 0; + } + + public static avatarSetFirstSelectableColor(name: string): number + { + const setType = GetAvatarRenderManager().structureData.getSetType(name); + + if(!setType) return -1; + + const palette = GetAvatarRenderManager().structureData.getPalette(setType.paletteID); + + if(!palette) return -1; + + for(const color of palette.colors.getValues()) + { + if(!color.isSelectable || (GetClubMemberLevel() < color.clubLevel)) continue; + + return color.id; + } + + return -1; + } + + public static get clubItemsFirst(): boolean + { + return GetConfigurationValue('avatareditor.show.clubitems.first', true); + } + + public static get clubItemsDimmed(): boolean + { + return GetConfigurationValue('avatareditor.show.clubitems.dimmed', true); + } +} diff --git a/src/api/avatar/BodyModel.ts b/src/api/avatar/BodyModel.ts new file mode 100644 index 0000000..d2cf7e4 --- /dev/null +++ b/src/api/avatar/BodyModel.ts @@ -0,0 +1,75 @@ +import { AvatarEditorFigureCategory, AvatarScaleType, AvatarSetType, GetAvatarRenderManager } from '@nitrots/nitro-renderer'; +import { AvatarEditorUtilities } from './AvatarEditorUtilities'; +import { CategoryBaseModel } from './CategoryBaseModel'; +import { FigureData } from './FigureData'; + +export class BodyModel extends CategoryBaseModel +{ + private _imageCallBackHandled: boolean = false; + + public init(): void + { + super.init(); + + this.addCategory(FigureData.FACE); + + this._isInitalized = true; + } + + public selectColor(category: string, colorIndex: number, paletteId: number): void + { + super.selectColor(category, colorIndex, paletteId); + + this.updateSelectionsFromFigure(FigureData.FACE); + } + + protected updateSelectionsFromFigure(name: string): void + { + if(!this._categories || !AvatarEditorUtilities.CURRENT_FIGURE) return; + + const category = this._categories.get(name); + + if(!category) return; + + const setId = AvatarEditorUtilities.CURRENT_FIGURE.getPartSetId(name); + + let colorIds = AvatarEditorUtilities.CURRENT_FIGURE.getColorIds(name); + + if(!colorIds) colorIds = []; + + category.selectPartId(setId); + category.selectColorIds(colorIds); + + for(const part of category.parts) + { + const resetFigure = (figure: string) => + { + const figureString = AvatarEditorUtilities.CURRENT_FIGURE.getFigureStringWithFace(part.id); + const avatarImage = GetAvatarRenderManager().createAvatarImage(figureString, AvatarScaleType.LARGE, null, { resetFigure, dispose: null, disposed: false }); + + const sprite = avatarImage.processAsContainer(AvatarSetType.HEAD); + + if(sprite) + { + sprite.y = 10; + + part.thumbContainer = sprite; + + setTimeout(() => avatarImage.dispose(), 0); + } + } + + resetFigure(null); + } + } + + public get canSetGender(): boolean + { + return true; + } + + public get name(): string + { + return AvatarEditorFigureCategory.GENERIC; + } +} diff --git a/src/api/avatar/CategoryBaseModel.ts b/src/api/avatar/CategoryBaseModel.ts new file mode 100644 index 0000000..34dd933 --- /dev/null +++ b/src/api/avatar/CategoryBaseModel.ts @@ -0,0 +1,246 @@ +import { AvatarEditorUtilities } from './AvatarEditorUtilities'; +import { CategoryData } from './CategoryData'; +import { IAvatarEditorCategoryModel } from './IAvatarEditorCategoryModel'; + +export class CategoryBaseModel implements IAvatarEditorCategoryModel +{ + protected _categories: Map; + protected _isInitalized: boolean; + protected _maxPaletteCount: number; + private _disposed: boolean; + + constructor() + { + this._isInitalized = false; + this._maxPaletteCount = 0; + } + + public dispose(): void + { + this._categories = null; + this._disposed = true; + } + + public get disposed(): boolean + { + return this._disposed; + } + + public init(): void + { + if(!this._categories) this._categories = new Map(); + } + + public reset(): void + { + this._isInitalized = false; + + if(this._categories) + { + for(const category of this._categories.values()) (category && category.dispose()); + } + + this._categories = new Map(); + } + + protected addCategory(name: string): void + { + let existing = this._categories.get(name); + + if(existing) return; + + existing = AvatarEditorUtilities.createCategory(this, name); + + if(!existing) return; + + this._categories.set(name, existing); + + this.updateSelectionsFromFigure(name); + } + + protected updateSelectionsFromFigure(figure: string): void + { + const category = this._categories.get(figure); + + if(!category) return; + + const setId = AvatarEditorUtilities.CURRENT_FIGURE.getPartSetId(figure); + + let colorIds = AvatarEditorUtilities.CURRENT_FIGURE.getColorIds(figure); + + if(!colorIds) colorIds = []; + + category.selectPartId(setId); + category.selectColorIds(colorIds); + } + + public hasClubSelectionsOverLevel(level: number): boolean + { + if(!this._categories) return false; + + for(const category of this._categories.values()) + { + if(!category) continue; + + if(category.hasClubSelectionsOverLevel(level)) return true; + } + + return false; + } + + public hasInvalidSelectedItems(ownedItems: number[]): boolean + { + if(!this._categories) return false; + + for(const category of this._categories.values()) + { + if(category.hasInvalidSelectedItems(ownedItems)) return true; + } + + return false; + } + + public stripClubItemsOverLevel(level: number): boolean + { + if(!this._categories) return false; + + let didStrip = false; + + for(const [ name, category ] of this._categories.entries()) + { + let isValid = false; + + if(category.stripClubItemsOverLevel(level)) isValid = true; + + if(category.stripClubColorsOverLevel(level)) isValid = true; + + if(isValid) + { + const partItem = category.getCurrentPart(); + + if(partItem && AvatarEditorUtilities.CURRENT_FIGURE) + { + AvatarEditorUtilities.CURRENT_FIGURE.savePartData(name, partItem.id, category.getSelectedColorIds(), true); + } + + didStrip = true; + } + } + + return didStrip; + } + + public stripInvalidSellableItems(): boolean + { + if(!this._categories) return false; + + let didStrip = false; + + for(const [ name, category ] of this._categories.entries()) + { + const isValid = false; + + // if(category._Str_8360(this._Str_2278.manager.inventory)) _local_6 = true; + + if(isValid) + { + const partItem = category.getCurrentPart(); + + if(partItem && AvatarEditorUtilities.CURRENT_FIGURE) + { + AvatarEditorUtilities.CURRENT_FIGURE.savePartData(name, partItem.id, category.getSelectedColorIds(), true); + } + + didStrip = true; + } + } + + return didStrip; + } + + public selectPart(category: string, partIndex: number): void + { + const categoryData = this._categories.get(category); + + if(!categoryData) return; + + const selectedPartIndex = categoryData.selectedPartIndex; + + categoryData.selectPartIndex(partIndex); + + const partItem = categoryData.getCurrentPart(); + + if(!partItem) return; + + if(partItem.isDisabled) + { + categoryData.selectPartIndex(selectedPartIndex); + + // open hc window + + return; + } + + this._maxPaletteCount = partItem.maxColorIndex; + + AvatarEditorUtilities.CURRENT_FIGURE.savePartData(category, partItem.id, categoryData.getSelectedColorIds(), true); + } + + public selectColor(category: string, colorIndex: number, paletteId: number): void + { + const categoryData = this._categories.get(category); + + if(!categoryData) return; + + const paletteIndex = categoryData.getCurrentColorIndex(paletteId); + + categoryData.selectColorIndex(colorIndex, paletteId); + + const colorItem = categoryData.getSelectedColor(paletteId); + + if(colorItem.isDisabled) + { + categoryData.selectColorIndex(paletteIndex, paletteId); + + // open hc window + + return; + } + + AvatarEditorUtilities.CURRENT_FIGURE.savePartSetColourId(category, categoryData.getSelectedColorIds(), true); + } + + public getCategoryData(category: string): CategoryData + { + if(!this._isInitalized) this.init(); + + if(!this._categories) return null; + + return this._categories.get(category); + } + + public get categories(): Map + { + return this._categories; + } + + public get canSetGender(): boolean + { + return false; + } + + public get maxPaletteCount(): number + { + return (this._maxPaletteCount || 1); + } + + public set maxPaletteCount(count: number) + { + this._maxPaletteCount = count; + } + + public get name(): string + { + return null; + } +} diff --git a/src/api/avatar/CategoryData.ts b/src/api/avatar/CategoryData.ts new file mode 100644 index 0000000..db82f01 --- /dev/null +++ b/src/api/avatar/CategoryData.ts @@ -0,0 +1,487 @@ +import { IPartColor } from '@nitrots/nitro-renderer'; +import { AvatarEditorGridColorItem } from './AvatarEditorGridColorItem'; +import { AvatarEditorGridPartItem } from './AvatarEditorGridPartItem'; + +export class CategoryData +{ + private _name: string; + private _parts: AvatarEditorGridPartItem[]; + private _palettes: AvatarEditorGridColorItem[][]; + private _selectedPartIndex: number = -1; + private _paletteIndexes: number[]; + + constructor(name: string, partItems: AvatarEditorGridPartItem[], colorItems: AvatarEditorGridColorItem[][]) + { + this._name = name; + this._parts = partItems; + this._palettes = colorItems; + this._selectedPartIndex = -1; + } + + private static defaultColorId(palettes: AvatarEditorGridColorItem[], clubLevel: number): number + { + if(!palettes || !palettes.length) return -1; + + let i = 0; + + while(i < palettes.length) + { + const colorItem = palettes[i]; + + if(colorItem.partColor && (colorItem.partColor.clubLevel <= clubLevel)) + { + return colorItem.partColor.id; + } + + i++; + } + + return -1; + } + + public init(): void + { + for(const part of this._parts) + { + if(!part) continue; + + part.init(); + } + } + + public dispose(): void + { + if(this._parts) + { + for(const part of this._parts) part.dispose(); + + this._parts = null; + } + + if(this._palettes) + { + for(const palette of this._palettes) for(const colorItem of palette) colorItem.dispose(); + + this._palettes = null; + } + + this._selectedPartIndex = -1; + this._paletteIndexes = null; + } + + public selectPartId(partId: number): void + { + if(!this._parts) return; + + let i = 0; + + while(i < this._parts.length) + { + const partItem = this._parts[i]; + + if(partItem.id === partId) + { + this.selectPartIndex(i); + + return; + } + + i++; + } + } + + public selectColorIds(colorIds: number[]): void + { + if(!colorIds || !this._palettes) return; + + this._paletteIndexes = new Array(colorIds.length); + + let i = 0; + + while(i < this._palettes.length) + { + const palette = this.getPalette(i); + + if(palette) + { + let colorId = 0; + + if(colorIds.length > i) + { + colorId = colorIds[i]; + } + else + { + const colorItem = palette[0]; + + if(colorItem && colorItem.partColor) colorId = colorItem.partColor.id; + } + + let j = 0; + + while(j < palette.length) + { + const colorItem = palette[j]; + + if(colorItem.partColor.id === colorId) + { + this._paletteIndexes[i] = j; + + colorItem.isSelected = true; + } + else + { + colorItem.isSelected = false; + } + + j++; + } + } + + i++; + } + + this.updatePartColors(); + } + + public selectPartIndex(partIndex: number): AvatarEditorGridPartItem + { + if(!this._parts) return null; + + if((this._selectedPartIndex >= 0) && (this._parts.length > this._selectedPartIndex)) + { + const partItem = this._parts[this._selectedPartIndex]; + + if(partItem) partItem.isSelected = false; + } + + if(this._parts.length > partIndex) + { + const partItem = this._parts[partIndex]; + + if(partItem) + { + partItem.isSelected = true; + + this._selectedPartIndex = partIndex; + + return partItem; + } + } + + return null; + } + + public selectColorIndex(colorIndex: number, paletteId: number): AvatarEditorGridColorItem + { + const palette = this.getPalette(paletteId); + + if(!palette) return null; + + if(palette.length <= colorIndex) return null; + + this.deselectColorIndex(this._paletteIndexes[paletteId], paletteId); + + this._paletteIndexes[paletteId] = colorIndex; + + const colorItem = palette[colorIndex]; + + if(!colorItem) return null; + + colorItem.isSelected = true; + + this.updatePartColors(); + + return colorItem; + } + + public getCurrentColorIndex(k: number): number + { + return this._paletteIndexes[k]; + } + + private deselectColorIndex(colorIndex: number, paletteIndex: number): void + { + const palette = this.getPalette(paletteIndex); + + if(!palette) return; + + if(palette.length <= colorIndex) return; + + const colorItem = palette[colorIndex]; + + if(!colorItem) return; + + colorItem.isSelected = false; + } + + public getSelectedColorIds(): number[] + { + if(!this._paletteIndexes || !this._paletteIndexes.length) return null; + + if(!this._palettes || !this._palettes.length) return null; + + const palette = this._palettes[0]; + + if(!palette || (!palette.length)) return null; + + const colorItem = palette[0]; + + if(!colorItem || !colorItem.partColor) return null; + + const colorId = colorItem.partColor.id; + const colorIds: number[] = []; + + let i = 0; + + while(i < this._paletteIndexes.length) + { + const paletteSet = this._palettes[i]; + + if(!((!(paletteSet)) || (paletteSet.length <= i))) + { + if(paletteSet.length > this._paletteIndexes[i]) + { + const color = paletteSet[this._paletteIndexes[i]]; + + if(color && color.partColor) + { + colorIds.push(color.partColor.id); + } + else + { + colorIds.push(colorId); + } + } + else + { + colorIds.push(colorId); + } + } + + i++; + } + + const partItem = this.getCurrentPart(); + + if(!partItem) return null; + + return colorIds.slice(0, Math.max(partItem.maxColorIndex, 1)); + } + + private getSelectedColors(): IPartColor[] + { + const partColors: IPartColor[] = []; + + let i = 0; + + while(i < this._paletteIndexes.length) + { + const colorItem = this.getSelectedColor(i); + + if(colorItem) + { + partColors.push(colorItem.partColor); + } + else + { + partColors.push(null); + } + + i++; + } + + return partColors; + } + + public getSelectedColor(paletteId: number): AvatarEditorGridColorItem + { + const palette = this.getPalette(paletteId); + + if(!palette || (palette.length <= this._paletteIndexes[paletteId])) return null; + + return palette[this._paletteIndexes[paletteId]]; + } + + public getSelectedColorId(paletteId: number): number + { + const colorItem = this.getSelectedColor(paletteId); + + if(colorItem && (colorItem.partColor)) return colorItem.partColor.id; + + return 0; + } + + public getPalette(paletteId: number): AvatarEditorGridColorItem[] + { + if(!this._paletteIndexes || !this._palettes || (this._palettes.length <= paletteId)) + { + return null; + } + + return this._palettes[paletteId]; + } + + public getCurrentPart(): AvatarEditorGridPartItem + { + return this._parts[this._selectedPartIndex] as AvatarEditorGridPartItem; + } + + private updatePartColors(): void + { + const partColors = this.getSelectedColors(); + + for(const partItem of this._parts) + { + if(partItem) partItem.partColors = partColors; + } + } + + public hasClubSelectionsOverLevel(level: number): boolean + { + let hasInvalidSelections = false; + + const partColors = this.getSelectedColors(); + + if(partColors) + { + let i = 0; + + while(i < partColors.length) + { + const partColor = partColors[i]; + + if(partColor && (partColor.clubLevel > level)) hasInvalidSelections = true; + + i++; + } + } + + const partItem = this.getCurrentPart(); + + if(partItem && partItem.partSet) + { + const partSet = partItem.partSet; + + if(partSet && (partSet.clubLevel > level)) hasInvalidSelections = true; + } + + return hasInvalidSelections; + } + + public hasInvalidSelectedItems(ownedItems: number[]): boolean + { + const part = this.getCurrentPart(); + + if(!part) return false; + + const partSet = part.partSet; + + if(!partSet || !partSet.isSellable) return; + + return (ownedItems.indexOf(partSet.id) > -1); + } + + public stripClubItemsOverLevel(level: number): boolean + { + const partItem = this.getCurrentPart(); + + if(partItem && partItem.partSet) + { + const partSet = partItem.partSet; + + if(partSet.clubLevel > level) + { + const newPartItem = this.selectPartIndex(0); + + if(newPartItem && !newPartItem.partSet) this.selectPartIndex(1); + + return true; + } + } + + return false; + } + + public stripClubColorsOverLevel(level: number): boolean + { + const colorIds: number[] = []; + const partColors = this.getSelectedColors(); + const colorItems = this.getPalette(0); + + let didStrip = false; + + const colorId = CategoryData.defaultColorId(colorItems, level); + + if(colorId === -1) return false; + + let i = 0; + + while(i < partColors.length) + { + const partColor = partColors[i]; + + if(!partColor) + { + colorIds.push(colorId); + + didStrip = true; + } + else + { + if(partColor.clubLevel > level) + { + colorIds.push(colorId); + + didStrip = true; + } + else + { + colorIds.push(partColor.id); + } + } + + i++; + } + + if(didStrip) this.selectColorIds(colorIds); + + return didStrip; + } + + // public stripInvalidSellableItems(k:IHabboInventory): boolean + // { + // var _local_3:IFigurePartSet; + // var _local_4:AvatarEditorGridPartItem; + // var _local_2:AvatarEditorGridPartItem = this._Str_6315(); + // if (((_local_2) && (_local_2.partSet))) + // { + // _local_3 = _local_2.partSet; + // if (((_local_3.isSellable) && (!(k._Str_14439(_local_3.id))))) + // { + // _local_4 = this._Str_8066(0); + // if (((!(_local_4 == null)) && (_local_4.partSet == null))) + // { + // this._Str_8066(1); + // } + // return true; + // } + // } + // return false; + // } + + public get name(): string + { + return this._name; + } + + public get parts(): AvatarEditorGridPartItem[] + { + return this._parts; + } + + public get selectedPartIndex(): number + { + return this._selectedPartIndex; + } +} diff --git a/src/api/avatar/FigureData.ts b/src/api/avatar/FigureData.ts new file mode 100644 index 0000000..78014d1 --- /dev/null +++ b/src/api/avatar/FigureData.ts @@ -0,0 +1,287 @@ +import { AvatarEditorUtilities } from './AvatarEditorUtilities'; + +export class FigureData +{ + private static DEFAULT_DIRECTION: number = 4; + + public static MALE: string = 'M'; + public static FEMALE: string = 'F'; + public static UNISEX: string = 'U'; + public static SCALE: string = 'h'; + public static STD: string = 'std'; + public static DEFAULT_FRAME: string = '0'; + public static FACE: string = 'hd'; + public static HAIR: string = 'hr'; + public static HAT: string = 'ha'; + public static HEAD_ACCESSORIES: string = 'he'; + public static EYE_ACCESSORIES: string = 'ea'; + public static FACE_ACCESSORIES: string = 'fa'; + public static JACKET: string = 'cc'; + public static SHIRT: string = 'ch'; + public static CHEST_ACCESSORIES: string = 'ca'; + public static CHEST_PRINTS: string = 'cp'; + public static TROUSERS: string = 'lg'; + public static SHOES: string = 'sh'; + public static TROUSER_ACCESSORIES: string = 'wa'; + public static SET_TYPES = [ FigureData.FACE, FigureData.HAIR, FigureData.HAT, FigureData.HEAD_ACCESSORIES, FigureData.EYE_ACCESSORIES, FigureData.FACE_ACCESSORIES, FigureData.JACKET, FigureData.SHIRT, FigureData.CHEST_ACCESSORIES, FigureData.CHEST_PRINTS, FigureData.TROUSERS, FigureData.SHOES, FigureData.TROUSERS ]; + + private _data: Map; + private _colors: Map; + private _gender: string = 'M'; + private _direction: number = FigureData.DEFAULT_DIRECTION; + private _avatarEffectType: number = -1; + private _notifier: () => void = null; + + public loadAvatarData(figureString: string, gender: string): void + { + this._data = new Map(); + this._colors = new Map(); + this._gender = gender; + + this.parseFigureString(figureString); + this.updateView(); + } + + private parseFigureString(figure: string): void + { + if(!figure) return; + + const sets = figure.split('.'); + + if(!sets || !sets.length) return; + + for(const set of sets) + { + const parts = set.split('-'); + + if(!parts.length) continue; + + const setType = parts[0]; + const setId = parseInt(parts[1]); + const colorIds: number[] = []; + + let offset = 2; + + while(offset < parts.length) + { + colorIds.push(parseInt(parts[offset])); + + offset++; + } + + if(!colorIds.length) colorIds.push(0); + + this.savePartSetId(setType, setId, false); + this.savePartSetColourId(setType, colorIds, false); + } + } + + public getPartSetId(setType: string): number + { + const existing = this._data.get(setType); + + if(existing !== undefined) return existing; + + return -1; + } + + public getColorIds(setType: string): number[] + { + const existing = this._colors.get(setType); + + if(existing !== undefined) return existing; + + return [ AvatarEditorUtilities.avatarSetFirstSelectableColor(setType) ]; + } + + public getFigureString(): string + { + let figureString = ''; + const setParts: string[] = []; + + for(const [ setType, setId ] of this._data.entries()) + { + const colorIds = this._colors.get(setType); + + let setPart = ((setType + '-') + setId); + + if(colorIds && colorIds.length) + { + let i = 0; + + while(i < colorIds.length) + { + setPart = (setPart + ('-' + colorIds[i])); + + i++; + } + } + + setParts.push(setPart); + } + + let i = 0; + + while(i < setParts.length) + { + figureString = (figureString + setParts[i]); + + if(i < (setParts.length - 1)) figureString = (figureString + '.'); + + i++; + } + + return figureString; + } + + public savePartData(setType: string, partId: number, colorIds: number[], update: boolean = false): void + { + this.savePartSetId(setType, partId, update); + this.savePartSetColourId(setType, colorIds, update); + } + + private savePartSetId(setType: string, partId: number, update: boolean = true): void + { + switch(setType) + { + case FigureData.FACE: + case FigureData.HAIR: + case FigureData.HAT: + case FigureData.HEAD_ACCESSORIES: + case FigureData.EYE_ACCESSORIES: + case FigureData.FACE_ACCESSORIES: + case FigureData.SHIRT: + case FigureData.JACKET: + case FigureData.CHEST_ACCESSORIES: + case FigureData.CHEST_PRINTS: + case FigureData.TROUSERS: + case FigureData.SHOES: + case FigureData.TROUSER_ACCESSORIES: + if(partId >= 0) + { + this._data.set(setType, partId); + } + else + { + this._data.delete(setType); + } + break; + } + + if(update) this.updateView(); + } + + public savePartSetColourId(setType: string, colorIds: number[], update: boolean = true): void + { + switch(setType) + { + case FigureData.FACE: + case FigureData.HAIR: + case FigureData.HAT: + case FigureData.HEAD_ACCESSORIES: + case FigureData.EYE_ACCESSORIES: + case FigureData.FACE_ACCESSORIES: + case FigureData.SHIRT: + case FigureData.JACKET: + case FigureData.CHEST_ACCESSORIES: + case FigureData.CHEST_PRINTS: + case FigureData.TROUSERS: + case FigureData.SHOES: + case FigureData.TROUSER_ACCESSORIES: + this._colors.set(setType, colorIds); + break; + } + + if(update) this.updateView(); + } + + public getFigureStringWithFace(k: number, override = true): string + { + let figureString = ''; + + const setTypes: string[] = [ FigureData.FACE ]; + const figureSets: string[] = []; + + for(const setType of setTypes) + { + const colors = this._colors.get(setType); + + if(!colors) continue; + + let setId = this._data.get(setType); + + if((setType === FigureData.FACE) && override) setId = k; + + let figureSet = ((setType + '-') + setId); + + if(setId >= 0) + { + let i = 0; + + while(i < colors.length) + { + figureSet = (figureSet + ('-' + colors[i])); + + i++; + } + } + + figureSets.push(figureSet); + } + + let i = 0; + + while(i < figureSets.length) + { + figureString = (figureString + figureSets[i]); + + if(i < (figureSets.length - 1)) figureString = (figureString + '.'); + + i++; + } + + return figureString; + } + + public updateView(): void + { + if(this.notify) this.notify(); + } + + public get gender(): string + { + return this._gender; + } + + public get direction(): number + { + return this._direction; + } + + public set direction(direction: number) + { + this._direction = direction; + + this.updateView(); + } + + public set avatarEffectType(k: number) + { + this._avatarEffectType = k; + } + + public get avatarEffectType(): number + { + return this._avatarEffectType; + } + + public get notify(): () => void + { + return this._notifier; + } + + public set notify(notifier: () => void) + { + this._notifier = notifier; + } +} diff --git a/src/api/avatar/FigureGenerator.ts b/src/api/avatar/FigureGenerator.ts new file mode 100644 index 0000000..6fc0093 --- /dev/null +++ b/src/api/avatar/FigureGenerator.ts @@ -0,0 +1,89 @@ +import { AvatarFigureContainer, GetAvatarRenderManager, IFigurePartSet, IPalette, IPartColor, SetType } from '@nitrots/nitro-renderer'; +import { Randomizer } from '../utils'; +import { FigureData } from './FigureData'; + +function getTotalColors(partSet: IFigurePartSet): number +{ + const parts = partSet.parts; + + let totalColors = 0; + + for(const part of parts) totalColors = Math.max(totalColors, part.colorLayerIndex); + + return totalColors; +} + +function getRandomSetTypes(requiredSets: string[], options: string[]): string[] +{ + options = options.filter(option => (requiredSets.indexOf(option) === -1)); + + return [ ...requiredSets, ...Randomizer.getRandomElements(options, (Randomizer.getRandomNumber(options.length) + 1)) ]; +} + +function getRandomPartSet(setType: SetType, gender: string, clubLevel: number = 0, figureSetIds: number[] = []): IFigurePartSet +{ + if(!setType) return null; + + const options = setType.partSets.getValues().filter(option => + { + if(!option.isSelectable || ((option.gender !== 'U') && (option.gender !== gender)) || (option.clubLevel > clubLevel) || (option.isSellable && (figureSetIds.indexOf(option.id) === -1))) return null; + + return option; + }); + + if(!options || !options.length) return null; + + return Randomizer.getRandomElement(options); +} + +function getRandomColors(palette: IPalette, partSet: IFigurePartSet, clubLevel: number = 0): IPartColor[] +{ + if(!palette) return []; + + const options = palette.colors.getValues().filter(option => + { + if(!option.isSelectable || (option.clubLevel > clubLevel)) return null; + + return option; + }); + + if(!options || !options.length) return null; + + return Randomizer.getRandomElements(options, getTotalColors(partSet)); +} + +export function generateRandomFigure(figureData: FigureData, gender: string, clubLevel: number = 0, figureSetIds: number[] = [], ignoredSets: string[] = []): string +{ + const structure = GetAvatarRenderManager().structure; + const figureContainer = new AvatarFigureContainer(''); + const requiredSets = getRandomSetTypes(structure.getMandatorySetTypeIds(gender, clubLevel), FigureData.SET_TYPES); + + for(const setType of ignoredSets) + { + const partSetId = figureData.getPartSetId(setType); + const colors = figureData.getColorIds(setType); + + figureContainer.updatePart(setType, partSetId, colors); + } + + for(const type of requiredSets) + { + if(figureContainer.hasPartType(type)) continue; + + const setType = (structure.figureData.getSetType(type) as SetType); + const selectedSet = getRandomPartSet(setType, gender, clubLevel, figureSetIds); + + if(!selectedSet) continue; + + let selectedColors: number[] = []; + + if(selectedSet.isColorable) + { + selectedColors = getRandomColors(structure.figureData.getPalette(setType.paletteID), selectedSet, clubLevel).map(color => color.id); + } + + figureContainer.updatePart(setType.type, selectedSet.id, selectedColors); + } + + return figureContainer.getFigureString(); +} diff --git a/src/api/avatar/HeadModel.ts b/src/api/avatar/HeadModel.ts new file mode 100644 index 0000000..5dc30cd --- /dev/null +++ b/src/api/avatar/HeadModel.ts @@ -0,0 +1,24 @@ +import { AvatarEditorFigureCategory } from '@nitrots/nitro-renderer'; +import { CategoryBaseModel } from './CategoryBaseModel'; +import { FigureData } from './FigureData'; + +export class HeadModel extends CategoryBaseModel +{ + public init(): void + { + super.init(); + + this.addCategory(FigureData.HAIR); + this.addCategory(FigureData.HAT); + this.addCategory(FigureData.HEAD_ACCESSORIES); + this.addCategory(FigureData.EYE_ACCESSORIES); + this.addCategory(FigureData.FACE_ACCESSORIES); + + this._isInitalized = true; + } + + public get name(): string + { + return AvatarEditorFigureCategory.HEAD; + } +} diff --git a/src/api/avatar/IAvatarEditorCategory.ts b/src/api/avatar/IAvatarEditorCategory.ts new file mode 100644 index 0000000..a7cfd51 --- /dev/null +++ b/src/api/avatar/IAvatarEditorCategory.ts @@ -0,0 +1,9 @@ +import { IPartColor } from '@nitrots/nitro-renderer'; +import { IAvatarEditorCategoryPartItem } from './IAvatarEditorCategoryPartItem'; + +export interface IAvatarEditorCategory +{ + setType: string; + partItems: IAvatarEditorCategoryPartItem[]; + colorItems: IPartColor[][]; +} diff --git a/src/api/avatar/IAvatarEditorCategoryModel.ts b/src/api/avatar/IAvatarEditorCategoryModel.ts new file mode 100644 index 0000000..dc9affa --- /dev/null +++ b/src/api/avatar/IAvatarEditorCategoryModel.ts @@ -0,0 +1,19 @@ +import { CategoryData } from './CategoryData'; + +export interface IAvatarEditorCategoryModel +{ + init(): void; + dispose(): void; + reset(): void; + getCategoryData(category: string): CategoryData; + selectPart(category: string, partIndex: number): void; + selectColor(category: string, colorIndex: number, paletteId: number): void; + hasClubSelectionsOverLevel(level: number): boolean; + hasInvalidSelectedItems(ownedItems: number[]): boolean; + stripClubItemsOverLevel(level: number): boolean; + stripInvalidSellableItems(): boolean; + categories: Map; + canSetGender: boolean; + maxPaletteCount: number; + name: string; +} diff --git a/src/api/avatar/IAvatarEditorCategoryPartItem.ts b/src/api/avatar/IAvatarEditorCategoryPartItem.ts new file mode 100644 index 0000000..d1cbc0d --- /dev/null +++ b/src/api/avatar/IAvatarEditorCategoryPartItem.ts @@ -0,0 +1,10 @@ +import { IFigurePartSet } from '@nitrots/nitro-renderer'; + +export interface IAvatarEditorCategoryPartItem +{ + id?: number; + partSet?: IFigurePartSet; + usesColor?: boolean; + maxPaletteCount?: number; + isClear?: boolean; +} diff --git a/src/api/avatar/LegModel.ts b/src/api/avatar/LegModel.ts new file mode 100644 index 0000000..5633930 --- /dev/null +++ b/src/api/avatar/LegModel.ts @@ -0,0 +1,22 @@ +import { AvatarEditorFigureCategory } from '@nitrots/nitro-renderer'; +import { CategoryBaseModel } from './CategoryBaseModel'; +import { FigureData } from './FigureData'; + +export class LegModel extends CategoryBaseModel +{ + public init(): void + { + super.init(); + + this.addCategory(FigureData.TROUSERS); + this.addCategory(FigureData.SHOES); + this.addCategory(FigureData.TROUSER_ACCESSORIES); + + this._isInitalized = true; + } + + public get name(): string + { + return AvatarEditorFigureCategory.LEGS; + } +} diff --git a/src/api/avatar/TorsoModel.ts b/src/api/avatar/TorsoModel.ts new file mode 100644 index 0000000..43e48cf --- /dev/null +++ b/src/api/avatar/TorsoModel.ts @@ -0,0 +1,23 @@ +import { AvatarEditorFigureCategory } from '@nitrots/nitro-renderer'; +import { CategoryBaseModel } from './CategoryBaseModel'; +import { FigureData } from './FigureData'; + +export class TorsoModel extends CategoryBaseModel +{ + public init(): void + { + super.init(); + + this.addCategory(FigureData.SHIRT); + this.addCategory(FigureData.CHEST_PRINTS); + this.addCategory(FigureData.JACKET); + this.addCategory(FigureData.CHEST_ACCESSORIES); + + this._isInitalized = true; + } + + public get name(): string + { + return AvatarEditorFigureCategory.TORSO; + } +} diff --git a/src/api/avatar/index.ts b/src/api/avatar/index.ts new file mode 100644 index 0000000..acf945b --- /dev/null +++ b/src/api/avatar/index.ts @@ -0,0 +1,16 @@ +export * from './AvatarEditorAction'; +export * from './AvatarEditorGridColorItem'; +export * from './AvatarEditorGridPartItem'; +export * from './AvatarEditorThumbnailsHelper'; +export * from './AvatarEditorUtilities'; +export * from './BodyModel'; +export * from './CategoryBaseModel'; +export * from './CategoryData'; +export * from './FigureData'; +export * from './FigureGenerator'; +export * from './HeadModel'; +export * from './IAvatarEditorCategory'; +export * from './IAvatarEditorCategoryModel'; +export * from './IAvatarEditorCategoryPartItem'; +export * from './LegModel'; +export * from './TorsoModel'; diff --git a/src/api/camera/CameraEditorTabs.ts b/src/api/camera/CameraEditorTabs.ts new file mode 100644 index 0000000..6e894e7 --- /dev/null +++ b/src/api/camera/CameraEditorTabs.ts @@ -0,0 +1,5 @@ +export class CameraEditorTabs +{ + public static readonly COLORMATRIX: string = 'colormatrix'; + public static readonly COMPOSITE: string = 'composite'; +} diff --git a/src/api/camera/CameraPicture.ts b/src/api/camera/CameraPicture.ts new file mode 100644 index 0000000..fe8c221 --- /dev/null +++ b/src/api/camera/CameraPicture.ts @@ -0,0 +1,9 @@ +import { NitroTexture } from '@nitrots/nitro-renderer'; + +export class CameraPicture +{ + constructor( + public texture: NitroTexture, + public imageUrl: string) + {} +} diff --git a/src/api/camera/CameraPictureThumbnail.ts b/src/api/camera/CameraPictureThumbnail.ts new file mode 100644 index 0000000..cd12660 --- /dev/null +++ b/src/api/camera/CameraPictureThumbnail.ts @@ -0,0 +1,7 @@ +export class CameraPictureThumbnail +{ + constructor( + public effectName: string, + public thumbnailUrl: string) + {} +} diff --git a/src/api/camera/index.ts b/src/api/camera/index.ts new file mode 100644 index 0000000..93c6ccb --- /dev/null +++ b/src/api/camera/index.ts @@ -0,0 +1,3 @@ +export * from './CameraEditorTabs'; +export * from './CameraPicture'; +export * from './CameraPictureThumbnail'; diff --git a/src/api/campaign/CalendarItem.ts b/src/api/campaign/CalendarItem.ts new file mode 100644 index 0000000..d3634b3 --- /dev/null +++ b/src/api/campaign/CalendarItem.ts @@ -0,0 +1,30 @@ +import { ICalendarItem } from './ICalendarItem'; + +export class CalendarItem implements ICalendarItem +{ + private _productName: string; + private _customImage: string; + private _furnitureClassName: string; + + constructor(productName: string, customImage: string, furnitureClassName: string) + { + this._productName = productName; + this._customImage = customImage; + this._furnitureClassName = furnitureClassName; + } + + public get productName(): string + { + return this._productName; + } + + public get customImage(): string + { + return this._customImage; + } + + public get furnitureClassName(): string + { + return this._furnitureClassName; + } +} diff --git a/src/api/campaign/CalendarItemState.ts b/src/api/campaign/CalendarItemState.ts new file mode 100644 index 0000000..1b91ca3 --- /dev/null +++ b/src/api/campaign/CalendarItemState.ts @@ -0,0 +1,7 @@ +export class CalendarItemState +{ + public static readonly STATE_UNLOCKED = 1; + public static readonly STATE_LOCKED_AVAILABLE = 2; + public static readonly STATE_LOCKED_EXPIRED = 3; + public static readonly STATE_LOCKED_FUTURE = 4; +} diff --git a/src/api/campaign/ICalendarItem.ts b/src/api/campaign/ICalendarItem.ts new file mode 100644 index 0000000..87dfbd6 --- /dev/null +++ b/src/api/campaign/ICalendarItem.ts @@ -0,0 +1,6 @@ +export interface ICalendarItem +{ + readonly productName: string; + readonly customImage: string; + readonly furnitureClassName: string; +} diff --git a/src/api/campaign/index.ts b/src/api/campaign/index.ts new file mode 100644 index 0000000..a86e40c --- /dev/null +++ b/src/api/campaign/index.ts @@ -0,0 +1,3 @@ +export * from './CalendarItem'; +export * from './CalendarItemState'; +export * from './ICalendarItem'; diff --git a/src/api/catalog/BuilderFurniPlaceableStatus.ts b/src/api/catalog/BuilderFurniPlaceableStatus.ts new file mode 100644 index 0000000..40eb6f6 --- /dev/null +++ b/src/api/catalog/BuilderFurniPlaceableStatus.ts @@ -0,0 +1,10 @@ +export class BuilderFurniPlaceableStatus +{ + public static OKAY: number = 0; + public static MISSING_OFFER: number = 1; + public static FURNI_LIMIT_REACHED: number = 2; + public static NOT_IN_ROOM: number = 3; + public static NOT_ROOM_OWNER: number = 4; + public static GUILD_ROOM: number = 5; + public static VISITORS_IN_ROOM: number = 6; +} diff --git a/src/api/catalog/CatalogNode.ts b/src/api/catalog/CatalogNode.ts new file mode 100644 index 0000000..893aa32 --- /dev/null +++ b/src/api/catalog/CatalogNode.ts @@ -0,0 +1,124 @@ +import { NodeData } from '@nitrots/nitro-renderer'; +import { ICatalogNode } from './ICatalogNode'; + +export class CatalogNode implements ICatalogNode +{ + private _depth: number = 0; + private _localization: string = ''; + private _pageId: number = -1; + private _pageName: string = ''; + private _iconId: number = 0; + private _children: ICatalogNode[]; + private _offerIds: number[]; + private _parent: ICatalogNode; + private _isVisible: boolean; + private _isActive: boolean; + private _isOpen: boolean; + + constructor(node: NodeData, depth: number, parent: ICatalogNode) + { + this._depth = depth; + this._parent = parent; + this._localization = node.localization; + this._pageId = node.pageId; + this._pageName = node.pageName; + this._iconId = node.icon; + this._children = []; + this._offerIds = node.offerIds; + this._isVisible = node.visible; + this._isActive = false; + this._isOpen = false; + } + + public activate(): void + { + this._isActive = true; + } + + public deactivate(): void + { + this._isActive = false; + } + + public open(): void + { + this._isOpen = true; + } + + public close(): void + { + this._isOpen = false; + } + + public addChild(child: ICatalogNode):void + { + if(!child) return; + + this._children.push(child); + } + + public get depth(): number + { + return this._depth; + } + + public get isBranch(): boolean + { + return (this._children.length > 0); + } + + public get isLeaf(): boolean + { + return (this._children.length === 0); + } + + public get localization(): string + { + return this._localization; + } + + public get pageId(): number + { + return this._pageId; + } + + public get pageName(): string + { + return this._pageName; + } + + public get iconId(): number + { + return this._iconId; + } + + public get children(): ICatalogNode[] + { + return this._children; + } + + public get offerIds(): number[] + { + return this._offerIds; + } + + public get parent(): ICatalogNode + { + return this._parent; + } + + public get isVisible(): boolean + { + return this._isVisible; + } + + public get isActive(): boolean + { + return this._isActive; + } + + public get isOpen(): boolean + { + return this._isOpen; + } +} diff --git a/src/api/catalog/CatalogPage.ts b/src/api/catalog/CatalogPage.ts new file mode 100644 index 0000000..1e80609 --- /dev/null +++ b/src/api/catalog/CatalogPage.ts @@ -0,0 +1,59 @@ +import { ICatalogPage } from './ICatalogPage'; +import { IPageLocalization } from './IPageLocalization'; +import { IPurchasableOffer } from './IPurchasableOffer'; + +export class CatalogPage implements ICatalogPage +{ + public static MODE_NORMAL: number = 0; + + private _pageId: number; + private _layoutCode: string; + private _localization: IPageLocalization; + private _offers: IPurchasableOffer[]; + private _acceptSeasonCurrencyAsCredits: boolean; + private _mode: number; + + constructor(pageId: number, layoutCode: string, localization: IPageLocalization, offers: IPurchasableOffer[], acceptSeasonCurrencyAsCredits: boolean, mode: number = -1) + { + this._pageId = pageId; + this._layoutCode = layoutCode; + this._localization = localization; + this._offers = offers; + this._acceptSeasonCurrencyAsCredits = acceptSeasonCurrencyAsCredits; + + for(const offer of offers) (offer.page = this); + + if(mode === -1) this._mode = CatalogPage.MODE_NORMAL; + else this._mode = mode; + } + + public get pageId(): number + { + return this._pageId; + } + + public get layoutCode(): string + { + return this._layoutCode; + } + + public get localization(): IPageLocalization + { + return this._localization; + } + + public get offers(): IPurchasableOffer[] + { + return this._offers; + } + + public get acceptSeasonCurrencyAsCredits(): boolean + { + return this._acceptSeasonCurrencyAsCredits; + } + + public get mode(): number + { + return this._mode; + } +} diff --git a/src/api/catalog/CatalogPageName.ts b/src/api/catalog/CatalogPageName.ts new file mode 100644 index 0000000..8e4c7b6 --- /dev/null +++ b/src/api/catalog/CatalogPageName.ts @@ -0,0 +1,26 @@ +export class CatalogPageName +{ + public static DUCKET_INFO: string = 'ducket_info'; + public static CREDITS: string = 'credits'; + public static AVATAR_EFFECTS: string = 'avatar_effects'; + public static HC_MEMBERSHIP: string = 'hc_membership'; + public static CLUB_GIFTS: string = 'club_gifts'; + public static LIMITED_SOLD: string = 'limited_sold'; + public static PET_ACCESSORIES: string = 'pet_accessories'; + public static TRAX_SONGS: string = 'trax_songs'; + public static NEW_ADDITIONS: string = 'new_additions'; + public static QUEST_SHELL: string = 'quest_shell'; + public static QUEST_SNOWFLAKES: string = 'quest_snowflakes'; + public static VAL_QUESTS: string = 'val_quests'; + public static GUILD_CUSTOM_FURNI: string = 'guild_custom_furni'; + public static GIFT_SHOP: string = 'gift_shop'; + public static HORSE_STYLES: string = 'horse_styles'; + public static HORSE_SHOE: string = 'horse_shoe'; + public static SET_EASTER: string = 'set_easter'; + public static ECOTRON_TRANSFORM: string = 'ecotron_transform'; + public static LOYALTY_INFO: string = 'loyalty_info'; + public static ROOM_BUNDLES: string = 'room_bundles'; + public static ROOM_BUNDLES_MOBILE: string = 'room_bundles_mobile'; + public static HABBO_CLUB_DESKTOP: string = 'habbo_club_desktop'; + public static MOBILE_SUBSCRIPTIONS: string = 'mobile_subscriptions'; +} diff --git a/src/api/catalog/CatalogPetPalette.ts b/src/api/catalog/CatalogPetPalette.ts new file mode 100644 index 0000000..d92c40d --- /dev/null +++ b/src/api/catalog/CatalogPetPalette.ts @@ -0,0 +1,10 @@ +import { SellablePetPaletteData } from '@nitrots/nitro-renderer'; + +export class CatalogPetPalette +{ + constructor( + public readonly breed: string, + public readonly palettes: SellablePetPaletteData[] + ) + {} +} diff --git a/src/api/catalog/CatalogPurchaseState.ts b/src/api/catalog/CatalogPurchaseState.ts new file mode 100644 index 0000000..b442f62 --- /dev/null +++ b/src/api/catalog/CatalogPurchaseState.ts @@ -0,0 +1,10 @@ +export class CatalogPurchaseState +{ + public static NONE = 0; + public static CONFIRM = 1; + public static PURCHASE = 2; + public static NO_CREDITS = 3; + public static NO_POINTS = 4; + public static SOLD_OUT = 5; + public static FAILED = 6; +} diff --git a/src/api/catalog/CatalogType.ts b/src/api/catalog/CatalogType.ts new file mode 100644 index 0000000..670ad6f --- /dev/null +++ b/src/api/catalog/CatalogType.ts @@ -0,0 +1,5 @@ +export class CatalogType +{ + public static NORMAL: string = 'NORMAL'; + public static BUILDER: string = 'BUILDERS_CLUB'; +} diff --git a/src/api/catalog/CatalogUtilities.ts b/src/api/catalog/CatalogUtilities.ts new file mode 100644 index 0000000..7aff6a8 --- /dev/null +++ b/src/api/catalog/CatalogUtilities.ts @@ -0,0 +1,124 @@ +import { GetRoomEngine, SellablePetPaletteData } from '@nitrots/nitro-renderer'; +import { ICatalogNode } from './ICatalogNode'; + +export const GetPixelEffectIcon = (id: number) => +{ + return ''; +} + +export const GetSubscriptionProductIcon = (id: number) => +{ + return ''; +} + +export const GetOfferNodes = (offerNodes: Map, offerId: number) => +{ + const nodes = offerNodes.get(offerId); + const allowedNodes: ICatalogNode[] = []; + + if(nodes && nodes.length) + { + for(const node of nodes) + { + if(!node.isVisible) continue; + + allowedNodes.push(node); + } + } + + return allowedNodes; +} + +export const FilterCatalogNode = (search: string, furniLines: string[], node: ICatalogNode, nodes: ICatalogNode[]) => +{ + if(node.isVisible && (node.pageId > 0)) + { + let nodeAdded = false; + + const hayStack = [ node.pageName, node.localization ].join(' ').toLowerCase().replace(/ /gi, ''); + + if(hayStack.indexOf(search) > -1) + { + nodes.push(node); + + nodeAdded = true; + } + + if(!nodeAdded) + { + for(const furniLine of furniLines) + { + if(hayStack.indexOf(furniLine) >= 0) + { + nodes.push(node); + + break; + } + } + } + } + + for(const child of node.children) FilterCatalogNode(search, furniLines, child, nodes); +} + +export function GetPetIndexFromLocalization(localization: string) +{ + if(!localization.length) return 0; + + let index = (localization.length - 1); + + while(index >= 0) + { + if(isNaN(parseInt(localization.charAt(index)))) break; + + index--; + } + + if(index > 0) return parseInt(localization.substring(index + 1)); + + return -1; +} + +export function GetPetAvailableColors(petIndex: number, palettes: SellablePetPaletteData[]): number[][] +{ + switch(petIndex) + { + case 0: + return [ [ 16743226 ], [ 16750435 ], [ 16764339 ], [ 0xF59500 ], [ 16498012 ], [ 16704690 ], [ 0xEDD400 ], [ 16115545 ], [ 16513201 ], [ 8694111 ], [ 11585939 ], [ 14413767 ], [ 6664599 ], [ 9553845 ], [ 12971486 ], [ 8358322 ], [ 10002885 ], [ 13292268 ], [ 10780600 ], [ 12623573 ], [ 14403561 ], [ 12418717 ], [ 14327229 ], [ 15517403 ], [ 14515069 ], [ 15764368 ], [ 16366271 ], [ 0xABABAB ], [ 0xD4D4D4 ], [ 0xFFFFFF ], [ 14256481 ], [ 14656129 ], [ 15848130 ], [ 14005087 ], [ 14337152 ], [ 15918540 ], [ 15118118 ], [ 15531929 ], [ 9764857 ], [ 11258085 ] ]; + case 1: + return [ [ 16743226 ], [ 16750435 ], [ 16764339 ], [ 0xF59500 ], [ 16498012 ], [ 16704690 ], [ 0xEDD400 ], [ 16115545 ], [ 16513201 ], [ 8694111 ], [ 11585939 ], [ 14413767 ], [ 6664599 ], [ 9553845 ], [ 12971486 ], [ 8358322 ], [ 10002885 ], [ 13292268 ], [ 10780600 ], [ 12623573 ], [ 14403561 ], [ 12418717 ], [ 14327229 ], [ 15517403 ], [ 14515069 ], [ 15764368 ], [ 16366271 ], [ 0xABABAB ], [ 0xD4D4D4 ], [ 0xFFFFFF ], [ 14256481 ], [ 14656129 ], [ 15848130 ], [ 14005087 ], [ 14337152 ], [ 15918540 ], [ 15118118 ], [ 15531929 ], [ 9764857 ], [ 11258085 ] ]; + case 2: + return [ [ 16579283 ], [ 15378351 ], [ 8830016 ], [ 15257125 ], [ 9340985 ], [ 8949607 ], [ 6198292 ], [ 8703620 ], [ 9889626 ], [ 8972045 ], [ 12161285 ], [ 13162269 ], [ 8620113 ], [ 12616503 ], [ 8628101 ], [ 0xD2FF00 ], [ 9764857 ] ]; + case 3: + return [ [ 0xFFFFFF ], [ 0xEEEEEE ], [ 0xDDDDDD ] ]; + case 4: + return [ [ 0xFFFFFF ], [ 16053490 ], [ 15464440 ], [ 16248792 ], [ 15396319 ], [ 15007487 ] ]; + case 5: + return [ [ 0xFFFFFF ], [ 0xEEEEEE ], [ 0xDDDDDD ] ]; + case 6: + return [ [ 0xFFFFFF ], [ 0xEEEEEE ], [ 0xDDDDDD ], [ 16767177 ], [ 16770205 ], [ 16751331 ] ]; + case 7: + return [ [ 0xCCCCCC ], [ 0xAEAEAE ], [ 16751331 ], [ 10149119 ], [ 16763290 ], [ 16743786 ] ]; + default: { + const colors: number[][] = []; + + for(const palette of palettes) + { + const petColorResult = GetRoomEngine().getPetColorResult(petIndex, palette.paletteId); + + if(!petColorResult) continue; + + if(petColorResult.primaryColor === petColorResult.secondaryColor) + { + colors.push([ petColorResult.primaryColor ]); + } + else + { + colors.push([ petColorResult.primaryColor, petColorResult.secondaryColor ]); + } + } + + return colors; + } + } +} diff --git a/src/api/catalog/FurnitureOffer.ts b/src/api/catalog/FurnitureOffer.ts new file mode 100644 index 0000000..367f247 --- /dev/null +++ b/src/api/catalog/FurnitureOffer.ts @@ -0,0 +1,120 @@ +import { GetProductOfferComposer, IFurnitureData } from '@nitrots/nitro-renderer'; +import { GetProductDataForLocalization, SendMessageComposer } from '../nitro'; +import { ICatalogPage } from './ICatalogPage'; +import { IProduct } from './IProduct'; +import { IPurchasableOffer } from './IPurchasableOffer'; +import { Offer } from './Offer'; +import { Product } from './Product'; + +export class FurnitureOffer implements IPurchasableOffer +{ + private _furniData:IFurnitureData; + private _page: ICatalogPage; + private _product: IProduct; + + constructor(furniData: IFurnitureData) + { + this._furniData = furniData; + this._product = (new Product(this._furniData.type, this._furniData.id, this._furniData.customParams, 1, GetProductDataForLocalization(this._furniData.className), this._furniData) as IProduct); + } + + public activate(): void + { + SendMessageComposer(new GetProductOfferComposer((this._furniData.rentOfferId > -1) ? this._furniData.rentOfferId : this._furniData.purchaseOfferId)); + } + + public get offerId(): number + { + return (this.isRentOffer) ? this._furniData.rentOfferId : this._furniData.purchaseOfferId; + } + + public get priceInActivityPoints(): number + { + return 0; + } + + public get activityPointType(): number + { + return 0; + } + + public get priceInCredits(): number + { + return 0; + } + + public get page(): ICatalogPage + { + return this._page; + } + + public set page(page: ICatalogPage) + { + this._page = page; + } + + public get priceType(): string + { + return ''; + } + + public get product(): IProduct + { + return this._product; + } + + public get products(): IProduct[] + { + return [ this._product ]; + } + + public get localizationId(): string + { + return 'roomItem.name.' + this._furniData.id; + } + + public get bundlePurchaseAllowed(): boolean + { + return false; + } + + public get isRentOffer(): boolean + { + return (this._furniData.rentOfferId > -1); + } + + public get giftable(): boolean + { + return false; + } + + public get pricingModel(): string + { + return Offer.PRICING_MODEL_FURNITURE; + } + + public get clubLevel(): number + { + return 0; + } + + public get badgeCode(): string + { + return ''; + } + + public get localizationName(): string + { + return this._furniData.name; + } + + public get localizationDescription(): string + { + return this._furniData.description; + } + + public get isLazy(): boolean + { + return true; + } +} diff --git a/src/api/catalog/GetImageIconUrlForProduct.ts b/src/api/catalog/GetImageIconUrlForProduct.ts new file mode 100644 index 0000000..8c363e7 --- /dev/null +++ b/src/api/catalog/GetImageIconUrlForProduct.ts @@ -0,0 +1,19 @@ +import { GetRoomEngine } from '@nitrots/nitro-renderer'; +import { ProductTypeEnum } from './ProductTypeEnum'; + +export const GetImageIconUrlForProduct = (productType: string, productClassId: number, extraData: string = null) => +{ + let imageUrl: string = null; + + switch(productType.toLocaleLowerCase()) + { + case ProductTypeEnum.FLOOR: + imageUrl = GetRoomEngine().getFurnitureFloorIconUrl(productClassId); + break; + case ProductTypeEnum.WALL: + imageUrl = GetRoomEngine().getFurnitureWallIconUrl(productClassId, extraData); + break; + } + + return imageUrl; +} diff --git a/src/api/catalog/GiftWrappingConfiguration.ts b/src/api/catalog/GiftWrappingConfiguration.ts new file mode 100644 index 0000000..9d29b8c --- /dev/null +++ b/src/api/catalog/GiftWrappingConfiguration.ts @@ -0,0 +1,51 @@ +import { GiftWrappingConfigurationParser } from '@nitrots/nitro-renderer'; + +export class GiftWrappingConfiguration +{ + private _isEnabled: boolean = false; + private _price: number = null; + private _stuffTypes: number[] = null; + private _boxTypes: number[] = null; + private _ribbonTypes: number[] = null; + private _defaultStuffTypes: number[] = null; + + constructor(parser: GiftWrappingConfigurationParser) + { + this._isEnabled = parser.isEnabled; + this._price = parser.price; + this._boxTypes = parser.boxTypes; + this._ribbonTypes = parser.ribbonTypes; + this._stuffTypes = parser.giftWrappers; + this._defaultStuffTypes = parser.giftFurnis; + } + + public get isEnabled(): boolean + { + return this._isEnabled; + } + + public get price(): number + { + return this._price; + } + + public get stuffTypes(): number[] + { + return this._stuffTypes; + } + + public get boxTypes(): number[] + { + return this._boxTypes; + } + + public get ribbonTypes(): number[] + { + return this._ribbonTypes; + } + + public get defaultStuffTypes(): number[] + { + return this._defaultStuffTypes; + } +} diff --git a/src/api/catalog/ICatalogNode.ts b/src/api/catalog/ICatalogNode.ts new file mode 100644 index 0000000..c69f5a6 --- /dev/null +++ b/src/api/catalog/ICatalogNode.ts @@ -0,0 +1,21 @@ +export interface ICatalogNode +{ + activate(): void; + deactivate(): void; + open(): void; + close(): void; + addChild(node: ICatalogNode): void; + readonly depth: number; + readonly isBranch: boolean; + readonly isLeaf: boolean; + readonly localization: string; + readonly pageId: number; + readonly pageName: string; + readonly iconId: number; + readonly children: ICatalogNode[]; + readonly offerIds: number[]; + readonly parent: ICatalogNode; + readonly isVisible: boolean; + readonly isActive: boolean; + readonly isOpen: boolean; +} diff --git a/src/api/catalog/ICatalogOptions.ts b/src/api/catalog/ICatalogOptions.ts new file mode 100644 index 0000000..2035694 --- /dev/null +++ b/src/api/catalog/ICatalogOptions.ts @@ -0,0 +1,13 @@ +import { ClubGiftInfoParser, ClubOfferData, HabboGroupEntryData, MarketplaceConfigurationMessageParser } from '@nitrots/nitro-renderer'; +import { CatalogPetPalette } from './CatalogPetPalette'; +import { GiftWrappingConfiguration } from './GiftWrappingConfiguration'; + +export interface ICatalogOptions +{ + groups?: HabboGroupEntryData[]; + petPalettes?: CatalogPetPalette[]; + clubOffers?: ClubOfferData[]; + clubGifts?: ClubGiftInfoParser; + giftConfiguration?: GiftWrappingConfiguration; + marketplaceConfiguration?: MarketplaceConfigurationMessageParser; +} diff --git a/src/api/catalog/ICatalogPage.ts b/src/api/catalog/ICatalogPage.ts new file mode 100644 index 0000000..ed11ba0 --- /dev/null +++ b/src/api/catalog/ICatalogPage.ts @@ -0,0 +1,12 @@ +import { IPageLocalization } from './IPageLocalization'; +import { IPurchasableOffer } from './IPurchasableOffer'; + +export interface ICatalogPage +{ + readonly pageId: number; + readonly layoutCode: string; + readonly localization: IPageLocalization; + readonly offers: IPurchasableOffer[]; + readonly acceptSeasonCurrencyAsCredits: boolean; + readonly mode: number; +} diff --git a/src/api/catalog/IMarketplaceSearchOptions.ts b/src/api/catalog/IMarketplaceSearchOptions.ts new file mode 100644 index 0000000..9489ef0 --- /dev/null +++ b/src/api/catalog/IMarketplaceSearchOptions.ts @@ -0,0 +1,7 @@ +export interface IMarketplaceSearchOptions +{ + query: string; + type: number; + minPrice: number; + maxPrice: number; +} diff --git a/src/api/catalog/IPageLocalization.ts b/src/api/catalog/IPageLocalization.ts new file mode 100644 index 0000000..ad652e1 --- /dev/null +++ b/src/api/catalog/IPageLocalization.ts @@ -0,0 +1,5 @@ +export interface IPageLocalization +{ + getText(index: number): string + getImage(index: number): string +} diff --git a/src/api/catalog/IProduct.ts b/src/api/catalog/IProduct.ts new file mode 100644 index 0000000..4a1a392 --- /dev/null +++ b/src/api/catalog/IProduct.ts @@ -0,0 +1,16 @@ +import { IFurnitureData, IProductData } from '@nitrots/nitro-renderer'; +import { IPurchasableOffer } from './IPurchasableOffer'; + +export interface IProduct +{ + getIconUrl(offer?: IPurchasableOffer): string; + productType: string; + productClassId: number; + extraParam: string; + productCount: number; + productData: IProductData; + furnitureData: IFurnitureData; + isUniqueLimitedItem: boolean; + uniqueLimitedItemSeriesSize: number; + uniqueLimitedItemsLeft: number; +} diff --git a/src/api/catalog/IPurchasableOffer.ts b/src/api/catalog/IPurchasableOffer.ts new file mode 100644 index 0000000..b182865 --- /dev/null +++ b/src/api/catalog/IPurchasableOffer.ts @@ -0,0 +1,25 @@ +import { ICatalogPage } from './ICatalogPage'; +import { IProduct } from './IProduct'; + +export interface IPurchasableOffer +{ + activate(): void; + clubLevel: number; + page: ICatalogPage; + offerId: number; + localizationId: string; + priceInCredits: number; + priceInActivityPoints: number; + activityPointType: number; + giftable: boolean; + product: IProduct; + pricingModel: string; + priceType: string; + bundlePurchaseAllowed: boolean; + isRentOffer: boolean; + badgeCode: string; + localizationName: string; + localizationDescription: string; + isLazy: boolean; + products: IProduct[]; +} diff --git a/src/api/catalog/IPurchaseOptions.ts b/src/api/catalog/IPurchaseOptions.ts new file mode 100644 index 0000000..c9fab89 --- /dev/null +++ b/src/api/catalog/IPurchaseOptions.ts @@ -0,0 +1,9 @@ +import { IObjectData } from '@nitrots/nitro-renderer'; + +export interface IPurchaseOptions +{ + quantity?: number; + extraData?: string; + extraParamRequired?: boolean; + previewStuffData?: IObjectData; +} diff --git a/src/api/catalog/MarketplaceOfferData.ts b/src/api/catalog/MarketplaceOfferData.ts new file mode 100644 index 0000000..ba1fa88 --- /dev/null +++ b/src/api/catalog/MarketplaceOfferData.ts @@ -0,0 +1,128 @@ +import { IObjectData } from '@nitrots/nitro-renderer'; + +export class MarketplaceOfferData +{ + public static readonly TYPE_FLOOR: number = 1; + public static readonly TYPE_WALL: number = 2; + + private _offerId: number; + private _furniId: number; + private _furniType: number; + private _extraData: string; + private _stuffData: IObjectData; + private _price: number; + private _averagePrice: number; + private _imageCallback: number; + private _status: number; + private _timeLeftMinutes: number = -1; + private _offerCount: number; + private _image: string; + + constructor(offerId: number, furniId: number, furniType: number, extraData: string, stuffData: IObjectData, price: number, status: number, averagePrice: number, offerCount: number = -1) + { + this._offerId = offerId; + this._furniId = furniId; + this._furniType = furniType; + this._extraData = extraData; + this._stuffData = stuffData; + this._price = price; + this._status = status; + this._averagePrice = averagePrice; + this._offerCount = offerCount; + } + + public get offerId(): number + { + return this._offerId; + } + + public set offerId(offerId: number) + { + this._offerId = offerId; + } + + public get furniId(): number + { + return this._furniId; + } + + public get furniType(): number + { + return this._furniType; + } + + public get extraData(): string + { + return this._extraData; + } + + public get stuffData(): IObjectData + { + return this._stuffData; + } + + public get price(): number + { + return this._price; + } + + public set price(price: number) + { + this._price = price; + } + + public get averagePrice(): number + { + return this._averagePrice; + } + + public get image(): string + { + return this._image; + } + + public set image(image: string) + { + this._image = image; + } + + public get imageCallback(): number + { + return this._imageCallback; + } + + public set imageCallback(callback: number) + { + this._imageCallback = callback; + } + + public get status(): number + { + return this._status; + } + + public get timeLeftMinutes(): number + { + return this._timeLeftMinutes; + } + + public set timeLeftMinutes(minutes: number) + { + this._timeLeftMinutes = minutes; + } + + public get offerCount(): number + { + return this._offerCount; + } + + public set offerCount(count: number) + { + this._offerCount = count; + } + + public get isUniqueLimitedItem(): boolean + { + return (this.stuffData && (this.stuffData.uniqueSeries > 0)); + } +} diff --git a/src/api/catalog/MarketplaceOfferState.ts b/src/api/catalog/MarketplaceOfferState.ts new file mode 100644 index 0000000..20c0e45 --- /dev/null +++ b/src/api/catalog/MarketplaceOfferState.ts @@ -0,0 +1,7 @@ +export class MarketPlaceOfferState +{ + public static readonly ONGOING = 1; + public static readonly ONGOING_OWN = 1; + public static readonly SOLD = 2; + public static readonly EXPIRED = 3; +} diff --git a/src/api/catalog/MarketplaceSearchType.ts b/src/api/catalog/MarketplaceSearchType.ts new file mode 100644 index 0000000..ac7a701 --- /dev/null +++ b/src/api/catalog/MarketplaceSearchType.ts @@ -0,0 +1,6 @@ +export class MarketplaceSearchType +{ + public static readonly BY_ACTIVITY = 1; + public static readonly BY_VALUE = 2; + public static readonly ADVANCED = 3; +} diff --git a/src/api/catalog/Offer.ts b/src/api/catalog/Offer.ts new file mode 100644 index 0000000..c14d6ac --- /dev/null +++ b/src/api/catalog/Offer.ts @@ -0,0 +1,245 @@ +import { GetFurnitureData, GetProductDataForLocalization, LocalizeText, ProductTypeEnum } from '..'; +import { ICatalogPage } from './ICatalogPage'; +import { IProduct } from './IProduct'; +import { IPurchasableOffer } from './IPurchasableOffer'; +import { Product } from './Product'; + +export class Offer implements IPurchasableOffer +{ + public static PRICING_MODEL_UNKNOWN: string = 'pricing_model_unknown'; + public static PRICING_MODEL_SINGLE: string = 'pricing_model_single'; + public static PRICING_MODEL_MULTI: string = 'pricing_model_multi'; + public static PRICING_MODEL_BUNDLE: string = 'pricing_model_bundle'; + public static PRICING_MODEL_FURNITURE: string = 'pricing_model_furniture'; + public static PRICE_TYPE_NONE: string = 'price_type_none'; + public static PRICE_TYPE_CREDITS: string = 'price_type_credits'; + public static PRICE_TYPE_ACTIVITYPOINTS: string = 'price_type_activitypoints'; + public static PRICE_TYPE_CREDITS_ACTIVITYPOINTS: string = 'price_type_credits_and_activitypoints'; + + private _pricingModel: string; + private _priceType: string; + private _offerId: number; + private _localizationId: string; + private _priceInCredits: number; + private _priceInActivityPoints: number; + private _activityPointType: number; + private _giftable: boolean; + private _isRentOffer: boolean; + private _page: ICatalogPage; + private _clubLevel: number = 0; + private _products: IProduct[]; + private _badgeCode: string; + private _bundlePurchaseAllowed: boolean = false; + + constructor(offerId: number, localizationId: string, isRentOffer: boolean, priceInCredits: number, priceInActivityPoints: number, activityPointType: number, giftable: boolean, clubLevel: number, products: IProduct[], bundlePurchaseAllowed: boolean) + { + this._offerId = offerId; + this._localizationId = localizationId; + this._isRentOffer = isRentOffer; + this._priceInCredits = priceInCredits; + this._priceInActivityPoints = priceInActivityPoints; + this._activityPointType = activityPointType; + this._giftable = giftable; + this._clubLevel = clubLevel; + this._products = products; + this._bundlePurchaseAllowed = bundlePurchaseAllowed; + + this.setPricingModelForProducts(); + this.setPricingType(); + + for(const product of products) + { + if(product.productType === ProductTypeEnum.BADGE) + { + this._badgeCode = product.extraParam; + + break; + } + } + } + + public activate(): void + { + + } + + public get clubLevel(): number + { + return this._clubLevel; + } + + public get page(): ICatalogPage + { + return this._page; + } + + public set page(k: ICatalogPage) + { + this._page = k; + } + + public get offerId(): number + { + return this._offerId; + } + + public get localizationId(): string + { + return this._localizationId; + } + + public get priceInCredits(): number + { + return this._priceInCredits; + } + + public get priceInActivityPoints(): number + { + return this._priceInActivityPoints; + } + + public get activityPointType(): number + { + return this._activityPointType; + } + + public get giftable(): boolean + { + return this._giftable; + } + + public get product(): IProduct + { + if(!this._products || !this._products.length) return null; + + if(this._products.length === 1) return this._products[0]; + + const products = Product.stripAddonProducts(this._products); + + if(products.length) return products[0]; + + return null; + } + + public get pricingModel(): string + { + return this._pricingModel; + } + + public get priceType(): string + { + return this._priceType; + } + + public get bundlePurchaseAllowed(): boolean + { + return this._bundlePurchaseAllowed; + } + + public get isRentOffer(): boolean + { + return this._isRentOffer; + } + + public get badgeCode(): string + { + return this._badgeCode; + } + + public get localizationName(): string + { + const productData = GetProductDataForLocalization(this._localizationId); + + if(productData) return productData.name; + + return LocalizeText(this._localizationId); + } + + public get localizationDescription(): string + { + const productData = GetProductDataForLocalization(this._localizationId); + + if(productData) return productData.description; + + return LocalizeText(this._localizationId); + } + + public get isLazy(): boolean + { + return false; + } + + public get products(): IProduct[] + { + return this._products; + } + + private setPricingModelForProducts(): void + { + const products = Product.stripAddonProducts(this._products); + + if(products.length === 1) + { + if(products[0].productCount === 1) + { + this._pricingModel = Offer.PRICING_MODEL_SINGLE; + } + else + { + this._pricingModel = Offer.PRICING_MODEL_MULTI; + } + } + + else if(products.length > 1) + { + this._pricingModel = Offer.PRICING_MODEL_BUNDLE; + } + + else + { + this._pricingModel = Offer.PRICING_MODEL_UNKNOWN; + } + } + + private setPricingType(): void + { + if((this._priceInCredits > 0) && (this._priceInActivityPoints > 0)) + { + this._priceType = Offer.PRICE_TYPE_CREDITS_ACTIVITYPOINTS; + } + + else if(this._priceInCredits > 0) + { + this._priceType = Offer.PRICE_TYPE_CREDITS; + } + + else if(this._priceInActivityPoints > 0) + { + this._priceType = Offer.PRICE_TYPE_ACTIVITYPOINTS; + } + + else + { + this._priceType = Offer.PRICE_TYPE_NONE; + } + } + + public clone(): IPurchasableOffer + { + const products: IProduct[] = []; + const productData = GetProductDataForLocalization(this.localizationId); + + for(const product of this._products) + { + const furnitureData = GetFurnitureData(product.productClassId, product.productType); + + products.push(new Product(product.productType, product.productClassId, product.extraParam, product.productCount, productData, furnitureData)); + } + + const offer = new Offer(this.offerId, this.localizationId, this.isRentOffer, this.priceInCredits, this.priceInActivityPoints, this.activityPointType, this.giftable, this.clubLevel, products, this.bundlePurchaseAllowed); + + offer.page = this.page; + + return offer; + } +} diff --git a/src/api/catalog/PageLocalization.ts b/src/api/catalog/PageLocalization.ts new file mode 100644 index 0000000..d433923 --- /dev/null +++ b/src/api/catalog/PageLocalization.ts @@ -0,0 +1,36 @@ +import { GetConfigurationValue } from '../nitro'; +import { IPageLocalization } from './IPageLocalization'; + +export class PageLocalization implements IPageLocalization +{ + private _images: string[]; + private _texts: string[] + + constructor(images: string[], texts: string[]) + { + this._images = images; + this._texts = texts; + } + + public getText(index: number): string + { + let message = (this._texts[index] || ''); + + if(message && message.length) message = message.replace(/\r\n|\r|\n/g, '
'); + + return message; + } + + public getImage(index: number): string + { + const imageName = (this._images[index] || ''); + + if(!imageName || !imageName.length) return null; + + let assetUrl = GetConfigurationValue('catalog.asset.image.url'); + + assetUrl = assetUrl.replace('%name%', imageName); + + return assetUrl; + } +} diff --git a/src/api/catalog/PlacedObjectPurchaseData.ts b/src/api/catalog/PlacedObjectPurchaseData.ts new file mode 100644 index 0000000..84bad8c --- /dev/null +++ b/src/api/catalog/PlacedObjectPurchaseData.ts @@ -0,0 +1,41 @@ +import { IFurnitureData, IProductData } from '@nitrots/nitro-renderer'; +import { IPurchasableOffer } from './IPurchasableOffer'; + +export class PlacedObjectPurchaseData +{ + constructor( + public readonly roomId: number, + public readonly objectId: number, + public readonly category: number, + public readonly wallLocation: string, + public readonly x: number, + public readonly y: number, + public readonly direction: number, + public readonly offer: IPurchasableOffer) + {} + + public get offerId(): number + { + return this.offer.offerId; + } + + public get productClassId(): number + { + return this.offer.product.productClassId; + } + + public get productData(): IProductData + { + return this.offer.product.productData; + } + + public get furniData(): IFurnitureData + { + return this.offer.product.furnitureData; + } + + public get extraParam(): string + { + return this.offer.product.extraParam; + } +} diff --git a/src/api/catalog/Product.ts b/src/api/catalog/Product.ts new file mode 100644 index 0000000..17d9340 --- /dev/null +++ b/src/api/catalog/Product.ts @@ -0,0 +1,143 @@ +import { GetRoomEngine, GetSessionDataManager, IFurnitureData, IObjectData, IProductData } from '@nitrots/nitro-renderer'; +import { GetConfigurationValue } from '../nitro'; +import { GetPixelEffectIcon, GetSubscriptionProductIcon } from './CatalogUtilities'; +import { IProduct } from './IProduct'; +import { IPurchasableOffer } from './IPurchasableOffer'; +import { ProductTypeEnum } from './ProductTypeEnum'; + +export class Product implements IProduct +{ + public static EFFECT_CLASSID_NINJA_DISAPPEAR: number = 108; + + private _productType: string; + private _productClassId: number; + private _extraParam: string; + private _productCount: number; + private _productData: IProductData; + private _furnitureData: IFurnitureData; + private _isUniqueLimitedItem: boolean; + private _uniqueLimitedItemSeriesSize: number; + private _uniqueLimitedItemsLeft: number; + + constructor(productType: string, productClassId: number, extraParam: string, productCount: number, productData: IProductData, furnitureData: IFurnitureData, isUniqueLimitedItem: boolean = false, uniqueLimitedItemSeriesSize: number = 0, uniqueLimitedItemsLeft: number = 0) + { + this._productType = productType.toLowerCase(); + this._productClassId = productClassId; + this._extraParam = extraParam; + this._productCount = productCount; + this._productData = productData; + this._furnitureData = furnitureData; + this._isUniqueLimitedItem = isUniqueLimitedItem; + this._uniqueLimitedItemSeriesSize = uniqueLimitedItemSeriesSize; + this._uniqueLimitedItemsLeft = uniqueLimitedItemsLeft; + } + + public static stripAddonProducts(products: IProduct[]): IProduct[] + { + if(products.length === 1) return products; + + return products.filter(product => ((product.productType !== ProductTypeEnum.BADGE) && (product.productType !== ProductTypeEnum.EFFECT) && (product.productClassId !== Product.EFFECT_CLASSID_NINJA_DISAPPEAR))); + } + + public getIconUrl(offer: IPurchasableOffer = null, stuffData: IObjectData = null): string + { + switch(this._productType) + { + case ProductTypeEnum.FLOOR: + return GetRoomEngine().getFurnitureFloorIconUrl(this.productClassId); + case ProductTypeEnum.WALL: { + if(offer && this._furnitureData) + { + let iconName = ''; + + switch(this._furnitureData.className) + { + case 'floor': + iconName = [ 'th', this._furnitureData.className, offer.product.extraParam ].join('_'); + break; + case 'wallpaper': + iconName = [ 'th', 'wall', offer.product.extraParam ].join('_'); + break; + case 'landscape': + iconName = [ 'th', this._furnitureData.className, (offer.product.extraParam || '').replace('.', '_'), '001' ].join('_'); + break; + } + + if(iconName !== '') + { + const assetUrl = GetConfigurationValue('catalog.asset.url'); + + return `${ assetUrl }/${ iconName }.png`; + } + } + + return GetRoomEngine().getFurnitureWallIconUrl(this.productClassId, this._extraParam); + } + case ProductTypeEnum.EFFECT: + return GetPixelEffectIcon(this.productClassId); + case ProductTypeEnum.HABBO_CLUB: + return GetSubscriptionProductIcon(this.productClassId); + case ProductTypeEnum.BADGE: + return GetSessionDataManager().getBadgeUrl(this._extraParam); + case ProductTypeEnum.ROBOT: + return null; + } + + return null; + } + + public get productType(): string + { + return this._productType; + } + + public get productClassId(): number + { + return this._productClassId; + } + + public get extraParam(): string + { + return this._extraParam; + } + + public set extraParam(extraParam: string) + { + this._extraParam = extraParam; + } + + public get productCount(): number + { + return this._productCount; + } + + public get productData(): IProductData + { + return this._productData; + } + + public get furnitureData(): IFurnitureData + { + return this._furnitureData; + } + + public get isUniqueLimitedItem(): boolean + { + return this._isUniqueLimitedItem; + } + + public get uniqueLimitedItemSeriesSize(): number + { + return this._uniqueLimitedItemSeriesSize; + } + + public get uniqueLimitedItemsLeft(): number + { + return this._uniqueLimitedItemsLeft; + } + + public set uniqueLimitedItemsLeft(uniqueLimitedItemsLeft: number) + { + this._uniqueLimitedItemsLeft = uniqueLimitedItemsLeft; + } +} diff --git a/src/api/catalog/ProductTypeEnum.ts b/src/api/catalog/ProductTypeEnum.ts new file mode 100644 index 0000000..f249081 --- /dev/null +++ b/src/api/catalog/ProductTypeEnum.ts @@ -0,0 +1,11 @@ +export class ProductTypeEnum +{ + public static WALL: string = 'i'; + public static FLOOR: string = 's'; + public static EFFECT: string = 'e'; + public static HABBO_CLUB: string = 'h'; + public static BADGE: string = 'b'; + public static GAME_TOKEN: string = 'GAME_TOKEN'; + public static PET: string = 'p'; + public static ROBOT: string = 'r'; +} diff --git a/src/api/catalog/RequestedPage.ts b/src/api/catalog/RequestedPage.ts new file mode 100644 index 0000000..8c22730 --- /dev/null +++ b/src/api/catalog/RequestedPage.ts @@ -0,0 +1,63 @@ +export class RequestedPage +{ + public static REQUEST_TYPE_NONE: number = 0; + public static REQUEST_TYPE_ID: number = 1; + public static REQUEST_TYPE_OFFER: number = 2; + public static REQUEST_TYPE_NAME: number = 3; + + private _requestType: number; + private _requestById: number; + private _requestedByOfferId: number; + private _requestByName: string; + + constructor() + { + this._requestType = RequestedPage.REQUEST_TYPE_NONE; + } + + public resetRequest():void + { + this._requestType = RequestedPage.REQUEST_TYPE_NONE; + this._requestById = -1; + this._requestedByOfferId = -1; + this._requestByName = null; + } + + public get requestType(): number + { + return this._requestType; + } + + public get requestById(): number + { + return this._requestById; + } + + public set requestById(id: number) + { + this._requestType = RequestedPage.REQUEST_TYPE_ID; + this._requestById = id; + } + + public get requestedByOfferId(): number + { + return this._requestedByOfferId; + } + + public set requestedByOfferId(offerId: number) + { + this._requestType = RequestedPage.REQUEST_TYPE_OFFER; + this._requestedByOfferId = offerId; + } + + public get requestByName(): string + { + return this._requestByName; + } + + public set requestByName(name: string) + { + this._requestType = RequestedPage.REQUEST_TYPE_NAME; + this._requestByName = name; + } +} diff --git a/src/api/catalog/SearchResult.ts b/src/api/catalog/SearchResult.ts new file mode 100644 index 0000000..419f3cf --- /dev/null +++ b/src/api/catalog/SearchResult.ts @@ -0,0 +1,11 @@ +import { ICatalogNode } from './ICatalogNode'; +import { IPurchasableOffer } from './IPurchasableOffer'; + +export class SearchResult +{ + constructor( + public readonly searchValue: string, + public readonly offers: IPurchasableOffer[], + public readonly filteredNodes: ICatalogNode[]) + {} +} diff --git a/src/api/catalog/index.ts b/src/api/catalog/index.ts new file mode 100644 index 0000000..6c5b9e2 --- /dev/null +++ b/src/api/catalog/index.ts @@ -0,0 +1,29 @@ +export * from './BuilderFurniPlaceableStatus'; +export * from './CatalogNode'; +export * from './CatalogPage'; +export * from './CatalogPageName'; +export * from './CatalogPetPalette'; +export * from './CatalogPurchaseState'; +export * from './CatalogType'; +export * from './CatalogUtilities'; +export * from './FurnitureOffer'; +export * from './GetImageIconUrlForProduct'; +export * from './GiftWrappingConfiguration'; +export * from './ICatalogNode'; +export * from './ICatalogOptions'; +export * from './ICatalogPage'; +export * from './IMarketplaceSearchOptions'; +export * from './IPageLocalization'; +export * from './IProduct'; +export * from './IPurchasableOffer'; +export * from './IPurchaseOptions'; +export * from './MarketplaceOfferData'; +export * from './MarketplaceOfferState'; +export * from './MarketplaceSearchType'; +export * from './Offer'; +export * from './PageLocalization'; +export * from './PlacedObjectPurchaseData'; +export * from './Product'; +export * from './ProductTypeEnum'; +export * from './RequestedPage'; +export * from './SearchResult'; diff --git a/src/api/chat-history/ChatEntryType.ts b/src/api/chat-history/ChatEntryType.ts new file mode 100644 index 0000000..045f00c --- /dev/null +++ b/src/api/chat-history/ChatEntryType.ts @@ -0,0 +1,6 @@ +export class ChatEntryType +{ + public static TYPE_CHAT = 1; + public static TYPE_ROOM_INFO = 2; + public static TYPE_IM = 3; +} diff --git a/src/api/chat-history/ChatHistoryCurrentDate.ts b/src/api/chat-history/ChatHistoryCurrentDate.ts new file mode 100644 index 0000000..6947bca --- /dev/null +++ b/src/api/chat-history/ChatHistoryCurrentDate.ts @@ -0,0 +1,6 @@ +export const ChatHistoryCurrentDate = () => +{ + const currentTime = new Date(); + + return `${ currentTime.getHours().toString().padStart(2, '0') }:${ currentTime.getMinutes().toString().padStart(2, '0') }`; +} diff --git a/src/api/chat-history/IChatEntry.ts b/src/api/chat-history/IChatEntry.ts new file mode 100644 index 0000000..1bf7a52 --- /dev/null +++ b/src/api/chat-history/IChatEntry.ts @@ -0,0 +1,17 @@ +export interface IChatEntry +{ + id: number; + webId: number; + entityId: number; + name: string; + look?: string; + message?: string; + entityType?: number; + style?: number; + chatType?: number; + imageUrl?: string; + color?: string; + roomId: number; + timestamp: string; + type: number; +} diff --git a/src/api/chat-history/IRoomHistoryEntry.ts b/src/api/chat-history/IRoomHistoryEntry.ts new file mode 100644 index 0000000..4986154 --- /dev/null +++ b/src/api/chat-history/IRoomHistoryEntry.ts @@ -0,0 +1,5 @@ +export interface IRoomHistoryEntry +{ + id: number; + name: string; +} diff --git a/src/api/chat-history/MessengerHistoryCurrentDate.ts b/src/api/chat-history/MessengerHistoryCurrentDate.ts new file mode 100644 index 0000000..3aeebc5 --- /dev/null +++ b/src/api/chat-history/MessengerHistoryCurrentDate.ts @@ -0,0 +1,6 @@ +export const MessengerHistoryCurrentDate = (secondsSinceNow: number = 0) => +{ + const currentTime = secondsSinceNow ? new Date(Date.now() - secondsSinceNow * 1000) : new Date(); + + return `${ currentTime.getHours().toString().padStart(2, '0') }:${ currentTime.getMinutes().toString().padStart(2, '0') }`; +} diff --git a/src/api/chat-history/index.ts b/src/api/chat-history/index.ts new file mode 100644 index 0000000..a989374 --- /dev/null +++ b/src/api/chat-history/index.ts @@ -0,0 +1,5 @@ +export * from './ChatEntryType'; +export * from './ChatHistoryCurrentDate'; +export * from './IChatEntry'; +export * from './IRoomHistoryEntry'; +export * from './MessengerHistoryCurrentDate'; diff --git a/src/api/events/DispatchEvent.ts b/src/api/events/DispatchEvent.ts new file mode 100644 index 0000000..79e2f5c --- /dev/null +++ b/src/api/events/DispatchEvent.ts @@ -0,0 +1,3 @@ +import { IEventDispatcher, NitroEvent } from '@nitrots/nitro-renderer'; + +export const DispatchEvent = (eventDispatcher: IEventDispatcher, event: NitroEvent) => eventDispatcher.dispatchEvent(event); diff --git a/src/api/events/DispatchMainEvent.ts b/src/api/events/DispatchMainEvent.ts new file mode 100644 index 0000000..e316b30 --- /dev/null +++ b/src/api/events/DispatchMainEvent.ts @@ -0,0 +1,4 @@ +import { GetEventDispatcher, NitroEvent } from '@nitrots/nitro-renderer'; +import { DispatchEvent } from './DispatchEvent'; + +export const DispatchMainEvent = (event: NitroEvent) => DispatchEvent(GetEventDispatcher(), event); diff --git a/src/api/events/DispatchUiEvent.ts b/src/api/events/DispatchUiEvent.ts new file mode 100644 index 0000000..5200bb4 --- /dev/null +++ b/src/api/events/DispatchUiEvent.ts @@ -0,0 +1,5 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; +import { DispatchEvent } from './DispatchEvent'; +import { UI_EVENT_DISPATCHER } from './UI_EVENT_DISPATCHER'; + +export const DispatchUiEvent = (event: NitroEvent) => DispatchEvent(UI_EVENT_DISPATCHER, event); diff --git a/src/api/events/UI_EVENT_DISPATCHER.ts b/src/api/events/UI_EVENT_DISPATCHER.ts new file mode 100644 index 0000000..cb57311 --- /dev/null +++ b/src/api/events/UI_EVENT_DISPATCHER.ts @@ -0,0 +1,3 @@ +import { EventDispatcher, IEventDispatcher } from '@nitrots/nitro-renderer'; + +export const UI_EVENT_DISPATCHER: IEventDispatcher = new EventDispatcher(); diff --git a/src/api/events/index.ts b/src/api/events/index.ts new file mode 100644 index 0000000..b7c22ee --- /dev/null +++ b/src/api/events/index.ts @@ -0,0 +1,4 @@ +export * from './DispatchEvent'; +export * from './DispatchMainEvent'; +export * from './DispatchUiEvent'; +export * from './UI_EVENT_DISPATCHER'; diff --git a/src/api/friends/GetGroupChatData.ts b/src/api/friends/GetGroupChatData.ts new file mode 100644 index 0000000..75df962 --- /dev/null +++ b/src/api/friends/GetGroupChatData.ts @@ -0,0 +1,13 @@ +import { IGroupChatData } from './IGroupChatData'; + +export const GetGroupChatData = (extraData: string) => +{ + if(!extraData || !extraData.length) return null; + + const splitData = extraData.split('/'); + const username = splitData[0]; + const figure = splitData[1]; + const userId = parseInt(splitData[2]); + + return ({ username: username, figure: figure, userId: userId } as IGroupChatData); +} diff --git a/src/api/friends/IGroupChatData.ts b/src/api/friends/IGroupChatData.ts new file mode 100644 index 0000000..24a3f9c --- /dev/null +++ b/src/api/friends/IGroupChatData.ts @@ -0,0 +1,6 @@ +export interface IGroupChatData +{ + username: string; + figure: string; + userId: number; +} diff --git a/src/api/friends/MessengerFriend.ts b/src/api/friends/MessengerFriend.ts new file mode 100644 index 0000000..b5cfc88 --- /dev/null +++ b/src/api/friends/MessengerFriend.ts @@ -0,0 +1,43 @@ +import { FriendParser } from '@nitrots/nitro-renderer'; + +export class MessengerFriend +{ + public static RELATIONSHIP_NONE: number = 0; + public static RELATIONSHIP_HEART: number = 1; + public static RELATIONSHIP_SMILE: number = 2; + public static RELATIONSHIP_BOBBA: number = 3; + + public id: number = -1; + public name: string = null; + public gender: number = 0; + public online: boolean = false; + public followingAllowed: boolean = false; + public figure: string = null; + public categoryId: number = 0; + public motto: string = null; + public realName: string = null; + public lastAccess: string = null; + public persistedMessageUser: boolean = false; + public vipMember: boolean = false; + public pocketHabboUser: boolean = false; + public relationshipStatus: number = -1; + public unread: number = 0; + + public populate(parser: FriendParser): void + { + this.id = parser.id; + this.name = parser.name; + this.gender = parser.gender; + this.online = parser.online; + this.followingAllowed = parser.followingAllowed; + this.figure = parser.figure; + this.categoryId = parser.categoryId; + this.motto = parser.motto; + this.realName = parser.realName; + this.lastAccess = parser.lastAccess; + this.persistedMessageUser = parser.persistedMessageUser; + this.vipMember = parser.vipMember; + this.pocketHabboUser = parser.pocketHabboUser; + this.relationshipStatus = parser.relationshipStatus; + } +} diff --git a/src/api/friends/MessengerGroupType.ts b/src/api/friends/MessengerGroupType.ts new file mode 100644 index 0000000..d46a1b6 --- /dev/null +++ b/src/api/friends/MessengerGroupType.ts @@ -0,0 +1,5 @@ +export class MessengerGroupType +{ + public static readonly GROUP_CHAT = 0; + public static readonly PRIVATE_CHAT = 1; +} diff --git a/src/api/friends/MessengerIconState.ts b/src/api/friends/MessengerIconState.ts new file mode 100644 index 0000000..63f8c13 --- /dev/null +++ b/src/api/friends/MessengerIconState.ts @@ -0,0 +1,6 @@ +export class MessengerIconState +{ + public static HIDDEN: number = 0; + public static SHOW: number = 1; + public static UNREAD: number = 2; +} diff --git a/src/api/friends/MessengerRequest.ts b/src/api/friends/MessengerRequest.ts new file mode 100644 index 0000000..89ceec5 --- /dev/null +++ b/src/api/friends/MessengerRequest.ts @@ -0,0 +1,41 @@ +import { FriendRequestData } from '@nitrots/nitro-renderer'; + +export class MessengerRequest +{ + private _id: number; + private _name: string; + private _requesterUserId: number; + private _figureString: string; + + public populate(data: FriendRequestData): boolean + { + if(!data) return false; + + this._id = data.requestId; + this._name = data.requesterName; + this._figureString = data.figureString; + this._requesterUserId = data.requesterUserId; + + return true; + } + + public get id(): number + { + return this._id; + } + + public get name(): string + { + return this._name; + } + + public get requesterUserId(): number + { + return this._requesterUserId; + } + + public get figureString(): string + { + return this._figureString; + } +} diff --git a/src/api/friends/MessengerSettings.ts b/src/api/friends/MessengerSettings.ts new file mode 100644 index 0000000..e0fc8c2 --- /dev/null +++ b/src/api/friends/MessengerSettings.ts @@ -0,0 +1,11 @@ +import { FriendCategoryData } from '@nitrots/nitro-renderer'; + +export class MessengerSettings +{ + constructor( + public userFriendLimit: number = 0, + public normalFriendLimit: number = 0, + public extendedFriendLimit: number = 0, + public categories: FriendCategoryData[] = []) + {} +} diff --git a/src/api/friends/MessengerThread.ts b/src/api/friends/MessengerThread.ts new file mode 100644 index 0000000..405ea33 --- /dev/null +++ b/src/api/friends/MessengerThread.ts @@ -0,0 +1,96 @@ +import { GetGroupChatData } from './GetGroupChatData'; +import { MessengerFriend } from './MessengerFriend'; +import { MessengerGroupType } from './MessengerGroupType'; +import { MessengerThreadChat } from './MessengerThreadChat'; +import { MessengerThreadChatGroup } from './MessengerThreadChatGroup'; + +export class MessengerThread +{ + public static MESSAGE_RECEIVED: string = 'MT_MESSAGE_RECEIVED'; + public static THREAD_ID: number = 0; + + private _threadId: number; + private _participant: MessengerFriend; + private _groups: MessengerThreadChatGroup[]; + private _lastUpdated: Date; + private _unreadCount: number; + + constructor(participant: MessengerFriend) + { + this._threadId = ++MessengerThread.THREAD_ID; + this._participant = participant; + this._groups = []; + this._lastUpdated = new Date(); + this._unreadCount = 0; + } + + public addMessage(senderId: number, message: string, secondsSinceSent: number = 0, extraData: string = null, type: number = 0): MessengerThreadChat + { + const isGroupChat = (senderId < 0 && extraData); + const userId = isGroupChat ? GetGroupChatData(extraData).userId : senderId; + + const group = this.getLastGroup(userId); + + if(!group) return; + + if(isGroupChat) group.type = MessengerGroupType.GROUP_CHAT; + + const chat = new MessengerThreadChat(senderId, message, secondsSinceSent, extraData, type); + + group.addChat(chat); + + this._lastUpdated = new Date(); + + this._unreadCount++; + + return chat; + } + + private getLastGroup(userId: number): MessengerThreadChatGroup + { + let group = this._groups[(this._groups.length - 1)]; + + if(group && (group.userId === userId)) return group; + + group = new MessengerThreadChatGroup(userId); + + this._groups.push(group); + + return group; + } + + public setRead(): void + { + this._unreadCount = 0; + } + + public get threadId(): number + { + return this._threadId; + } + + public get participant(): MessengerFriend + { + return this._participant; + } + + public get groups(): MessengerThreadChatGroup[] + { + return this._groups; + } + + public get lastUpdated(): Date + { + return this._lastUpdated; + } + + public get unreadCount(): number + { + return this._unreadCount; + } + + public get unread(): boolean + { + return (this._unreadCount > 0); + } +} diff --git a/src/api/friends/MessengerThreadChat.ts b/src/api/friends/MessengerThreadChat.ts new file mode 100644 index 0000000..2927fec --- /dev/null +++ b/src/api/friends/MessengerThreadChat.ts @@ -0,0 +1,54 @@ +export class MessengerThreadChat +{ + public static CHAT: number = 0; + public static ROOM_INVITE: number = 1; + public static STATUS_NOTIFICATION: number = 2; + public static SECURITY_NOTIFICATION: number = 3; + + private _type: number; + private _senderId: number; + private _message: string; + private _secondsSinceSent: number; + private _extraData: string; + private _date: Date; + + constructor(senderId: number, message: string, secondsSinceSent: number = 0, extraData: string = null, type: number = 0) + { + this._type = type; + this._senderId = senderId; + this._message = message; + this._secondsSinceSent = secondsSinceSent; + this._extraData = extraData; + this._date = new Date(); + } + + public get type(): number + { + return this._type; + } + + public get senderId(): number + { + return this._senderId; + } + + public get message(): string + { + return this._message; + } + + public get secondsSinceSent(): number + { + return this._secondsSinceSent; + } + + public get extraData(): string + { + return this._extraData; + } + + public get date(): Date + { + return this._date; + } +} diff --git a/src/api/friends/MessengerThreadChatGroup.ts b/src/api/friends/MessengerThreadChatGroup.ts new file mode 100644 index 0000000..1668aed --- /dev/null +++ b/src/api/friends/MessengerThreadChatGroup.ts @@ -0,0 +1,41 @@ +import { MessengerGroupType } from './MessengerGroupType'; +import { MessengerThreadChat } from './MessengerThreadChat'; + +export class MessengerThreadChatGroup +{ + private _userId: number; + private _chats: MessengerThreadChat[]; + private _type: number; + + constructor(userId: number, type = MessengerGroupType.PRIVATE_CHAT) + { + this._userId = userId; + this._chats = []; + this._type = type; + } + + public addChat(message: MessengerThreadChat): void + { + this._chats.push(message); + } + + public get userId(): number + { + return this._userId; + } + + public get chats(): MessengerThreadChat[] + { + return this._chats; + } + + public get type(): number + { + return this._type; + } + + public set type(type: number) + { + this._type = type; + } +} diff --git a/src/api/friends/OpenMessengerChat.ts b/src/api/friends/OpenMessengerChat.ts new file mode 100644 index 0000000..6050f2f --- /dev/null +++ b/src/api/friends/OpenMessengerChat.ts @@ -0,0 +1,7 @@ +import { CreateLinkEvent } from '@nitrots/nitro-renderer'; + +export function OpenMessengerChat(friendId: number = 0): void +{ + if(friendId === 0) CreateLinkEvent('friends-messenger/toggle'); + else CreateLinkEvent(`friends-messenger/${ friendId }`); +} diff --git a/src/api/friends/index.ts b/src/api/friends/index.ts new file mode 100644 index 0000000..ce1ed60 --- /dev/null +++ b/src/api/friends/index.ts @@ -0,0 +1,11 @@ +export * from './GetGroupChatData'; +export * from './IGroupChatData'; +export * from './MessengerFriend'; +export * from './MessengerGroupType'; +export * from './MessengerIconState'; +export * from './MessengerRequest'; +export * from './MessengerSettings'; +export * from './MessengerThread'; +export * from './MessengerThreadChat'; +export * from './MessengerThreadChatGroup'; +export * from './OpenMessengerChat'; diff --git a/src/api/groups/GetGroupInformation.ts b/src/api/groups/GetGroupInformation.ts new file mode 100644 index 0000000..14fe326 --- /dev/null +++ b/src/api/groups/GetGroupInformation.ts @@ -0,0 +1,7 @@ +import { GroupInformationComposer } from '@nitrots/nitro-renderer'; +import { SendMessageComposer } from '../nitro'; + +export function GetGroupInformation(groupId: number): void +{ + SendMessageComposer(new GroupInformationComposer(groupId, true)); +} diff --git a/src/api/groups/GetGroupManager.ts b/src/api/groups/GetGroupManager.ts new file mode 100644 index 0000000..2044a45 --- /dev/null +++ b/src/api/groups/GetGroupManager.ts @@ -0,0 +1,6 @@ +import { CreateLinkEvent } from '@nitrots/nitro-renderer'; + +export function GetGroupManager(groupId: number): void +{ + CreateLinkEvent(`groups/manage/${ groupId }`); +} diff --git a/src/api/groups/GetGroupMembers.ts b/src/api/groups/GetGroupMembers.ts new file mode 100644 index 0000000..9e10b01 --- /dev/null +++ b/src/api/groups/GetGroupMembers.ts @@ -0,0 +1,7 @@ +import { CreateLinkEvent } from '@nitrots/nitro-renderer'; + +export function GetGroupMembers(groupId: number, levelId?: number): void +{ + if(!levelId) CreateLinkEvent(`group-members/${ groupId }`); + else CreateLinkEvent(`group-members/${ groupId }/${ levelId }`); +} diff --git a/src/api/groups/GroupBadgePart.ts b/src/api/groups/GroupBadgePart.ts new file mode 100644 index 0000000..09c137c --- /dev/null +++ b/src/api/groups/GroupBadgePart.ts @@ -0,0 +1,30 @@ +export class GroupBadgePart +{ + public static BASE: string = 'b'; + public static SYMBOL: string = 's'; + + public type: string; + public key: number; + public color: number; + public position: number; + + constructor(type: string, key?: number, color?: number, position?: number) + { + this.type = type; + this.key = key ? key : 0; + this.color = color ? color : 0; + this.position = position ? position : 4; + } + + public get code(): string + { + if((this.key === 0) && (this.type !== GroupBadgePart.BASE)) return null; + + return GroupBadgePart.getCode(this.type, this.key, this.color, this.position); + } + + public static getCode(type: string, key: number, color: number, position: number): string + { + return type + (key < 10 ? '0' : '') + key + (color < 10 ? '0' : '') + color + position; + } +} diff --git a/src/api/groups/GroupMembershipType.ts b/src/api/groups/GroupMembershipType.ts new file mode 100644 index 0000000..532c836 --- /dev/null +++ b/src/api/groups/GroupMembershipType.ts @@ -0,0 +1,6 @@ +export class GroupMembershipType +{ + public static NOT_MEMBER: number = 0; + public static MEMBER: number = 1; + public static REQUEST_PENDING: number = 2; +} diff --git a/src/api/groups/GroupType.ts b/src/api/groups/GroupType.ts new file mode 100644 index 0000000..58ae72c --- /dev/null +++ b/src/api/groups/GroupType.ts @@ -0,0 +1,6 @@ +export class GroupType +{ + public static REGULAR: number = 0; + public static EXCLUSIVE: number = 1; + public static PRIVATE: number = 2; +} diff --git a/src/api/groups/IGroupCustomize.ts b/src/api/groups/IGroupCustomize.ts new file mode 100644 index 0000000..44fc4ff --- /dev/null +++ b/src/api/groups/IGroupCustomize.ts @@ -0,0 +1,8 @@ +export interface IGroupCustomize +{ + badgeBases: { id: number, images: string[] }[]; + badgeSymbols: { id: number, images: string[] }[]; + badgePartColors: { id: number, color: string }[]; + groupColorsA: { id: number, color: string }[]; + groupColorsB: { id: number, color: string }[]; +} diff --git a/src/api/groups/IGroupData.ts b/src/api/groups/IGroupData.ts new file mode 100644 index 0000000..bb65b49 --- /dev/null +++ b/src/api/groups/IGroupData.ts @@ -0,0 +1,13 @@ +import { GroupBadgePart } from './GroupBadgePart'; + +export interface IGroupData +{ + groupId: number; + groupName: string; + groupDescription: string; + groupHomeroomId: number; + groupState: number; + groupCanMembersDecorate: boolean; + groupColors: number[]; + groupBadgeParts: GroupBadgePart[]; +} diff --git a/src/api/groups/ToggleFavoriteGroup.ts b/src/api/groups/ToggleFavoriteGroup.ts new file mode 100644 index 0000000..572e05d --- /dev/null +++ b/src/api/groups/ToggleFavoriteGroup.ts @@ -0,0 +1,7 @@ +import { GroupFavoriteComposer, GroupUnfavoriteComposer, HabboGroupEntryData } from '@nitrots/nitro-renderer'; +import { SendMessageComposer } from '../nitro'; + +export const ToggleFavoriteGroup = (group: HabboGroupEntryData) => +{ + SendMessageComposer(group.favourite ? new GroupUnfavoriteComposer(group.groupId) : new GroupFavoriteComposer(group.groupId)); +} diff --git a/src/api/groups/TryJoinGroup.ts b/src/api/groups/TryJoinGroup.ts new file mode 100644 index 0000000..63959bf --- /dev/null +++ b/src/api/groups/TryJoinGroup.ts @@ -0,0 +1,4 @@ +import { GroupJoinComposer } from '@nitrots/nitro-renderer'; +import { SendMessageComposer } from '../nitro'; + +export const TryJoinGroup = (groupId: number) => SendMessageComposer(new GroupJoinComposer(groupId)); diff --git a/src/api/groups/index.ts b/src/api/groups/index.ts new file mode 100644 index 0000000..4842948 --- /dev/null +++ b/src/api/groups/index.ts @@ -0,0 +1,10 @@ +export * from './GetGroupInformation'; +export * from './GetGroupManager'; +export * from './GetGroupMembers'; +export * from './GroupBadgePart'; +export * from './GroupMembershipType'; +export * from './GroupType'; +export * from './IGroupCustomize'; +export * from './IGroupData'; +export * from './ToggleFavoriteGroup'; +export * from './TryJoinGroup'; diff --git a/src/api/guide-tool/GuideSessionState.ts b/src/api/guide-tool/GuideSessionState.ts new file mode 100644 index 0000000..c5e24f3 --- /dev/null +++ b/src/api/guide-tool/GuideSessionState.ts @@ -0,0 +1,23 @@ +export class GuideSessionState +{ + public static readonly NONE: string = 'NONE'; + public static readonly ERROR: string = 'ERROR'; + public static readonly REJECTED: string = 'REJECTED'; + public static readonly USER_CREATE: string = 'USER_CREATE'; + public static readonly USER_PENDING: string = 'USER_PENDING'; + public static readonly USER_ONGOING: string = 'USER_ONGOING'; + public static readonly USER_FEEDBACK: string = 'USER_FEEDBACK'; + public static readonly USER_NO_HELPERS: string = 'USER_NO_HELPERS'; + public static readonly USER_SOMETHING_WRONG: string = 'USER_SOMETHING_WRONG'; + public static readonly USER_THANKS: string = 'USER_THANKS'; + public static readonly USER_GUIDE_DISCONNECTED: string = 'USER_GUIDE_DISCONNECTED'; + public static readonly GUIDE_TOOL_MENU: string = 'GUIDE_TOOL_MENU'; + public static readonly GUIDE_ACCEPT: string = 'GUIDE_ACCEPT'; + public static readonly GUIDE_ONGOING: string = 'GUIDE_ONGOING'; + public static readonly GUIDE_CLOSED: string = 'GUIDE_CLOSED'; + public static readonly GUARDIAN_CHAT_REVIEW_ACCEPT: string = 'GUARDIAN_CHAT_REVIEW_ACCEPT'; + public static readonly GUARDIAN_CHAT_REVIEW_WAIT_FOR_VOTERS: string = 'GUARDIAN_CHAT_REVIEW_WAIT_FOR_VOTERS'; + public static readonly GUARDIAN_CHAT_REVIEW_VOTE: string = 'GUARDIAN_CHAT_REVIEW_VOTE'; + public static readonly GUARDIAN_CHAT_REVIEW_WAIT_FOR_RESULTS: string = 'GUARDIAN_CHAT_REVIEW_WAIT_FOR_RESULTS'; + public static readonly GUARDIAN_CHAT_REVIEW_RESULTS: string = 'GUARDIAN_CHAT_REVIEW_RESULTS'; +} diff --git a/src/api/guide-tool/GuideToolMessage.ts b/src/api/guide-tool/GuideToolMessage.ts new file mode 100644 index 0000000..3810726 --- /dev/null +++ b/src/api/guide-tool/GuideToolMessage.ts @@ -0,0 +1,21 @@ +export class GuideToolMessage +{ + private _message: string; + private _roomId: number; + + constructor(message: string, roomId?: number) + { + this._message = message; + this._roomId = roomId; + } + + public get message(): string + { + return this._message; + } + + public get roomId(): number + { + return this._roomId; + } +} diff --git a/src/api/guide-tool/GuideToolMessageGroup.ts b/src/api/guide-tool/GuideToolMessageGroup.ts new file mode 100644 index 0000000..bf03c9b --- /dev/null +++ b/src/api/guide-tool/GuideToolMessageGroup.ts @@ -0,0 +1,28 @@ +import { GuideToolMessage } from './GuideToolMessage'; + +export class GuideToolMessageGroup +{ + private _userId: number; + private _messages: GuideToolMessage[]; + + constructor(userId: number) + { + this._userId = userId; + this._messages = []; + } + + public addChat(message: GuideToolMessage): void + { + this._messages.push(message); + } + + public get userId(): number + { + return this._userId; + } + + public get messages(): GuideToolMessage[] + { + return this._messages; + } +} diff --git a/src/api/guide-tool/index.ts b/src/api/guide-tool/index.ts new file mode 100644 index 0000000..1400adc --- /dev/null +++ b/src/api/guide-tool/index.ts @@ -0,0 +1,3 @@ +export * from './GuideSessionState'; +export * from './GuideToolMessage'; +export * from './GuideToolMessageGroup'; diff --git a/src/api/hc-center/ClubStatus.ts b/src/api/hc-center/ClubStatus.ts new file mode 100644 index 0000000..8200b14 --- /dev/null +++ b/src/api/hc-center/ClubStatus.ts @@ -0,0 +1,6 @@ +export class ClubStatus +{ + public static ACTIVE: string = 'active'; + public static NONE: string = 'none'; + public static EXPIRED: string = 'expired'; +} diff --git a/src/api/hc-center/GetClubBadge.ts b/src/api/hc-center/GetClubBadge.ts new file mode 100644 index 0000000..6b779e0 --- /dev/null +++ b/src/api/hc-center/GetClubBadge.ts @@ -0,0 +1,11 @@ +const DEFAULT_BADGE: string = 'HC1'; +const BADGES: string[] = [ 'ACH_VipHC1', 'ACH_VipHC2', 'ACH_VipHC3', 'ACH_VipHC4', 'ACH_VipHC5', 'HC1', 'HC2', 'HC3', 'HC4', 'HC5' ]; + +export const GetClubBadge = (badgeCodes: string[]) => +{ + let badgeCode: string = null; + + BADGES.forEach(badge => ((badgeCodes.indexOf(badge) > -1) && (badgeCode = badge))); + + return (badgeCode || DEFAULT_BADGE); +} diff --git a/src/api/hc-center/index.ts b/src/api/hc-center/index.ts new file mode 100644 index 0000000..cee8f69 --- /dev/null +++ b/src/api/hc-center/index.ts @@ -0,0 +1,2 @@ +export * from './ClubStatus'; +export * from './GetClubBadge'; diff --git a/src/api/help/CallForHelpResult.ts b/src/api/help/CallForHelpResult.ts new file mode 100644 index 0000000..37e7ea1 --- /dev/null +++ b/src/api/help/CallForHelpResult.ts @@ -0,0 +1,5 @@ +export class CallForHelpResult +{ + public static readonly TOO_MANY_PENDING_CALLS_CODE = 1; + public static readonly HAS_ABUSIVE_CALL_CODE = 2; +} diff --git a/src/api/help/GetCloseReasonKey.ts b/src/api/help/GetCloseReasonKey.ts new file mode 100644 index 0000000..520d14f --- /dev/null +++ b/src/api/help/GetCloseReasonKey.ts @@ -0,0 +1,8 @@ +export const GetCloseReasonKey = (code: number) => +{ + if(code === 1) return 'useless'; + + if(code === 2) return 'abusive'; + + return 'resolved'; +} diff --git a/src/api/help/IHelpReport.ts b/src/api/help/IHelpReport.ts new file mode 100644 index 0000000..8611707 --- /dev/null +++ b/src/api/help/IHelpReport.ts @@ -0,0 +1,19 @@ +import { IChatEntry } from '../chat-history'; + +export interface IHelpReport +{ + reportType: number; + reportedUserId: number; + reportedChats: IChatEntry[]; + cfhCategory: number; + cfhTopic: number; + roomId: number; + roomName: string; + groupId: number; + threadId: number; + messageId: number; + extraData: string; + roomObjectId: number; + message: string; + currentStep: number; +} diff --git a/src/api/help/IReportedUser.ts b/src/api/help/IReportedUser.ts new file mode 100644 index 0000000..90a3887 --- /dev/null +++ b/src/api/help/IReportedUser.ts @@ -0,0 +1,5 @@ +export interface IReportedUser +{ + id: number; + username: string; +} diff --git a/src/api/help/ReportState.ts b/src/api/help/ReportState.ts new file mode 100644 index 0000000..ae3a3bd --- /dev/null +++ b/src/api/help/ReportState.ts @@ -0,0 +1,8 @@ +export class ReportState +{ + public static readonly SELECT_USER = 0; + public static readonly SELECT_CHATS = 1; + public static readonly SELECT_TOPICS = 2; + public static readonly INPUT_REPORT_MESSAGE = 3; + public static readonly REPORT_SUMMARY = 4; +} diff --git a/src/api/help/ReportType.ts b/src/api/help/ReportType.ts new file mode 100644 index 0000000..24eb7ae --- /dev/null +++ b/src/api/help/ReportType.ts @@ -0,0 +1,11 @@ +export class ReportType +{ + public static readonly EMERGENCY = 1; + public static readonly GUIDE = 2; + public static readonly IM = 3; + public static readonly ROOM = 4; + public static readonly BULLY = 6; + public static readonly THREAD = 7; + public static readonly MESSAGE = 8; + public static readonly PHOTO = 9; +} diff --git a/src/api/help/index.ts b/src/api/help/index.ts new file mode 100644 index 0000000..6fa2045 --- /dev/null +++ b/src/api/help/index.ts @@ -0,0 +1,6 @@ +export * from './CallForHelpResult'; +export * from './GetCloseReasonKey'; +export * from './IHelpReport'; +export * from './IReportedUser'; +export * from './ReportState'; +export * from './ReportType'; diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..7089277 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,28 @@ +export * from './GetRendererVersion'; +export * from './GetUIVersion'; +export * from './achievements'; +export * from './avatar'; +export * from './camera'; +export * from './campaign'; +export * from './catalog'; +export * from './chat-history'; +export * from './events'; +export * from './friends'; +export * from './groups'; +export * from './guide-tool'; +export * from './hc-center'; +export * from './help'; +export * from './inventory'; +export * from './mod-tools'; +export * from './navigator'; +export * from './nitro'; +export * from './nitro/room'; +export * from './nitro/session'; +export * from './notification'; +export * from './purse'; +export * from './room'; +export * from './room/events'; +export * from './room/widgets'; +export * from './user'; +export * from './utils'; +export * from './wired'; diff --git a/src/api/inventory/FurniCategory.ts b/src/api/inventory/FurniCategory.ts new file mode 100644 index 0000000..6528947 --- /dev/null +++ b/src/api/inventory/FurniCategory.ts @@ -0,0 +1,26 @@ +export class FurniCategory +{ + public static DEFAULT: number = 1; + public static WALL_PAPER: number = 2; + public static FLOOR: number = 3; + public static LANDSCAPE: number = 4; + public static POST_IT: number = 5; + public static POSTER: number = 6; + public static SOUND_SET: number = 7; + public static TRAX_SONG: number = 8; + public static PRESENT: number = 9; + public static ECOTRON_BOX: number = 10; + public static TROPHY: number = 11; + public static CREDIT_FURNI: number = 12; + public static PET_SHAMPOO: number = 13; + public static PET_CUSTOM_PART: number = 14; + public static PET_CUSTOM_PART_SHAMPOO: number = 15; + public static PET_SADDLE: number = 16; + public static GUILD_FURNI: number = 17; + public static GAME_FURNI: number = 18; + public static MONSTERPLANT_SEED: number = 19; + public static MONSTERPLANT_REVIVAL: number = 20; + public static MONSTERPLANT_REBREED: number = 21; + public static MONSTERPLANT_FERTILIZE: number = 22; + public static FIGURE_PURCHASABLE_SET: number = 23; +} diff --git a/src/api/inventory/FurnitureItem.ts b/src/api/inventory/FurnitureItem.ts new file mode 100644 index 0000000..f5f2f0f --- /dev/null +++ b/src/api/inventory/FurnitureItem.ts @@ -0,0 +1,245 @@ +import { GetTickerTime, IFurnitureItemData, IObjectData } from '@nitrots/nitro-renderer'; +import { IFurnitureItem } from './IFurnitureItem'; + +export class FurnitureItem implements IFurnitureItem +{ + private _expirationTimeStamp: number; + private _isWallItem: boolean; + private _songId: number; + private _locked: boolean; + private _id: number; + private _ref: number; + private _category: number; + private _type: number; + private _stuffData: IObjectData; + private _extra: number; + private _recyclable: boolean; + private _tradeable: boolean; + private _groupable: boolean; + private _sellable: boolean; + private _secondsToExpiration: number; + private _hasRentPeriodStarted: boolean; + private _creationDay: number; + private _creationMonth: number; + private _creationYear: number; + private _slotId: string; + private _isRented: boolean; + private _flatId: number; + + constructor(parser: IFurnitureItemData) + { + if(!parser) return; + + this._locked = false; + this._id = parser.itemId; + this._type = parser.spriteId; + this._ref = parser.ref; + this._category = parser.category; + this._groupable = ((parser.isGroupable) && (!(parser.rentable))); + this._tradeable = parser.tradable; + this._recyclable = parser.isRecycleable; + this._sellable = parser.sellable; + this._stuffData = parser.stuffData; + this._extra = parser.extra; + this._secondsToExpiration = parser.secondsToExpiration; + this._expirationTimeStamp = parser.expirationTimeStamp; + this._hasRentPeriodStarted = parser.hasRentPeriodStarted; + this._creationDay = parser.creationDay; + this._creationMonth = parser.creationMonth; + this._creationYear = parser.creationYear; + this._slotId = parser.slotId; + this._songId = parser.songId; + this._flatId = parser.flatId; + this._isRented = parser.rentable; + this._isWallItem = parser.isWallItem; + } + + public get rentable(): boolean + { + return this._isRented; + } + + public get id(): number + { + return this._id; + } + + public get ref(): number + { + return this._ref; + } + + public get category(): number + { + return this._category; + } + + public get type(): number + { + return this._type; + } + + public get stuffData(): IObjectData + { + return this._stuffData; + } + + public set stuffData(k: IObjectData) + { + this._stuffData = k; + } + + public get extra(): number + { + return this._extra; + } + + public get recyclable(): boolean + { + return this._recyclable; + } + + public get isTradable(): boolean + { + return this._tradeable; + } + + public get isGroupable(): boolean + { + return this._groupable; + } + + public get sellable(): boolean + { + return this._sellable; + } + + public get secondsToExpiration(): number + { + if(this._secondsToExpiration === -1) return -1; + + let time = -1; + + if(this._hasRentPeriodStarted) + { + time = (this._secondsToExpiration - ((GetTickerTime() - this._expirationTimeStamp) / 1000)); + + if(time < 0) time = 0; + } + else + { + time = this._secondsToExpiration; + } + + return time; + } + + public get creationDay(): number + { + return this._creationDay; + } + + public get creationMonth(): number + { + return this._creationMonth; + } + + public get creationYear(): number + { + return this._creationYear; + } + + public get slotId(): string + { + return this._slotId; + } + + public get songId(): number + { + return this._songId; + } + + public get locked(): boolean + { + return this._locked; + } + + public set locked(k: boolean) + { + this._locked = k; + } + + public get flatId(): number + { + return this._flatId; + } + + public get isWallItem(): boolean + { + return this._isWallItem; + } + + public get hasRentPeriodStarted(): boolean + { + return this._hasRentPeriodStarted; + } + + public get expirationTimeStamp(): number + { + return this._expirationTimeStamp; + } + + public update(parser: IFurnitureItemData): void + { + this._type = parser.spriteId; + this._ref = parser.ref; + this._category = parser.category; + this._groupable = (parser.isGroupable && !parser.rentable); + this._tradeable = parser.tradable; + this._recyclable = parser.isRecycleable; + this._sellable = parser.sellable; + this._stuffData = parser.stuffData; + this._extra = parser.extra; + this._secondsToExpiration = parser.secondsToExpiration; + this._expirationTimeStamp = parser.expirationTimeStamp; + this._hasRentPeriodStarted = parser.hasRentPeriodStarted; + this._creationDay = parser.creationDay; + this._creationMonth = parser.creationMonth; + this._creationYear = parser.creationYear; + this._slotId = parser.slotId; + this._songId = parser.songId; + this._flatId = parser.flatId; + this._isRented = parser.rentable; + this._isWallItem = parser.isWallItem; + } + + public clone(): FurnitureItem + { + const item = new FurnitureItem(null); + + item._expirationTimeStamp = this._expirationTimeStamp; + item._isWallItem = this._isWallItem; + item._songId = this._songId; + item._locked = this._locked; + item._id = this._id; + item._ref = this._ref; + item._category = this._category; + item._type = this._type; + item._stuffData = this._stuffData; + item._extra = this._extra; + item._recyclable = this._recyclable; + item._tradeable = this._tradeable; + item._groupable = this._groupable; + item._sellable = this._sellable; + item._secondsToExpiration = this._secondsToExpiration; + item._hasRentPeriodStarted = this._hasRentPeriodStarted; + item._creationDay = this._creationDay; + item._creationMonth = this._creationMonth; + item._creationYear = this._creationYear; + item._slotId = this._slotId; + item._isRented = this._isRented; + item._flatId = this._flatId; + + return item; + } +} diff --git a/src/api/inventory/FurnitureUtilities.ts b/src/api/inventory/FurnitureUtilities.ts new file mode 100644 index 0000000..1c12803 --- /dev/null +++ b/src/api/inventory/FurnitureUtilities.ts @@ -0,0 +1,171 @@ +import { FurnitureListItemParser, GetRoomEngine, IObjectData } from '@nitrots/nitro-renderer'; +import { FurniCategory } from './FurniCategory'; +import { FurnitureItem } from './FurnitureItem'; +import { GroupItem } from './GroupItem'; + +export const createGroupItem = (type: number, category: number, stuffData: IObjectData, extra: number = NaN) => new GroupItem(type, category, GetRoomEngine(), stuffData, extra); + +const addSingleFurnitureItem = (set: GroupItem[], item: FurnitureItem, unseen: boolean) => +{ + const groupItems: GroupItem[] = []; + + for(const groupItem of set) + { + if(groupItem.type === item.type) groupItems.push(groupItem); + } + + for(const groupItem of groupItems) + { + if(groupItem.getItemById(item.id)) return groupItem; + } + + const groupItem = createGroupItem(item.type, item.category, item.stuffData, item.extra); + + groupItem.push(item); + + if(unseen) + { + groupItem.hasUnseenItems = true; + + set.unshift(groupItem); + } + else + { + set.push(groupItem); + } + + return groupItem; +} + +const addGroupableFurnitureItem = (set: GroupItem[], item: FurnitureItem, unseen: boolean) => +{ + let existingGroup: GroupItem = null; + + for(const groupItem of set) + { + if((groupItem.type === item.type) && (groupItem.isWallItem === item.isWallItem) && groupItem.isGroupable) + { + if(item.category === FurniCategory.POSTER) + { + if(groupItem.stuffData.getLegacyString() === item.stuffData.getLegacyString()) + { + existingGroup = groupItem; + + break; + } + } + + else if(item.category === FurniCategory.GUILD_FURNI) + { + if(item.stuffData.compare(groupItem.stuffData)) + { + existingGroup = groupItem; + + break; + } + } + + else + { + existingGroup = groupItem; + + break; + } + } + } + + if(existingGroup) + { + existingGroup.push(item); + + if(unseen) + { + existingGroup.hasUnseenItems = true; + + const index = set.indexOf(existingGroup); + + if(index >= 0) set.splice(index, 1); + + set.unshift(existingGroup); + } + + return existingGroup; + } + + existingGroup = createGroupItem(item.type, item.category, item.stuffData, item.extra); + + existingGroup.push(item); + + if(unseen) + { + existingGroup.hasUnseenItems = true; + + set.unshift(existingGroup); + } + else + { + set.push(existingGroup); + } + + return existingGroup; +} + +export const addFurnitureItem = (set: GroupItem[], item: FurnitureItem, unseen: boolean) => +{ + if(!item.isGroupable) + { + addSingleFurnitureItem(set, item, unseen); + } + else + { + addGroupableFurnitureItem(set, item, unseen); + } +} + +export const mergeFurniFragments = (fragment: Map, totalFragments: number, fragmentNumber: number, fragments: Map[]) => +{ + if(totalFragments === 1) return fragment; + + fragments[fragmentNumber] = fragment; + + for(const frag of fragments) + { + if(!frag) return null; + } + + const merged: Map = new Map(); + + for(const frag of fragments) + { + for(const [ key, value ] of frag) merged.set(key, value); + + frag.clear(); + } + + fragments = null; + + return merged; +} + +export const getAllItemIds = (groupItems: GroupItem[]) => +{ + const itemIds: number[] = []; + + for(const groupItem of groupItems) + { + let totalCount = groupItem.getTotalCount(); + + if(groupItem.category === FurniCategory.POST_IT) totalCount = 1; + + let i = 0; + + while(i < totalCount) + { + itemIds.push(groupItem.getItemByIndex(i).id); + + i++; + } + } + + return itemIds; +} diff --git a/src/api/inventory/GroupItem.ts b/src/api/inventory/GroupItem.ts new file mode 100644 index 0000000..8569321 --- /dev/null +++ b/src/api/inventory/GroupItem.ts @@ -0,0 +1,461 @@ +import { IObjectData, IRoomEngine } from '@nitrots/nitro-renderer'; +import { LocalizeText } from '../utils'; +import { FurniCategory } from './FurniCategory'; +import { FurnitureItem } from './FurnitureItem'; +import { IFurnitureItem } from './IFurnitureItem'; + +export class GroupItem +{ + private _type: number; + private _category: number; + private _roomEngine: IRoomEngine; + private _stuffData: IObjectData; + private _extra: number; + private _isWallItem: boolean; + private _iconUrl: string; + private _name: string; + private _description: string; + private _locked: boolean; + private _selected: boolean; + private _hasUnseenItems: boolean; + private _items: FurnitureItem[]; + + constructor(type: number = -1, category: number = -1, roomEngine: IRoomEngine = null, stuffData: IObjectData = null, extra: number = -1) + { + this._type = type; + this._category = category; + this._roomEngine = roomEngine; + this._stuffData = stuffData; + this._extra = extra; + this._isWallItem = false; + this._iconUrl = null; + this._name = null; + this._description = null; + this._locked = false; + this._selected = false; + this._hasUnseenItems = false; + this._items = []; + } + + public clone(): GroupItem + { + const groupItem = new GroupItem(); + + groupItem._type = this._type; + groupItem._category = this._category; + groupItem._roomEngine = this._roomEngine; + groupItem._stuffData = this._stuffData; + groupItem._extra = this._extra; + groupItem._isWallItem = this._isWallItem; + groupItem._iconUrl = this._iconUrl; + groupItem._name = this._name; + groupItem._description = this._description; + groupItem._locked = this._locked; + groupItem._selected = this._selected; + groupItem._hasUnseenItems = this._hasUnseenItems; + groupItem._items = this._items; + + return groupItem; + } + + public prepareGroup(): void + { + this.setIcon(); + this.setName(); + this.setDescription(); + } + + public dispose(): void + { + + } + + public getItemByIndex(index: number): FurnitureItem + { + return this._items[index]; + } + + public getItemById(id: number): FurnitureItem + { + for(const item of this._items) + { + if(item.id !== id) continue; + + return item; + } + + return null; + } + + public getTradeItems(count: number): IFurnitureItem[] + { + const items: IFurnitureItem[] = []; + + const furnitureItem = this.getLastItem(); + + if(!furnitureItem) return items; + + let found = 0; + let i = 0; + + while(i < this._items.length) + { + if(found >= count) break; + + const item = this.getItemByIndex(i); + + if(!item.locked && item.isTradable && (item.type === furnitureItem.type)) + { + items.push(item); + + found++; + } + + i++; + } + + return items; + } + + public push(item: FurnitureItem): void + { + const items = [ ...this._items ]; + + let index = 0; + + while(index < items.length) + { + let existingItem = items[index]; + + if(existingItem.id === item.id) + { + existingItem = existingItem.clone(); + + existingItem.locked = false; + + items.splice(index, 1); + + items.push(existingItem); + + this._items = items; + + return; + } + + index++; + } + + items.push(item); + + this._items = items; + + if(this._items.length === 1) this.prepareGroup(); + } + + public pop(): FurnitureItem + { + const items = [ ...this._items ]; + + let item: FurnitureItem = null; + + if(items.length > 0) + { + const index = (items.length - 1); + + item = items[index]; + + items.splice(index, 1); + } + + this._items = items; + + return item; + } + + public remove(k: number): FurnitureItem + { + const items = [ ...this._items ]; + + let index = 0; + + while(index < items.length) + { + let existingItem = items[index]; + + if(existingItem.id === k) + { + items.splice(index, 1); + + this._items = items; + + return existingItem; + } + + index++; + } + + return null; + } + + public getTotalCount(): number + { + if(this._category === FurniCategory.POST_IT) + { + let count = 0; + let index = 0; + + while(index < this._items.length) + { + const item = this.getItemByIndex(index); + + count = (count + parseInt(item.stuffData.getLegacyString())); + + index++; + } + + return count; + } + + return this._items.length; + } + + public getUnlockedCount(): number + { + if(this.category === FurniCategory.POST_IT) return this.getTotalCount(); + + let count = 0; + let index = 0; + + while(index < this._items.length) + { + const item = this.getItemByIndex(index); + + if(!item.locked) count++; + + index++; + } + + return count; + } + + public getLastItem(): FurnitureItem + { + if(!this._items.length) return null; + + const item = this.getItemByIndex((this._items.length - 1)); + + return item; + } + + public unlockAllItems(): void + { + const items = [ ...this._items ]; + + let index = 0; + + while(index < items.length) + { + const item = items[index]; + + if(item.locked) + { + const newItem = item.clone(); + + newItem.locked = false; + + items[index] = newItem; + } + + index++; + } + + this._items = items; + } + + public lockItemIds(itemIds: number[]): boolean + { + const items = [ ...this._items ]; + + let index = 0; + let updated = false; + + while(index < items.length) + { + const item = items[index]; + const locked = (itemIds.indexOf(item.ref) >= 0); + + if(item.locked !== locked) + { + updated = true; + + const newItem = item.clone(); + + newItem.locked = locked; + + items[index] = newItem; + } + + index++; + } + + this._items = items; + + return updated; + } + + private setName(): void + { + const k = this.getLastItem(); + + if(!k) + { + this._name = ''; + + return; + } + + let key = ''; + + switch(this._category) + { + case FurniCategory.POSTER: + key = (('poster_' + k.stuffData.getLegacyString()) + '_name'); + break; + case FurniCategory.TRAX_SONG: + this._name = 'SONG_NAME'; + return; + default: + if(this.isWallItem) + { + key = ('wallItem.name.' + k.type); + } + else + { + key = ('roomItem.name.' + k.type); + } + } + + this._name = LocalizeText(key); + } + + private setDescription(): void + { + this._description = ''; + } + + private setIcon(): void + { + if(this._iconUrl) return; + + let url = null; + + if(this.isWallItem) + { + url = this._roomEngine.getFurnitureWallIconUrl(this._type, this._stuffData.getLegacyString()); + } + else + { + url = this._roomEngine.getFurnitureFloorIconUrl(this._type); + } + + if(!url) return; + + this._iconUrl = url; + } + + public get type(): number + { + return this._type; + } + + public get category(): number + { + return this._category; + } + + public get stuffData(): IObjectData + { + return this._stuffData; + } + + public get extra(): number + { + return this._extra; + } + + public get iconUrl(): string + { + return this._iconUrl; + } + + public get name(): string + { + return this._name; + } + + public get description(): string + { + return this._description; + } + + public get hasUnseenItems(): boolean + { + return this._hasUnseenItems; + } + + public set hasUnseenItems(flag: boolean) + { + this._hasUnseenItems = flag; + } + + public get locked(): boolean + { + return this._locked; + } + + public set locked(flag: boolean) + { + this._locked = flag; + } + + public get selected(): boolean + { + return this._selected; + } + + public set selected(flag: boolean) + { + this._selected = flag; + } + + public get isWallItem(): boolean + { + const item = this.getItemByIndex(0); + + return (item ? item.isWallItem : false); + } + + public get isGroupable(): boolean + { + const item = this.getItemByIndex(0); + + return (item ? item.isGroupable : false); + } + + public get isSellable(): boolean + { + const item = this.getItemByIndex(0); + + return (item ? item.sellable : false); + } + + public get items(): FurnitureItem[] + { + return this._items; + } + + public set items(items: FurnitureItem[]) + { + this._items = items; + } +} diff --git a/src/api/inventory/IBotItem.ts b/src/api/inventory/IBotItem.ts new file mode 100644 index 0000000..0a370ba --- /dev/null +++ b/src/api/inventory/IBotItem.ts @@ -0,0 +1,6 @@ +import { BotData } from '@nitrots/nitro-renderer'; + +export interface IBotItem +{ + botData: BotData; +} diff --git a/src/api/inventory/IFurnitureItem.ts b/src/api/inventory/IFurnitureItem.ts new file mode 100644 index 0000000..435597d --- /dev/null +++ b/src/api/inventory/IFurnitureItem.ts @@ -0,0 +1,17 @@ +import { IObjectData } from '@nitrots/nitro-renderer'; + +export interface IFurnitureItem +{ + id: number; + ref: number; + type: number; + stuffData: IObjectData; + extra: number; + category: number; + recyclable: boolean; + isTradable: boolean; + isGroupable: boolean; + sellable: boolean; + locked: boolean; + isWallItem: boolean; +} diff --git a/src/api/inventory/IPetItem.ts b/src/api/inventory/IPetItem.ts new file mode 100644 index 0000000..910d5df --- /dev/null +++ b/src/api/inventory/IPetItem.ts @@ -0,0 +1,6 @@ +import { PetData } from '@nitrots/nitro-renderer'; + +export interface IPetItem +{ + petData: PetData; +} diff --git a/src/api/inventory/IUnseenItemTracker.ts b/src/api/inventory/IUnseenItemTracker.ts new file mode 100644 index 0000000..8a70a16 --- /dev/null +++ b/src/api/inventory/IUnseenItemTracker.ts @@ -0,0 +1,12 @@ +export interface IUnseenItemTracker +{ + dispose(): void; + resetCategory(category: number): boolean; + resetItems(category: number, itemIds: number[]): boolean; + isUnseen(category: number, itemId: number): boolean; + removeUnseen(category: number, itemId: number): boolean; + getIds(category: number): number[]; + getCount(category: number): number; + getFullCount(): number; + addItems(category: number, itemIds: number[]): void; +} diff --git a/src/api/inventory/InventoryUtilities.ts b/src/api/inventory/InventoryUtilities.ts new file mode 100644 index 0000000..c383e07 --- /dev/null +++ b/src/api/inventory/InventoryUtilities.ts @@ -0,0 +1,117 @@ +import { CreateLinkEvent, FurniturePlacePaintComposer, GetRoomEngine, GetRoomSessionManager, RoomObjectCategory, RoomObjectPlacementSource, RoomObjectType } from '@nitrots/nitro-renderer'; +import { SendMessageComposer } from '../nitro'; +import { FurniCategory } from './FurniCategory'; +import { GroupItem } from './GroupItem'; +import { IBotItem } from './IBotItem'; +import { IPetItem } from './IPetItem'; + +let objectMoverRequested = false; +let itemIdInPlacing = -1; + +export const isObjectMoverRequested = () => objectMoverRequested; + +export const setObjectMoverRequested = (flag: boolean) => objectMoverRequested = flag; + +export const getPlacingItemId = () => itemIdInPlacing; + +export const setPlacingItemId = (id: number) => (itemIdInPlacing = id); + +export const cancelRoomObjectPlacement = () => +{ + if(getPlacingItemId() === -1) return; + + GetRoomEngine().cancelRoomObjectPlacement(); + + setPlacingItemId(-1); + setObjectMoverRequested(false); +} + +export const attemptPetPlacement = (petItem: IPetItem, flag: boolean = false) => +{ + const petData = petItem.petData; + + if(!petData) return false; + + const session = GetRoomSessionManager().getSession(1); + + if(!session) return false; + + if(!session.isRoomOwner && !session.allowPets) return false; + + CreateLinkEvent('inventory/hide'); + + if(GetRoomEngine().processRoomObjectPlacement(RoomObjectPlacementSource.INVENTORY, -(petData.id), RoomObjectCategory.UNIT, RoomObjectType.PET, petData.figureData.figuredata)) + { + setPlacingItemId(petData.id); + setObjectMoverRequested(true); + } + + return true; +} + +export const attemptItemPlacement = (groupItem: GroupItem, flag: boolean = false) => +{ + if(!groupItem || !groupItem.getUnlockedCount()) return false; + + const item = groupItem.getLastItem(); + + if(!item) return false; + + if((item.category === FurniCategory.FLOOR) || (item.category === FurniCategory.WALL_PAPER) || (item.category === FurniCategory.LANDSCAPE)) + { + if(flag) return false; + + SendMessageComposer(new FurniturePlacePaintComposer(item.id)); + + return false; + } + else + { + CreateLinkEvent('inventory/hide'); + + let category = 0; + let isMoving = false; + + if(item.isWallItem) category = RoomObjectCategory.WALL; + else category = RoomObjectCategory.FLOOR; + + if((item.category === FurniCategory.POSTER)) // or external image from furnidata + { + isMoving = GetRoomEngine().processRoomObjectPlacement(RoomObjectPlacementSource.INVENTORY, item.id, category, item.type, item.stuffData.getLegacyString()); + } + else + { + isMoving = GetRoomEngine().processRoomObjectPlacement(RoomObjectPlacementSource.INVENTORY, item.id, category, item.type, item.extra.toString(), item.stuffData); + } + + if(isMoving) + { + setPlacingItemId(item.ref); + setObjectMoverRequested(true); + } + } + + return true; +} + + +export const attemptBotPlacement = (botItem: IBotItem, flag: boolean = false) => +{ + const botData = botItem.botData; + + if(!botData) return false; + + const session = GetRoomSessionManager().getSession(1); + + if(!session || !session.isRoomOwner) return false; + + CreateLinkEvent('inventory/hide'); + + if(GetRoomEngine().processRoomObjectPlacement(RoomObjectPlacementSource.INVENTORY, -(botData.id), RoomObjectCategory.UNIT, RoomObjectType.RENTABLE_BOT, botData.figure)) + { + setPlacingItemId(botData.id); + setObjectMoverRequested(true); + } + + return true; +} diff --git a/src/api/inventory/PetUtilities.ts b/src/api/inventory/PetUtilities.ts new file mode 100644 index 0000000..bee286a --- /dev/null +++ b/src/api/inventory/PetUtilities.ts @@ -0,0 +1,103 @@ +import { CreateLinkEvent, PetData } from '@nitrots/nitro-renderer'; +import { IPetItem } from './IPetItem'; +import { cancelRoomObjectPlacement, getPlacingItemId } from './InventoryUtilities'; +import { UnseenItemCategory } from './UnseenItemCategory'; + +export const getAllPetIds = (petItems: IPetItem[]) => petItems.map(item => item.petData.id); + +export const addSinglePetItem = (petData: PetData, set: IPetItem[], unseen: boolean = true) => +{ + const petItem = { petData }; + + if(unseen) + { + //petItem.isUnseen = true; + + set.unshift(petItem); + } + else + { + set.push(petItem); + } + + return petItem; +} + +export const removePetItemById = (id: number, set: IPetItem[]) => +{ + let index = 0; + + while(index < set.length) + { + const petItem = set[index]; + + if(petItem && (petItem.petData.id === id)) + { + if(getPlacingItemId() === petItem.petData.id) + { + cancelRoomObjectPlacement(); + + CreateLinkEvent('inventory/open'); + } + + set.splice(index, 1); + + return petItem; + } + + index++; + } + + return null; +} + +export const processPetFragment = (set: IPetItem[], fragment: Map, isUnseen: (category: number, itemId: number) => boolean) => +{ + const existingIds = getAllPetIds(set); + const addedIds: number[] = []; + const removedIds: number[] = []; + + for(const key of fragment.keys()) (existingIds.indexOf(key) === -1) && addedIds.push(key); + + for(const itemId of existingIds) (!fragment.get(itemId)) && removedIds.push(itemId); + + const emptyExistingSet = (existingIds.length === 0); + + for(const id of removedIds) removePetItemById(id, set); + + for(const id of addedIds) + { + const parser = fragment.get(id); + + if(!parser) continue; + + addSinglePetItem(parser, set, isUnseen(UnseenItemCategory.PET, parser.id)); + } + + return set; +} + +export const mergePetFragments = (fragment: Map, totalFragments: number, fragmentNumber: number, fragments: Map[]) => +{ + if(totalFragments === 1) return fragment; + + fragments[fragmentNumber] = fragment; + + for(const frag of fragments) + { + if(!frag) return null; + } + + const merged: Map = new Map(); + + for(const frag of fragments) + { + for(const [ key, value ] of frag) merged.set(key, value); + + frag.clear(); + } + + fragments = null; + + return merged; +} diff --git a/src/api/inventory/TradeState.ts b/src/api/inventory/TradeState.ts new file mode 100644 index 0000000..3df418b --- /dev/null +++ b/src/api/inventory/TradeState.ts @@ -0,0 +1,10 @@ +export class TradeState +{ + public static TRADING_STATE_READY: number = 0; + public static TRADING_STATE_RUNNING: number = 1; + public static TRADING_STATE_COUNTDOWN: number = 2; + public static TRADING_STATE_CONFIRMING: number = 3; + public static TRADING_STATE_CONFIRMED: number = 4; + public static TRADING_STATE_COMPLETED: number = 5; + public static TRADING_STATE_CANCELLED: number = 6; +} diff --git a/src/api/inventory/TradeUserData.ts b/src/api/inventory/TradeUserData.ts new file mode 100644 index 0000000..452c7ee --- /dev/null +++ b/src/api/inventory/TradeUserData.ts @@ -0,0 +1,15 @@ +import { AdvancedMap } from '@nitrots/nitro-renderer'; +import { GroupItem } from './GroupItem'; + +export class TradeUserData +{ + constructor( + public userId: number = -1, + public userName: string = '', + public userItems: AdvancedMap = new AdvancedMap(), + public itemCount: number = 0, + public creditsCount: number = 0, + public accepts: boolean = false, + public canTrade: boolean = false) + {} +} diff --git a/src/api/inventory/TradingNotificationType.ts b/src/api/inventory/TradingNotificationType.ts new file mode 100644 index 0000000..4aed490 --- /dev/null +++ b/src/api/inventory/TradingNotificationType.ts @@ -0,0 +1,12 @@ +export class TradingNotificationType +{ + public static ALERT_SCAM: number = 0; + public static HOTEL_TRADING_DISABLED = 1; + public static YOU_NOT_ALLOWED: number = 2; + public static THEY_NOT_ALLOWED: number = 4; + public static ROOM_DISABLED: number = 6; + public static YOU_OPEN: number = 7; + public static THEY_OPEN: number = 8; + public static ERROR_WHILE_COMMIT: number = 9; + public static THEY_CANCELLED: number = 10; +} diff --git a/src/api/inventory/TradingUtilities.ts b/src/api/inventory/TradingUtilities.ts new file mode 100644 index 0000000..cd18fba --- /dev/null +++ b/src/api/inventory/TradingUtilities.ts @@ -0,0 +1,70 @@ +import { AdvancedMap, GetSessionDataManager, IObjectData, ItemDataStructure, StringDataType } from '@nitrots/nitro-renderer'; +import { FurniCategory } from './FurniCategory'; +import { FurnitureItem } from './FurnitureItem'; +import { createGroupItem } from './FurnitureUtilities'; +import { GroupItem } from './GroupItem'; + +const isExternalImage = (spriteId: number) => GetSessionDataManager().getWallItemData(spriteId)?.isExternalImage || false; + +export const parseTradeItems = (items: ItemDataStructure[]) => +{ + const existingItems = new AdvancedMap(); + const totalItems = items.length; + + if(totalItems) + { + for(const item of items) + { + const spriteId = item.spriteId; + const category = item.category; + + let name = (item.furniType + spriteId); + + if(!item.isGroupable || isExternalImage(spriteId)) + { + name = ('itemid' + item.itemId); + } + + if(item.category === FurniCategory.POSTER) + { + name = (item.itemId + 'poster' + item.stuffData.getLegacyString()); + } + + else if(item.category === FurniCategory.GUILD_FURNI) + { + name = ''; + } + + let groupItem = ((item.isGroupable && !isExternalImage(item.spriteId)) ? existingItems.getValue(name) : null); + + if(!groupItem) + { + groupItem = createGroupItem(spriteId, category, item.stuffData); + + existingItems.add(name, groupItem); + } + + groupItem.push(new FurnitureItem(item)); + } + } + + return existingItems; +} + +export const getGuildFurniType = (spriteId: number, stuffData: IObjectData) => +{ + let type = spriteId.toString(); + + if(!(stuffData instanceof StringDataType)) return type; + + let i = 1; + + while(i < 5) + { + type = (type + (',' + stuffData.getValue(i))); + + i++; + } + + return type; +} diff --git a/src/api/inventory/UnseenItemCategory.ts b/src/api/inventory/UnseenItemCategory.ts new file mode 100644 index 0000000..cbd7e9b --- /dev/null +++ b/src/api/inventory/UnseenItemCategory.ts @@ -0,0 +1,9 @@ +export class UnseenItemCategory +{ + public static FURNI: number = 1; + public static RENTABLE: number = 2; + public static PET: number = 3; + public static BADGE: number = 4; + public static BOT: number = 5; + public static GAMES: number = 6; +} diff --git a/src/api/inventory/index.ts b/src/api/inventory/index.ts new file mode 100644 index 0000000..6a245d7 --- /dev/null +++ b/src/api/inventory/index.ts @@ -0,0 +1,15 @@ +export * from './FurniCategory'; +export * from './FurnitureItem'; +export * from './FurnitureUtilities'; +export * from './GroupItem'; +export * from './IBotItem'; +export * from './IFurnitureItem'; +export * from './IPetItem'; +export * from './IUnseenItemTracker'; +export * from './InventoryUtilities'; +export * from './PetUtilities'; +export * from './TradeState'; +export * from './TradeUserData'; +export * from './TradingNotificationType'; +export * from './TradingUtilities'; +export * from './UnseenItemCategory'; diff --git a/src/api/mod-tools/GetIssueCategoryName.ts b/src/api/mod-tools/GetIssueCategoryName.ts new file mode 100644 index 0000000..81a3f86 --- /dev/null +++ b/src/api/mod-tools/GetIssueCategoryName.ts @@ -0,0 +1,35 @@ +export const GetIssueCategoryName = (categoryId: number) => +{ + switch(categoryId) + { + case 1: + case 2: + return 'Normal'; + case 3: + return 'Automatic'; + case 4: + return 'Automatic IM'; + case 5: + return 'Guide System'; + case 6: + return 'IM'; + case 7: + return 'Room'; + case 8: + return 'Panic'; + case 9: + return 'Guardian'; + case 10: + return 'Automatic Helper'; + case 11: + return 'Discussion'; + case 12: + return 'Selfie'; + case 14: + return 'Photo'; + case 15: + return 'Ambassador'; + } + + return 'Unknown'; +} diff --git a/src/api/mod-tools/ISelectedUser.ts b/src/api/mod-tools/ISelectedUser.ts new file mode 100644 index 0000000..4f6e76b --- /dev/null +++ b/src/api/mod-tools/ISelectedUser.ts @@ -0,0 +1,5 @@ +export interface ISelectedUser +{ + userId: number; + username: string; +} diff --git a/src/api/mod-tools/IUserInfo.ts b/src/api/mod-tools/IUserInfo.ts new file mode 100644 index 0000000..8d49aa7 --- /dev/null +++ b/src/api/mod-tools/IUserInfo.ts @@ -0,0 +1,6 @@ +export interface IUserInfo +{ + nameKey: string; + nameKeyFallback: string; + value: string; +} diff --git a/src/api/mod-tools/ModActionDefinition.ts b/src/api/mod-tools/ModActionDefinition.ts new file mode 100644 index 0000000..b28aa9c --- /dev/null +++ b/src/api/mod-tools/ModActionDefinition.ts @@ -0,0 +1,49 @@ +export class ModActionDefinition +{ + public static ALERT: number = 1; + public static MUTE: number = 2; + public static BAN: number = 3; + public static KICK: number = 4; + public static TRADE_LOCK: number = 5; + public static MESSAGE: number = 6; + + private readonly _actionId: number; + private readonly _name: string; + private readonly _actionType: number; + private readonly _sanctionTypeId: number; + private readonly _actionLengthHours: number; + + constructor(actionId: number, actionName: string, actionType: number, sanctionTypeId: number, actionLengthHours:number) + { + this._actionId = actionId; + this._name = actionName; + this._actionType = actionType; + this._sanctionTypeId = sanctionTypeId; + this._actionLengthHours = actionLengthHours; + } + + public get actionId(): number + { + return this._actionId; + } + + public get name(): string + { + return this._name; + } + + public get actionType(): number + { + return this._actionType; + } + + public get sanctionTypeId(): number + { + return this._sanctionTypeId; + } + + public get actionLengthHours(): number + { + return this._actionLengthHours; + } +} diff --git a/src/api/mod-tools/index.ts b/src/api/mod-tools/index.ts new file mode 100644 index 0000000..004bbaa --- /dev/null +++ b/src/api/mod-tools/index.ts @@ -0,0 +1,4 @@ +export * from './GetIssueCategoryName'; +export * from './ISelectedUser'; +export * from './IUserInfo'; +export * from './ModActionDefinition'; diff --git a/src/api/navigator/DoorStateType.ts b/src/api/navigator/DoorStateType.ts new file mode 100644 index 0000000..1f8a8ef --- /dev/null +++ b/src/api/navigator/DoorStateType.ts @@ -0,0 +1,12 @@ +export class DoorStateType +{ + public static NONE: number = 0; + public static START_DOORBELL: number = 1; + public static START_PASSWORD: number = 2; + public static STATE_PENDING_SERVER: number = 3; + public static UPDATE_STATE: number = 4; + public static STATE_WAITING: number = 5; + public static STATE_NO_ANSWER: number = 6; + public static STATE_WRONG_PASSWORD: number = 7; + public static STATE_ACCEPTED: number = 8; +} diff --git a/src/api/navigator/INavigatorData.ts b/src/api/navigator/INavigatorData.ts new file mode 100644 index 0000000..e50b6fe --- /dev/null +++ b/src/api/navigator/INavigatorData.ts @@ -0,0 +1,17 @@ +import { RoomDataParser } from '@nitrots/nitro-renderer'; + +export interface INavigatorData +{ + homeRoomId: number; + settingsReceived: boolean; + enteredGuestRoom: RoomDataParser; + currentRoomOwner: boolean; + currentRoomId: number; + currentRoomIsStaffPick: boolean; + createdFlatId: number; + avatarId: number; + roomPicker: boolean; + eventMod: boolean; + currentRoomRating: number; + canRate: boolean; +} diff --git a/src/api/navigator/INavigatorSearchFilter.ts b/src/api/navigator/INavigatorSearchFilter.ts new file mode 100644 index 0000000..179d5d5 --- /dev/null +++ b/src/api/navigator/INavigatorSearchFilter.ts @@ -0,0 +1,5 @@ +export interface INavigatorSearchFilter +{ + name: string; + query: string; +} diff --git a/src/api/navigator/IRoomChatSettings.ts b/src/api/navigator/IRoomChatSettings.ts new file mode 100644 index 0000000..aee426c --- /dev/null +++ b/src/api/navigator/IRoomChatSettings.ts @@ -0,0 +1,8 @@ +export interface IRoomChatSettings +{ + mode: number; + weight: number; + speed: number; + distance: number; + protection: number; +} diff --git a/src/api/navigator/IRoomData.ts b/src/api/navigator/IRoomData.ts new file mode 100644 index 0000000..9146314 --- /dev/null +++ b/src/api/navigator/IRoomData.ts @@ -0,0 +1,23 @@ +import { IRoomChatSettings } from './IRoomChatSettings'; +import { IRoomModerationSettings } from './IRoomModerationSettings'; + +export interface IRoomData +{ + roomId: number; + roomName: string; + roomDescription: string; + categoryId: number; + userCount: number; + tags: string[]; + tradeState: number; + allowWalkthrough: boolean; + lockState: number; + password: string; + allowPets: boolean; + allowPetsEat: boolean; + hideWalls: boolean; + wallThickness: number; + floorThickness: number; + chatSettings: IRoomChatSettings; + moderationSettings: IRoomModerationSettings; +} diff --git a/src/api/navigator/IRoomModel.ts b/src/api/navigator/IRoomModel.ts new file mode 100644 index 0000000..73dfe27 --- /dev/null +++ b/src/api/navigator/IRoomModel.ts @@ -0,0 +1,6 @@ +export interface IRoomModel +{ + clubLevel: number; + tileSize: number; + name: string; +} diff --git a/src/api/navigator/IRoomModerationSettings.ts b/src/api/navigator/IRoomModerationSettings.ts new file mode 100644 index 0000000..266fe47 --- /dev/null +++ b/src/api/navigator/IRoomModerationSettings.ts @@ -0,0 +1,6 @@ +export interface IRoomModerationSettings +{ + allowMute: number; + allowKick: number; + allowBan: number; +} diff --git a/src/api/navigator/NavigatorSearchResultViewDisplayMode.ts b/src/api/navigator/NavigatorSearchResultViewDisplayMode.ts new file mode 100644 index 0000000..b532d1a --- /dev/null +++ b/src/api/navigator/NavigatorSearchResultViewDisplayMode.ts @@ -0,0 +1,6 @@ +export class NavigatorSearchResultViewDisplayMode +{ + public static readonly LIST: number = 0; + public static readonly THUMBNAILS: number = 1; + public static readonly FORCED_THUMBNAILS: number = 2; +} diff --git a/src/api/navigator/RoomInfoData.ts b/src/api/navigator/RoomInfoData.ts new file mode 100644 index 0000000..fc0a93b --- /dev/null +++ b/src/api/navigator/RoomInfoData.ts @@ -0,0 +1,60 @@ +import { RoomDataParser } from '@nitrots/nitro-renderer'; + +export class RoomInfoData +{ + private _enteredGuestRoom: RoomDataParser = null; + private _createdRoomId: number = 0; + private _currentRoomId: number = 0; + private _currentRoomOwner: boolean = false; + private _canRate: boolean = false; + + public get enteredGuestRoom(): RoomDataParser + { + return this._enteredGuestRoom; + } + + public set enteredGuestRoom(data: RoomDataParser) + { + this._enteredGuestRoom = data; + } + + public get createdRoomId(): number + { + return this._createdRoomId; + } + + public set createdRoomId(id: number) + { + this._createdRoomId = id; + } + + public get currentRoomId(): number + { + return this._currentRoomId; + } + + public set currentRoomId(id: number) + { + this._currentRoomId = id; + } + + public get currentRoomOwner(): boolean + { + return this._currentRoomOwner; + } + + public set currentRoomOwner(flag: boolean) + { + this._currentRoomOwner = flag; + } + + public get canRate(): boolean + { + return this._canRate; + } + + public set canRate(flag: boolean) + { + this._canRate = flag; + } +} diff --git a/src/api/navigator/RoomSettingsUtils.ts b/src/api/navigator/RoomSettingsUtils.ts new file mode 100644 index 0000000..bc611da --- /dev/null +++ b/src/api/navigator/RoomSettingsUtils.ts @@ -0,0 +1,10 @@ +const BuildMaxVisitorsList = () => +{ + const list: number[] = []; + + for(let i = 10; i <= 100; i = i + 10) list.push(i); + + return list; +} + +export const GetMaxVisitorsList = BuildMaxVisitorsList(); diff --git a/src/api/navigator/SearchFilterOptions.ts b/src/api/navigator/SearchFilterOptions.ts new file mode 100644 index 0000000..aaf1290 --- /dev/null +++ b/src/api/navigator/SearchFilterOptions.ts @@ -0,0 +1,24 @@ +import { INavigatorSearchFilter } from './INavigatorSearchFilter'; + +export const SearchFilterOptions: INavigatorSearchFilter[] = [ + { + name: 'anything', + query: null + }, + { + name: 'room.name', + query: 'roomname' + }, + { + name: 'owner', + query: 'owner' + }, + { + name: 'tag', + query: 'tag' + }, + { + name: 'group', + query: 'group' + } +]; diff --git a/src/api/navigator/TryVisitRoom.ts b/src/api/navigator/TryVisitRoom.ts new file mode 100644 index 0000000..81138d6 --- /dev/null +++ b/src/api/navigator/TryVisitRoom.ts @@ -0,0 +1,7 @@ +import { GetGuestRoomMessageComposer } from '@nitrots/nitro-renderer'; +import { SendMessageComposer } from '../nitro'; + +export function TryVisitRoom(roomId: number): void +{ + SendMessageComposer(new GetGuestRoomMessageComposer(roomId, false, true)); +} diff --git a/src/api/navigator/index.ts b/src/api/navigator/index.ts new file mode 100644 index 0000000..bceb33e --- /dev/null +++ b/src/api/navigator/index.ts @@ -0,0 +1,12 @@ +export * from './DoorStateType'; +export * from './INavigatorData'; +export * from './INavigatorSearchFilter'; +export * from './IRoomChatSettings'; +export * from './IRoomData'; +export * from './IRoomModel'; +export * from './IRoomModerationSettings'; +export * from './NavigatorSearchResultViewDisplayMode'; +export * from './RoomInfoData'; +export * from './RoomSettingsUtils'; +export * from './SearchFilterOptions'; +export * from './TryVisitRoom'; diff --git a/src/api/nitro/GetConfigurationValue.ts b/src/api/nitro/GetConfigurationValue.ts new file mode 100644 index 0000000..4438444 --- /dev/null +++ b/src/api/nitro/GetConfigurationValue.ts @@ -0,0 +1,6 @@ +import { GetConfiguration } from '@nitrots/nitro-renderer'; + +export function GetConfigurationValue(key: string, value: T = null): T +{ + return GetConfiguration().getValue(key, value); +} diff --git a/src/api/nitro/OpenUrl.ts b/src/api/nitro/OpenUrl.ts new file mode 100644 index 0000000..0329c00 --- /dev/null +++ b/src/api/nitro/OpenUrl.ts @@ -0,0 +1,15 @@ +import { CreateLinkEvent, HabboWebTools } from '@nitrots/nitro-renderer'; + +export const OpenUrl = (url: string) => +{ + if(!url || !url.length) return; + + if(url.startsWith('http')) + { + HabboWebTools.openWebPage(url); + } + else + { + CreateLinkEvent(url); + } +} diff --git a/src/api/nitro/SendMessageComposer.ts b/src/api/nitro/SendMessageComposer.ts new file mode 100644 index 0000000..4229c28 --- /dev/null +++ b/src/api/nitro/SendMessageComposer.ts @@ -0,0 +1,3 @@ +import { GetCommunication, IMessageComposer } from '@nitrots/nitro-renderer'; + +export const SendMessageComposer = (event: IMessageComposer) => GetCommunication().connection.send(event); diff --git a/src/api/nitro/index.ts b/src/api/nitro/index.ts new file mode 100644 index 0000000..11b9d02 --- /dev/null +++ b/src/api/nitro/index.ts @@ -0,0 +1,5 @@ +export * from './GetConfigurationValue'; +export * from './OpenUrl'; +export * from './SendMessageComposer'; +export * from './room'; +export * from './session'; diff --git a/src/api/nitro/room/DispatchMouseEvent.ts b/src/api/nitro/room/DispatchMouseEvent.ts new file mode 100644 index 0000000..c28e5e4 --- /dev/null +++ b/src/api/nitro/room/DispatchMouseEvent.ts @@ -0,0 +1,54 @@ +import { GetRoomEngine, MouseEventType } from '@nitrots/nitro-renderer'; + +let didMouseMove = false; +let lastClick = 0; +let clickCount = 0; + +export const DispatchMouseEvent = (event: MouseEvent, canvasId: number = 1) => +{ + const x = event.clientX; + const y = event.clientY; + + let eventType = event.type; + + if(eventType === MouseEventType.MOUSE_CLICK) + { + if(lastClick) + { + clickCount = 1; + + if(lastClick >= Date.now() - 300) clickCount++; + } + + lastClick = Date.now(); + + if(clickCount === 2) + { + if(!didMouseMove) eventType = MouseEventType.DOUBLE_CLICK; + + clickCount = 0; + lastClick = null; + } + } + + switch(eventType) + { + case MouseEventType.MOUSE_CLICK: + break; + case MouseEventType.DOUBLE_CLICK: + break; + case MouseEventType.MOUSE_MOVE: + didMouseMove = true; + break; + case MouseEventType.MOUSE_DOWN: + didMouseMove = false; + break; + case MouseEventType.MOUSE_UP: + break; + case MouseEventType.RIGHT_CLICK: + break; + default: return; + } + + GetRoomEngine().dispatchMouseEvent(canvasId, x, y, eventType, event.altKey, (event.ctrlKey || event.metaKey), event.shiftKey, false); +} diff --git a/src/api/nitro/room/DispatchTouchEvent.ts b/src/api/nitro/room/DispatchTouchEvent.ts new file mode 100644 index 0000000..0f31bdc --- /dev/null +++ b/src/api/nitro/room/DispatchTouchEvent.ts @@ -0,0 +1,81 @@ +import { GetRoomEngine, MouseEventType, TouchEventType } from '@nitrots/nitro-renderer'; + +let didMouseMove = false; +let lastClick = 0; +let clickCount = 0; + +export const DispatchTouchEvent = (event: TouchEvent, canvasId: number = 1, longTouch: boolean = false, altKey: boolean = false, ctrlKey: boolean = false, shiftKey: boolean = false) => +{ + let x = 0; + let y = 0; + + if(event.touches[0]) + { + x = event.touches[0].clientX; + y = event.touches[0].clientY; + } + + else if(event.changedTouches[0]) + { + x = event.changedTouches[0].clientX; + y = event.changedTouches[0].clientY; + } + + let eventType = event.type; + + if(longTouch) eventType = TouchEventType.TOUCH_LONG; + + if(eventType === MouseEventType.MOUSE_CLICK || eventType === TouchEventType.TOUCH_END) + { + eventType = MouseEventType.MOUSE_CLICK; + + if(lastClick) + { + clickCount = 1; + + if(lastClick >= (Date.now() - 300)) clickCount++; + } + + lastClick = Date.now(); + + if(clickCount === 2) + { + if(!didMouseMove) eventType = MouseEventType.DOUBLE_CLICK; + + clickCount = 0; + lastClick = null; + } + } + + switch(eventType) + { + case MouseEventType.MOUSE_CLICK: + break; + case MouseEventType.DOUBLE_CLICK: + break; + case TouchEventType.TOUCH_START: + eventType = MouseEventType.MOUSE_DOWN; + + didMouseMove = false; + break; + case TouchEventType.TOUCH_MOVE: + eventType = MouseEventType.MOUSE_MOVE; + + didMouseMove = true; + break; + case TouchEventType.TOUCH_END: + eventType = MouseEventType.MOUSE_UP; + break; + case TouchEventType.TOUCH_LONG: + eventType = MouseEventType.MOUSE_DOWN_LONG; + break; + default: return; + } + + if (eventType === TouchEventType.TOUCH_START) + { + GetRoomEngine().dispatchMouseEvent(canvasId, x, y, eventType, altKey, ctrlKey, shiftKey, false); + } + + GetRoomEngine().dispatchMouseEvent(canvasId, x, y, eventType, altKey, ctrlKey, shiftKey, false); +} diff --git a/src/api/nitro/room/GetOwnRoomObject.ts b/src/api/nitro/room/GetOwnRoomObject.ts new file mode 100644 index 0000000..aae0b77 --- /dev/null +++ b/src/api/nitro/room/GetOwnRoomObject.ts @@ -0,0 +1,31 @@ +import { GetRoomEngine, GetSessionDataManager, IRoomObjectController, RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { GetRoomSession } from '../session'; + +export function GetOwnRoomObject(): IRoomObjectController +{ + const userId = GetSessionDataManager().userId; + const roomId = GetRoomEngine().activeRoomId; + const category = RoomObjectCategory.UNIT; + const totalObjects = GetRoomEngine().getTotalObjectsForManager(roomId, category); + + let i = 0; + + while(i < totalObjects) + { + const roomObject = GetRoomEngine().getRoomObjectByIndex(roomId, i, category); + + if(roomObject) + { + const userData = GetRoomSession().userDataManager.getUserDataByIndex(roomObject.id); + + if(userData) + { + if(userData.webID === userId) return roomObject; + } + } + + i++; + } + + return null; +} diff --git a/src/api/nitro/room/GetRoomObjectBounds.ts b/src/api/nitro/room/GetRoomObjectBounds.ts new file mode 100644 index 0000000..e32fd25 --- /dev/null +++ b/src/api/nitro/room/GetRoomObjectBounds.ts @@ -0,0 +1,13 @@ +import { GetRoomEngine } from '@nitrots/nitro-renderer'; + +export const GetRoomObjectBounds = (roomId: number, objectId: number, category: number, canvasId = 1) => +{ + const rectangle = GetRoomEngine().getRoomObjectBoundingRectangle(roomId, objectId, category, canvasId); + + if(!rectangle) return null; + + rectangle.x = Math.round(rectangle.x); + rectangle.y = Math.round(rectangle.y); + + return rectangle; +} diff --git a/src/api/nitro/room/GetRoomObjectScreenLocation.ts b/src/api/nitro/room/GetRoomObjectScreenLocation.ts new file mode 100644 index 0000000..58cbc92 --- /dev/null +++ b/src/api/nitro/room/GetRoomObjectScreenLocation.ts @@ -0,0 +1,13 @@ +import { GetRoomEngine } from '@nitrots/nitro-renderer'; + +export const GetRoomObjectScreenLocation = (roomId: number, objectId: number, category: number, canvasId = 1) => +{ + const point = GetRoomEngine().getRoomObjectScreenLocation(roomId, objectId, category, canvasId); + + if(!point) return null; + + point.x = Math.round(point.x); + point.y = Math.round(point.y); + + return point; +} diff --git a/src/api/nitro/room/InitializeRoomInstanceRenderingCanvas.ts b/src/api/nitro/room/InitializeRoomInstanceRenderingCanvas.ts new file mode 100644 index 0000000..4f3eae5 --- /dev/null +++ b/src/api/nitro/room/InitializeRoomInstanceRenderingCanvas.ts @@ -0,0 +1,9 @@ +import { GetRoomEngine } from '@nitrots/nitro-renderer'; + +export const InitializeRoomInstanceRenderingCanvas = (width: number, height: number, canvasId: number = 1) => +{ + const roomEngine = GetRoomEngine(); + const roomId = roomEngine.activeRoomId; + + roomEngine.initializeRoomInstanceRenderingCanvas(roomId, canvasId, width, height); +} diff --git a/src/api/nitro/room/IsFurnitureSelectionDisabled.ts b/src/api/nitro/room/IsFurnitureSelectionDisabled.ts new file mode 100644 index 0000000..e86f9a3 --- /dev/null +++ b/src/api/nitro/room/IsFurnitureSelectionDisabled.ts @@ -0,0 +1,22 @@ +import { GetRoomEngine, GetSessionDataManager, RoomEngineObjectEvent, RoomObjectVariable } from '@nitrots/nitro-renderer'; + +export function IsFurnitureSelectionDisabled(event: RoomEngineObjectEvent): boolean +{ + let result = false; + + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + + if(roomObject) + { + const selectionDisabled = (roomObject.model.getValue(RoomObjectVariable.FURNITURE_SELECTION_DISABLED) === 1); + + if(selectionDisabled) + { + result = true; + + if(GetSessionDataManager().isModerator) result = false; + } + } + + return result; +} diff --git a/src/api/nitro/room/ProcessRoomObjectOperation.ts b/src/api/nitro/room/ProcessRoomObjectOperation.ts new file mode 100644 index 0000000..5a1c997 --- /dev/null +++ b/src/api/nitro/room/ProcessRoomObjectOperation.ts @@ -0,0 +1,6 @@ +import { GetRoomEngine } from '@nitrots/nitro-renderer'; + +export function ProcessRoomObjectOperation(objectId: number, category: number, operation: string): void +{ + GetRoomEngine().processRoomObjectOperation(objectId, category, operation); +} diff --git a/src/api/nitro/room/SetActiveRoomId.ts b/src/api/nitro/room/SetActiveRoomId.ts new file mode 100644 index 0000000..9446537 --- /dev/null +++ b/src/api/nitro/room/SetActiveRoomId.ts @@ -0,0 +1,6 @@ +import { GetRoomEngine } from '@nitrots/nitro-renderer'; + +export function SetActiveRoomId(roomId: number): void +{ + GetRoomEngine().setActiveRoomId(roomId); +} diff --git a/src/api/nitro/room/index.ts b/src/api/nitro/room/index.ts new file mode 100644 index 0000000..2af9c28 --- /dev/null +++ b/src/api/nitro/room/index.ts @@ -0,0 +1,9 @@ +export * from './DispatchMouseEvent'; +export * from './DispatchTouchEvent'; +export * from './GetOwnRoomObject'; +export * from './GetRoomObjectBounds'; +export * from './GetRoomObjectScreenLocation'; +export * from './InitializeRoomInstanceRenderingCanvas'; +export * from './IsFurnitureSelectionDisabled'; +export * from './ProcessRoomObjectOperation'; +export * from './SetActiveRoomId'; diff --git a/src/api/nitro/session/CanManipulateFurniture.ts b/src/api/nitro/session/CanManipulateFurniture.ts new file mode 100644 index 0000000..ba89efd --- /dev/null +++ b/src/api/nitro/session/CanManipulateFurniture.ts @@ -0,0 +1,9 @@ +import { GetRoomEngine, GetSessionDataManager, IRoomSession, RoomControllerLevel } from '@nitrots/nitro-renderer'; +import { IsOwnerOfFurniture } from './IsOwnerOfFurniture'; + +export function CanManipulateFurniture(roomSession: IRoomSession, objectId: number, category: number): boolean +{ + if(!roomSession) return false; + + return (roomSession.isRoomOwner || (roomSession.controllerLevel >= RoomControllerLevel.GUEST) || GetSessionDataManager().isModerator || IsOwnerOfFurniture(GetRoomEngine().getRoomObject(roomSession.roomId, objectId, category))); +} diff --git a/src/api/nitro/session/CreateRoomSession.ts b/src/api/nitro/session/CreateRoomSession.ts new file mode 100644 index 0000000..3f12bb4 --- /dev/null +++ b/src/api/nitro/session/CreateRoomSession.ts @@ -0,0 +1,6 @@ +import { GetRoomSessionManager } from '@nitrots/nitro-renderer'; + +export function CreateRoomSession(roomId: number, password: string = null): void +{ + GetRoomSessionManager().createSession(roomId, password); +} diff --git a/src/api/nitro/session/GetCanStandUp.ts b/src/api/nitro/session/GetCanStandUp.ts new file mode 100644 index 0000000..4915d18 --- /dev/null +++ b/src/api/nitro/session/GetCanStandUp.ts @@ -0,0 +1,13 @@ +import { AvatarAction, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { GetOwnRoomObject } from '../room'; + +export function GetCanStandUp(): string +{ + const roomObject = GetOwnRoomObject(); + + if(!roomObject) return AvatarAction.POSTURE_STAND; + + const model = roomObject.model; + + return model.getValue(RoomObjectVariable.FIGURE_CAN_STAND_UP); +} diff --git a/src/api/nitro/session/GetCanUseExpression.ts b/src/api/nitro/session/GetCanUseExpression.ts new file mode 100644 index 0000000..c7c7367 --- /dev/null +++ b/src/api/nitro/session/GetCanUseExpression.ts @@ -0,0 +1,14 @@ +import { RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { GetOwnRoomObject } from '../room'; + +export function GetCanUseExpression(): boolean +{ + const roomObject = GetOwnRoomObject(); + + if(!roomObject) return false; + + const model = roomObject.model; + const effectId = model.getValue(RoomObjectVariable.FIGURE_EFFECT); + + return !((effectId === 29) || (effectId === 30) || (effectId === 185)); +} diff --git a/src/api/nitro/session/GetClubMemberLevel.ts b/src/api/nitro/session/GetClubMemberLevel.ts new file mode 100644 index 0000000..97d4949 --- /dev/null +++ b/src/api/nitro/session/GetClubMemberLevel.ts @@ -0,0 +1,9 @@ +import { GetSessionDataManager, HabboClubLevelEnum } from '@nitrots/nitro-renderer'; +import { GetConfigurationValue } from '../GetConfigurationValue'; + +export function GetClubMemberLevel(): number +{ + if(GetConfigurationValue('hc.disabled', false)) return HabboClubLevelEnum.VIP; + + return GetSessionDataManager().clubLevel; +} diff --git a/src/api/nitro/session/GetFurnitureData.ts b/src/api/nitro/session/GetFurnitureData.ts new file mode 100644 index 0000000..b7646df --- /dev/null +++ b/src/api/nitro/session/GetFurnitureData.ts @@ -0,0 +1,19 @@ +import { GetSessionDataManager, IFurnitureData } from '@nitrots/nitro-renderer'; +import { ProductTypeEnum } from '../../catalog'; + +export function GetFurnitureData(furniClassId: number, productType: string): IFurnitureData +{ + let furniData: IFurnitureData = null; + + switch(productType.toLowerCase()) + { + case ProductTypeEnum.FLOOR: + furniData = GetSessionDataManager().getFloorItemData(furniClassId); + break; + case ProductTypeEnum.WALL: + furniData = GetSessionDataManager().getWallItemData(furniClassId); + break; + } + + return furniData; +} diff --git a/src/api/nitro/session/GetFurnitureDataForProductOffer.ts b/src/api/nitro/session/GetFurnitureDataForProductOffer.ts new file mode 100644 index 0000000..95a6256 --- /dev/null +++ b/src/api/nitro/session/GetFurnitureDataForProductOffer.ts @@ -0,0 +1,20 @@ +import { CatalogPageMessageProductData, FurnitureType, GetSessionDataManager, IFurnitureData } from '@nitrots/nitro-renderer'; + +export function GetFurnitureDataForProductOffer(offer: CatalogPageMessageProductData): IFurnitureData +{ + if(!offer) return null; + + let furniData: IFurnitureData = null; + + switch((offer.productType.toUpperCase())) + { + case FurnitureType.FLOOR: + furniData = GetSessionDataManager().getFloorItemData(offer.furniClassId); + break; + case FurnitureType.WALL: + furniData = GetSessionDataManager().getWallItemData(offer.furniClassId); + break; + } + + return furniData; +} diff --git a/src/api/nitro/session/GetFurnitureDataForRoomObject.ts b/src/api/nitro/session/GetFurnitureDataForRoomObject.ts new file mode 100644 index 0000000..fb76b9e --- /dev/null +++ b/src/api/nitro/session/GetFurnitureDataForRoomObject.ts @@ -0,0 +1,20 @@ +import { GetRoomEngine, GetSessionDataManager, IFurnitureData, RoomObjectCategory, RoomObjectVariable } from '@nitrots/nitro-renderer'; + +export function GetFurnitureDataForRoomObject(roomId: number, objectId: number, category: number): IFurnitureData +{ + const roomObject = GetRoomEngine().getRoomObject(roomId, objectId, category); + + if(!roomObject) return; + + const typeId = roomObject.model.getValue(RoomObjectVariable.FURNITURE_TYPE_ID); + + switch(category) + { + case RoomObjectCategory.FLOOR: + return GetSessionDataManager().getFloorItemData(typeId); + case RoomObjectCategory.WALL: + return GetSessionDataManager().getWallItemData(typeId); + } + + return null; +} diff --git a/src/api/nitro/session/GetOwnPosture.ts b/src/api/nitro/session/GetOwnPosture.ts new file mode 100644 index 0000000..fe0c5f3 --- /dev/null +++ b/src/api/nitro/session/GetOwnPosture.ts @@ -0,0 +1,13 @@ +import { AvatarAction, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { GetOwnRoomObject } from '../room'; + +export function GetOwnPosture(): string +{ + const roomObject = GetOwnRoomObject(); + + if(!roomObject) return AvatarAction.POSTURE_STAND; + + const model = roomObject.model; + + return model.getValue(RoomObjectVariable.FIGURE_POSTURE); +} diff --git a/src/api/nitro/session/GetProductDataForLocalization.ts b/src/api/nitro/session/GetProductDataForLocalization.ts new file mode 100644 index 0000000..ac89803 --- /dev/null +++ b/src/api/nitro/session/GetProductDataForLocalization.ts @@ -0,0 +1,8 @@ +import { GetSessionDataManager, IProductData } from '@nitrots/nitro-renderer'; + +export function GetProductDataForLocalization(localizationId: string): IProductData +{ + if(!localizationId) return null; + + return GetSessionDataManager().getProductData(localizationId); +} diff --git a/src/api/nitro/session/GetRoomSession.ts b/src/api/nitro/session/GetRoomSession.ts new file mode 100644 index 0000000..da2af41 --- /dev/null +++ b/src/api/nitro/session/GetRoomSession.ts @@ -0,0 +1,3 @@ +import { GetRoomSessionManager } from '@nitrots/nitro-renderer'; + +export const GetRoomSession = () => GetRoomSessionManager().getSession(-1); diff --git a/src/api/nitro/session/GoToDesktop.ts b/src/api/nitro/session/GoToDesktop.ts new file mode 100644 index 0000000..34f2031 --- /dev/null +++ b/src/api/nitro/session/GoToDesktop.ts @@ -0,0 +1,7 @@ +import { DesktopViewComposer } from '@nitrots/nitro-renderer'; +import { SendMessageComposer } from '../SendMessageComposer'; + +export function GoToDesktop(): void +{ + SendMessageComposer(new DesktopViewComposer()); +} diff --git a/src/api/nitro/session/HasHabboClub.ts b/src/api/nitro/session/HasHabboClub.ts new file mode 100644 index 0000000..9cee03f --- /dev/null +++ b/src/api/nitro/session/HasHabboClub.ts @@ -0,0 +1,6 @@ +import { GetSessionDataManager, HabboClubLevelEnum } from '@nitrots/nitro-renderer'; + +export function HasHabboClub(): boolean +{ + return (GetSessionDataManager().clubLevel >= HabboClubLevelEnum.CLUB); +} diff --git a/src/api/nitro/session/HasHabboVip.ts b/src/api/nitro/session/HasHabboVip.ts new file mode 100644 index 0000000..f5a3e21 --- /dev/null +++ b/src/api/nitro/session/HasHabboVip.ts @@ -0,0 +1,6 @@ +import { GetSessionDataManager, HabboClubLevelEnum } from '@nitrots/nitro-renderer'; + +export function HasHabboVip(): boolean +{ + return (GetSessionDataManager().clubLevel >= HabboClubLevelEnum.VIP); +} diff --git a/src/api/nitro/session/IsOwnerOfFloorFurniture.ts b/src/api/nitro/session/IsOwnerOfFloorFurniture.ts new file mode 100644 index 0000000..5675db9 --- /dev/null +++ b/src/api/nitro/session/IsOwnerOfFloorFurniture.ts @@ -0,0 +1,14 @@ +import { GetRoomEngine, GetSessionDataManager, RoomObjectCategory, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { GetRoomSession } from './GetRoomSession'; + +export function IsOwnerOfFloorFurniture(id: number): boolean +{ + const roomObject = GetRoomEngine().getRoomObject(GetRoomSession().roomId, id, RoomObjectCategory.FLOOR); + + if(!roomObject || !roomObject.model) return false; + + const userId = GetSessionDataManager().userId; + const objectOwnerId = roomObject.model.getValue(RoomObjectVariable.FURNITURE_OWNER_ID); + + return (userId === objectOwnerId); +} diff --git a/src/api/nitro/session/IsOwnerOfFurniture.ts b/src/api/nitro/session/IsOwnerOfFurniture.ts new file mode 100644 index 0000000..49ce166 --- /dev/null +++ b/src/api/nitro/session/IsOwnerOfFurniture.ts @@ -0,0 +1,11 @@ +import { GetSessionDataManager, IRoomObject, RoomObjectVariable } from '@nitrots/nitro-renderer'; + +export function IsOwnerOfFurniture(roomObject: IRoomObject): boolean +{ + if(!roomObject || !roomObject.model) return false; + + const userId = GetSessionDataManager().userId; + const objectOwnerId = roomObject.model.getValue(RoomObjectVariable.FURNITURE_OWNER_ID); + + return (userId === objectOwnerId); +} diff --git a/src/api/nitro/session/IsRidingHorse.ts b/src/api/nitro/session/IsRidingHorse.ts new file mode 100644 index 0000000..f946b69 --- /dev/null +++ b/src/api/nitro/session/IsRidingHorse.ts @@ -0,0 +1,14 @@ +import { RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { GetOwnRoomObject } from '../room'; + +export function IsRidingHorse(): boolean +{ + const roomObject = GetOwnRoomObject(); + + if(!roomObject) return false; + + const model = roomObject.model; + const effectId = model.getValue(RoomObjectVariable.FIGURE_EFFECT); + + return (effectId === 77); +} diff --git a/src/api/nitro/session/StartRoomSession.ts b/src/api/nitro/session/StartRoomSession.ts new file mode 100644 index 0000000..c203a77 --- /dev/null +++ b/src/api/nitro/session/StartRoomSession.ts @@ -0,0 +1,6 @@ +import { GetRoomSessionManager, IRoomSession } from '@nitrots/nitro-renderer'; + +export function StartRoomSession(session: IRoomSession): void +{ + GetRoomSessionManager().startSession(session); +} diff --git a/src/api/nitro/session/VisitDesktop.ts b/src/api/nitro/session/VisitDesktop.ts new file mode 100644 index 0000000..9fe3fb7 --- /dev/null +++ b/src/api/nitro/session/VisitDesktop.ts @@ -0,0 +1,11 @@ +import { GetRoomSessionManager } from '@nitrots/nitro-renderer'; +import { GetRoomSession } from './GetRoomSession'; +import { GoToDesktop } from './GoToDesktop'; + +export const VisitDesktop = () => +{ + if(!GetRoomSession()) return; + + GoToDesktop(); + GetRoomSessionManager().removeSession(-1); +} diff --git a/src/api/nitro/session/index.ts b/src/api/nitro/session/index.ts new file mode 100644 index 0000000..4c0491d --- /dev/null +++ b/src/api/nitro/session/index.ts @@ -0,0 +1,19 @@ +export * from './CanManipulateFurniture'; +export * from './CreateRoomSession'; +export * from './GetCanStandUp'; +export * from './GetCanUseExpression'; +export * from './GetClubMemberLevel'; +export * from './GetFurnitureData'; +export * from './GetFurnitureDataForProductOffer'; +export * from './GetFurnitureDataForRoomObject'; +export * from './GetOwnPosture'; +export * from './GetProductDataForLocalization'; +export * from './GetRoomSession'; +export * from './GoToDesktop'; +export * from './HasHabboClub'; +export * from './HasHabboVip'; +export * from './IsOwnerOfFloorFurniture'; +export * from './IsOwnerOfFurniture'; +export * from './IsRidingHorse'; +export * from './StartRoomSession'; +export * from './VisitDesktop'; diff --git a/src/api/notification/NotificationAlertItem.ts b/src/api/notification/NotificationAlertItem.ts new file mode 100644 index 0000000..2d7702c --- /dev/null +++ b/src/api/notification/NotificationAlertItem.ts @@ -0,0 +1,67 @@ +import { NotificationAlertType } from './NotificationAlertType'; + +export class NotificationAlertItem +{ + private static ITEM_ID: number = -1; + + private _id: number; + private _messages: string[]; + private _alertType: string; + private _clickUrl: string; + private _clickUrlText: string; + private _title: string; + private _imageUrl: string; + + constructor(messages: string[], alertType: string = NotificationAlertType.DEFAULT, clickUrl: string = null, clickUrlText: string = null, title: string = null, imageUrl: string = null) + { + NotificationAlertItem.ITEM_ID += 1; + + this._id = NotificationAlertItem.ITEM_ID; + this._messages = messages; + this._alertType = alertType; + this._clickUrl = clickUrl; + this._clickUrlText = clickUrlText; + this._title = title; + this._imageUrl = imageUrl; + } + + public get id(): number + { + return this._id; + } + + public get messages(): string[] + { + return this._messages; + } + + public set alertType(alertType: string) + { + this._alertType = alertType; + } + + public get alertType(): string + { + return this._alertType; + } + + public get clickUrl(): string + { + return this._clickUrl; + } + + public get clickUrlText(): string + { + return this._clickUrlText; + } + + public get title(): string + { + return this._title; + } + + public get imageUrl(): string + { + return this._imageUrl; + } +} diff --git a/src/api/notification/NotificationAlertType.ts b/src/api/notification/NotificationAlertType.ts new file mode 100644 index 0000000..ad804e8 --- /dev/null +++ b/src/api/notification/NotificationAlertType.ts @@ -0,0 +1,10 @@ +export class NotificationAlertType +{ + public static DEFAULT: string = 'default'; + public static MOTD: string = 'motd'; + public static MODERATION: string = 'moderation'; + public static EVENT: string = 'event'; + public static NITRO: string = 'nitro'; + public static SEARCH: string = 'search'; + public static ALERT: string = 'alert'; +} diff --git a/src/api/notification/NotificationBubbleItem.ts b/src/api/notification/NotificationBubbleItem.ts new file mode 100644 index 0000000..fe90dab --- /dev/null +++ b/src/api/notification/NotificationBubbleItem.ts @@ -0,0 +1,48 @@ +import { NotificationBubbleType } from './NotificationBubbleType'; + +export class NotificationBubbleItem +{ + private static ITEM_ID: number = -1; + + private _id: number; + private _message: string; + private _notificationType: string; + private _iconUrl: string; + private _linkUrl: string; + + constructor(message: string, notificationType: string = NotificationBubbleType.INFO, iconUrl: string = null, linkUrl: string = null) + { + NotificationBubbleItem.ITEM_ID += 1; + + this._id = NotificationBubbleItem.ITEM_ID; + this._message = message; + this._notificationType = notificationType; + this._iconUrl = iconUrl; + this._linkUrl = linkUrl; + } + + public get id(): number + { + return this._id; + } + + public get message(): string + { + return this._message; + } + + public get notificationType(): string + { + return this._notificationType; + } + + public get iconUrl(): string + { + return this._iconUrl; + } + + public get linkUrl(): string + { + return this._linkUrl; + } +} diff --git a/src/api/notification/NotificationBubbleType.ts b/src/api/notification/NotificationBubbleType.ts new file mode 100644 index 0000000..cce38f5 --- /dev/null +++ b/src/api/notification/NotificationBubbleType.ts @@ -0,0 +1,19 @@ +export class NotificationBubbleType +{ + public static FRIENDOFFLINE: string = 'friendoffline'; + public static FRIENDONLINE: string = 'friendonline'; + public static THIRDPARTYFRIENDOFFLINE: string = 'thirdpartyfriendoffline'; + public static THIRDPARTYFRIENDONLINE: string = 'thirdpartyfriendonline'; + public static ACHIEVEMENT: string = 'achievement'; + public static BADGE_RECEIVED: string = 'badge_received'; + public static INFO: string = 'info'; + public static RECYCLEROK: string = 'recyclerok'; + public static RESPECT: string = 'respect'; + public static CLUB: string = 'club'; + public static SOUNDMACHINE: string = 'soundmachine'; + public static PETLEVEL: string = 'petlevel'; + public static CLUBGIFT: string = 'clubgift'; + public static BUYFURNI: string = 'buyfurni'; + public static VIP: string = 'vip'; + public static ROOMMESSAGESPOSTED: string = 'roommessagesposted'; +} diff --git a/src/api/notification/NotificationConfirmItem.ts b/src/api/notification/NotificationConfirmItem.ts new file mode 100644 index 0000000..0455662 --- /dev/null +++ b/src/api/notification/NotificationConfirmItem.ts @@ -0,0 +1,67 @@ +export class NotificationConfirmItem +{ + private static ITEM_ID: number = -1; + + private _id: number; + private _confirmType: string; + private _message: string; + private _onConfirm: Function; + private _onCancel: Function; + private _confirmText: string; + private _cancelText: string; + private _title: string; + + constructor(confirmType: string, message: string, onConfirm: Function, onCancel: Function, confirmText: string, cancelText: string, title: string) + { + NotificationConfirmItem.ITEM_ID += 1; + + this._id = NotificationConfirmItem.ITEM_ID; + this._confirmType = confirmType; + this._message = message; + this._onConfirm = onConfirm; + this._onCancel = onCancel; + this._confirmText = confirmText; + this._cancelText = cancelText; + this._title = title; + } + + public get id(): number + { + return this._id; + } + + public get confirmType(): string + { + return this._confirmType; + } + + public get message(): string + { + return this._message; + } + + public get onConfirm(): Function + { + return this._onConfirm; + } + + public get onCancel(): Function + { + return this._onCancel; + } + + public get confirmText(): string + { + return this._confirmText; + } + + public get cancelText(): string + { + return this._cancelText; + } + + public get title(): string + { + return this._title; + } +} diff --git a/src/api/notification/NotificationConfirmType.ts b/src/api/notification/NotificationConfirmType.ts new file mode 100644 index 0000000..533ca05 --- /dev/null +++ b/src/api/notification/NotificationConfirmType.ts @@ -0,0 +1,4 @@ +export class NotificationConfirmType +{ + public static DEFAULT: string = 'default'; +} diff --git a/src/api/notification/index.ts b/src/api/notification/index.ts new file mode 100644 index 0000000..23476d3 --- /dev/null +++ b/src/api/notification/index.ts @@ -0,0 +1,6 @@ +export * from './NotificationAlertItem'; +export * from './NotificationAlertType'; +export * from './NotificationBubbleItem'; +export * from './NotificationBubbleType'; +export * from './NotificationConfirmItem'; +export * from './NotificationConfirmType'; diff --git a/src/api/purse/IPurse.ts b/src/api/purse/IPurse.ts new file mode 100644 index 0000000..9fffb18 --- /dev/null +++ b/src/api/purse/IPurse.ts @@ -0,0 +1,15 @@ +export interface IPurse +{ + credits: number; + activityPoints: Map; + clubDays: number; + clubPeriods: number; + hasClubLeft: boolean; + isVip: boolean; + pastClubDays: number; + pastVipDays: number; + isExpiring: boolean; + minutesUntilExpiration: number; + minutesSinceLastModified: number; + clubLevel: number; +} diff --git a/src/api/purse/Purse.ts b/src/api/purse/Purse.ts new file mode 100644 index 0000000..6970e59 --- /dev/null +++ b/src/api/purse/Purse.ts @@ -0,0 +1,165 @@ +import { GetTickerTime, HabboClubLevelEnum } from '@nitrots/nitro-renderer'; +import { IPurse } from './IPurse'; + +export class Purse implements IPurse +{ + private _credits: number = 0; + private _activityPoints: Map = new Map(); + private _clubDays: number = 0; + private _clubPeriods: number = 0; + private _isVIP: boolean = false; + private _pastClubDays: number = 0; + private _pastVipDays: number = 0; + private _isExpiring: boolean = false; + private _minutesUntilExpiration: number = 0; + private _minutesSinceLastModified: number = 0; + private _lastUpdated: number = 0; + + public static from(purse: Purse): Purse + { + const newPurse = new Purse(); + + newPurse._credits = purse._credits; + newPurse._activityPoints = purse._activityPoints; + newPurse._clubDays = purse._clubDays; + newPurse._clubPeriods = purse._clubPeriods; + newPurse._isVIP = purse._isVIP; + newPurse._pastClubDays = purse._pastClubDays; + newPurse._pastVipDays = purse._pastVipDays; + newPurse._isExpiring = purse._isExpiring; + newPurse._minutesUntilExpiration = purse._minutesUntilExpiration; + newPurse._minutesSinceLastModified = purse._minutesSinceLastModified; + newPurse._lastUpdated = purse._lastUpdated; + + return newPurse; + } + + public get credits(): number + { + return this._credits; + } + + public set credits(credits: number) + { + this._lastUpdated = GetTickerTime(); + this._credits = credits; + } + + public get activityPoints(): Map + { + return this._activityPoints; + } + + public set activityPoints(k: Map) + { + this._lastUpdated = GetTickerTime(); + this._activityPoints = k; + } + + public get clubDays(): number + { + return this._clubDays; + } + + public set clubDays(k: number) + { + this._lastUpdated = GetTickerTime(); + this._clubDays = k; + } + + public get clubPeriods(): number + { + return this._clubPeriods; + } + + public set clubPeriods(k: number) + { + this._lastUpdated = GetTickerTime(); + this._clubPeriods = k; + } + + public get hasClubLeft(): boolean + { + return (this._clubDays > 0) || (this._clubPeriods > 0); + } + + public get isVip(): boolean + { + return this._isVIP; + } + + public set isVip(k: boolean) + { + this._isVIP = k; + } + + public get pastClubDays(): number + { + return this._pastClubDays; + } + + public set pastClubDays(k: number) + { + this._lastUpdated = GetTickerTime(); + this._pastClubDays = k; + } + + public get pastVipDays(): number + { + return this._pastVipDays; + } + + public set pastVipDays(k: number) + { + this._lastUpdated = GetTickerTime(); + this._pastVipDays = k; + } + + public get isExpiring(): boolean + { + return this._isExpiring; + } + + public set isExpiring(k: boolean) + { + this._isExpiring = k; + } + + public get minutesUntilExpiration(): number + { + var k: number = ((GetTickerTime() - this._lastUpdated) / (1000 * 60)); + var _local_2: number = (this._minutesUntilExpiration - k); + return (_local_2 > 0) ? _local_2 : 0; + } + + public set minutesUntilExpiration(k: number) + { + this._lastUpdated = GetTickerTime(); + this._minutesUntilExpiration = k; + } + + public get minutesSinceLastModified(): number + { + return this._minutesSinceLastModified; + } + + public set minutesSinceLastModified(k: number) + { + this._lastUpdated = GetTickerTime(); + this._minutesSinceLastModified = k; + } + + public get lastUpdated(): number + { + return this._lastUpdated; + } + + public get clubLevel(): number + { + if(((this.clubDays === 0) && (this.clubPeriods === 0))) return HabboClubLevelEnum.NO_CLUB; + + if(this.isVip) return HabboClubLevelEnum.VIP; + + return HabboClubLevelEnum.CLUB; + } +} diff --git a/src/api/purse/index.ts b/src/api/purse/index.ts new file mode 100644 index 0000000..ed34480 --- /dev/null +++ b/src/api/purse/index.ts @@ -0,0 +1,2 @@ +export * from './IPurse'; +export * from './Purse'; diff --git a/src/api/room/events/RoomWidgetPollUpdateEvent.ts b/src/api/room/events/RoomWidgetPollUpdateEvent.ts new file mode 100644 index 0000000..edfb8fd --- /dev/null +++ b/src/api/room/events/RoomWidgetPollUpdateEvent.ts @@ -0,0 +1,110 @@ +import { IPollQuestion } from '@nitrots/nitro-renderer'; +import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent'; + +export class RoomWidgetPollUpdateEvent extends RoomWidgetUpdateEvent +{ + public static readonly OFFER = 'RWPUW_OFFER'; + public static readonly ERROR = 'RWPUW_ERROR'; + public static readonly CONTENT = 'RWPUW_CONTENT'; + + private _id = -1; + private _summary: string; + private _headline: string; + private _numQuestions = 0; + private _startMessage = ''; + private _endMessage = ''; + private _questionArray: IPollQuestion[] = null; + private _pollType = ''; + private _npsPoll = false; + + constructor(type: string, id: number) + { + super(type); + this._id = id; + } + + public get id(): number + { + return this._id; + } + + public get summary(): string + { + return this._summary; + } + + public set summary(k: string) + { + this._summary = k; + } + + public get headline(): string + { + return this._headline; + } + + public set headline(k: string) + { + this._headline = k; + } + + public get numQuestions(): number + { + return this._numQuestions; + } + + public set numQuestions(k: number) + { + this._numQuestions = k; + } + + public get startMessage(): string + { + return this._startMessage; + } + + public set startMessage(k: string) + { + this._startMessage = k; + } + + public get endMessage(): string + { + return this._endMessage; + } + + public set endMessage(k: string) + { + this._endMessage = k; + } + + public get questionArray(): IPollQuestion[] + { + return this._questionArray; + } + + public set questionArray(k: IPollQuestion[]) + { + this._questionArray = k; + } + + public get pollType(): string + { + return this._pollType; + } + + public set pollType(k: string) + { + this._pollType = k; + } + + public get npsPoll(): boolean + { + return this._npsPoll; + } + + public set npsPoll(k: boolean) + { + this._npsPoll = k; + } +} diff --git a/src/api/room/events/RoomWidgetUpdateBackgroundColorPreviewEvent.ts b/src/api/room/events/RoomWidgetUpdateBackgroundColorPreviewEvent.ts new file mode 100644 index 0000000..30135a3 --- /dev/null +++ b/src/api/room/events/RoomWidgetUpdateBackgroundColorPreviewEvent.ts @@ -0,0 +1,35 @@ +import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent'; + +export class RoomWidgetUpdateBackgroundColorPreviewEvent extends RoomWidgetUpdateEvent +{ + public static PREVIEW = 'RWUBCPE_PREVIEW'; + public static CLEAR_PREVIEW = 'RWUBCPE_CLEAR_PREVIEW'; + + private _hue: number; + private _saturation: number; + private _lightness: number; + + constructor(type: string, hue: number = 0, saturation: number = 0, lightness: number = 0) + { + super(type); + + this._hue = hue; + this._saturation = saturation; + this._lightness = lightness; + } + + public get hue(): number + { + return this._hue; + } + + public get saturation(): number + { + return this._saturation; + } + + public get lightness(): number + { + return this._lightness; + } +} diff --git a/src/api/room/events/RoomWidgetUpdateChatInputContentEvent.ts b/src/api/room/events/RoomWidgetUpdateChatInputContentEvent.ts new file mode 100644 index 0000000..9352372 --- /dev/null +++ b/src/api/room/events/RoomWidgetUpdateChatInputContentEvent.ts @@ -0,0 +1,29 @@ +import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent'; + +export class RoomWidgetUpdateChatInputContentEvent extends RoomWidgetUpdateEvent +{ + public static CHAT_INPUT_CONTENT: string = 'RWUCICE_CHAT_INPUT_CONTENT'; + public static WHISPER: string = 'whisper'; + public static SHOUT: string = 'shout'; + + private _chatMode: string = ''; + private _userName: string = ''; + + constructor(chatMode: string, userName: string) + { + super(RoomWidgetUpdateChatInputContentEvent.CHAT_INPUT_CONTENT); + + this._chatMode = chatMode; + this._userName = userName; + } + + public get chatMode(): string + { + return this._chatMode; + } + + public get userName(): string + { + return this._userName; + } +} diff --git a/src/api/room/events/RoomWidgetUpdateEvent.ts b/src/api/room/events/RoomWidgetUpdateEvent.ts new file mode 100644 index 0000000..0ac8ff8 --- /dev/null +++ b/src/api/room/events/RoomWidgetUpdateEvent.ts @@ -0,0 +1,4 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; + +export class RoomWidgetUpdateEvent extends NitroEvent +{} diff --git a/src/api/room/events/RoomWidgetUpdateRentableBotChatEvent.ts b/src/api/room/events/RoomWidgetUpdateRentableBotChatEvent.ts new file mode 100644 index 0000000..6191e1b --- /dev/null +++ b/src/api/room/events/RoomWidgetUpdateRentableBotChatEvent.ts @@ -0,0 +1,62 @@ +import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent'; + +export class RoomWidgetUpdateRentableBotChatEvent extends RoomWidgetUpdateEvent +{ + public static UPDATE_CHAT: string = 'RWURBCE_UPDATE_CHAT'; + + private _objectId: number; + private _category: number; + private _botId: number; + private _chat: string; + private _automaticChat: boolean; + private _chatDelay: number; + private _mixSentences: boolean; + + constructor(objectId: number, category: number, botId: number, chat: string, automaticChat: boolean, chatDelay: number, mixSentences: boolean) + { + super(RoomWidgetUpdateRentableBotChatEvent.UPDATE_CHAT); + + this._objectId = objectId; + this._category = category; + this._botId = botId; + this._chat = chat; + this._automaticChat = automaticChat; + this._chatDelay = chatDelay; + this._mixSentences = mixSentences; + } + + public get objectId(): number + { + return this._objectId; + } + + public get category(): number + { + return this._category; + } + + public get botId(): number + { + return this._botId; + } + + public get chat(): string + { + return this._chat; + } + + public get automaticChat(): boolean + { + return this._automaticChat; + } + + public get chatDelay(): number + { + return this._chatDelay; + } + + public get mixSentences(): boolean + { + return this._mixSentences; + } +} diff --git a/src/api/room/events/RoomWidgetUpdateRoomObjectEvent.ts b/src/api/room/events/RoomWidgetUpdateRoomObjectEvent.ts new file mode 100644 index 0000000..0660276 --- /dev/null +++ b/src/api/room/events/RoomWidgetUpdateRoomObjectEvent.ts @@ -0,0 +1,43 @@ +import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent'; + +export class RoomWidgetUpdateRoomObjectEvent extends RoomWidgetUpdateEvent +{ + public static OBJECT_SELECTED: string = 'RWUROE_OBJECT_SELECTED'; + public static OBJECT_DESELECTED: string = 'RWUROE_OBJECT_DESELECTED'; + public static USER_REMOVED: string = 'RWUROE_USER_REMOVED'; + public static FURNI_REMOVED: string = 'RWUROE_FURNI_REMOVED'; + public static FURNI_ADDED: string = 'RWUROE_FURNI_ADDED'; + public static USER_ADDED: string = 'RWUROE_USER_ADDED'; + public static OBJECT_ROLL_OVER: string = 'RWUROE_OBJECT_ROLL_OVER'; + public static OBJECT_ROLL_OUT: string = 'RWUROE_OBJECT_ROLL_OUT'; + public static OBJECT_REQUEST_MANIPULATION: string = 'RWUROE_OBJECT_REQUEST_MANIPULATION'; + public static OBJECT_DOUBLE_CLICKED: string = 'RWUROE_OBJECT_DOUBLE_CLICKED'; + + private _id: number; + private _category: number; + private _roomId: number; + + constructor(type: string, id: number, category: number, roomId: number) + { + super(type); + + this._id = id; + this._category = category; + this._roomId = roomId; + } + + public get id(): number + { + return this._id; + } + + public get category(): number + { + return this._category; + } + + public get roomId(): number + { + return this._roomId; + } +} diff --git a/src/api/room/events/index.ts b/src/api/room/events/index.ts new file mode 100644 index 0000000..e5ed0d8 --- /dev/null +++ b/src/api/room/events/index.ts @@ -0,0 +1,6 @@ +export * from './RoomWidgetPollUpdateEvent'; +export * from './RoomWidgetUpdateBackgroundColorPreviewEvent'; +export * from './RoomWidgetUpdateChatInputContentEvent'; +export * from './RoomWidgetUpdateEvent'; +export * from './RoomWidgetUpdateRentableBotChatEvent'; +export * from './RoomWidgetUpdateRoomObjectEvent'; diff --git a/src/api/room/index.ts b/src/api/room/index.ts new file mode 100644 index 0000000..56aea79 --- /dev/null +++ b/src/api/room/index.ts @@ -0,0 +1,2 @@ +export * from './events'; +export * from './widgets'; diff --git a/src/api/room/widgets/AvatarInfoFurni.ts b/src/api/room/widgets/AvatarInfoFurni.ts new file mode 100644 index 0000000..47743e9 --- /dev/null +++ b/src/api/room/widgets/AvatarInfoFurni.ts @@ -0,0 +1,37 @@ +import { IObjectData } from '@nitrots/nitro-renderer'; +import { IAvatarInfo } from './IAvatarInfo'; + +export class AvatarInfoFurni implements IAvatarInfo +{ + public static FURNI: string = 'IFI_FURNI'; + + public id: number = 0; + public category: number = 0; + public name: string = ''; + public description: string = ''; + public isWallItem: boolean = false; + public isStickie: boolean = false; + public isRoomOwner: boolean = false; + public roomControllerLevel: number = 0; + public isAnyRoomController: boolean = false; + public expiration: number = -1; + public purchaseCatalogPageId: number = -1; + public purchaseOfferId: number = -1; + public extraParam: string = ''; + public isOwner: boolean = false; + public stuffData: IObjectData = null; + public groupId: number = 0; + public ownerId: number = 0; + public ownerName: string = ''; + public usagePolicy: number = 0; + public rentCatalogPageId: number = -1; + public rentOfferId: number = -1; + public purchaseCouldBeUsedForBuyout: boolean = false; + public rentCouldBeUsedForBuyout: boolean = false; + public availableForBuildersClub: boolean = false; + public tileSizeX: number = 1; + public tileSizeY: number = 1; + + constructor(public readonly type: string) + {} +} diff --git a/src/api/room/widgets/AvatarInfoName.ts b/src/api/room/widgets/AvatarInfoName.ts new file mode 100644 index 0000000..66a6a7e --- /dev/null +++ b/src/api/room/widgets/AvatarInfoName.ts @@ -0,0 +1,11 @@ +export class AvatarInfoName +{ + constructor( + public readonly roomIndex: number, + public readonly category: number, + public readonly id: number, + public readonly name: string, + public readonly userType: number, + public readonly isFriend: boolean = false) + {} +} diff --git a/src/api/room/widgets/AvatarInfoPet.ts b/src/api/room/widgets/AvatarInfoPet.ts new file mode 100644 index 0000000..0c0435a --- /dev/null +++ b/src/api/room/widgets/AvatarInfoPet.ts @@ -0,0 +1,46 @@ +import { IAvatarInfo } from './IAvatarInfo'; + +export class AvatarInfoPet implements IAvatarInfo +{ + public static PET_INFO: string = 'IPI_PET_INFO'; + + public level: number = 0; + public maximumLevel: number = 0; + public experience: number = 0; + public levelExperienceGoal: number = 0; + public energy: number = 0; + public maximumEnergy: number = 0; + public happyness: number = 0; + public maximumHappyness: number = 0; + public respectsPetLeft: number = 0; + public respect: number = 0; + public age: number = 0; + public name: string = ''; + public id: number = -1; + public image: HTMLImageElement = null; + public petType: number = 0; + public petBreed: number = 0; + public petFigure: string = ''; + public posture: string = 'std'; + public isOwner: boolean = false; + public ownerId: number = -1; + public ownerName: string = ''; + public canRemovePet: boolean = false; + public roomIndex: number = 0; + public unknownRarityLevel: number = 0; + public saddle: boolean = false; + public rider: boolean = false; + public breedable: boolean = false; + public skillTresholds: number[] = []; + public publiclyRideable: number = 0; + public fullyGrown: boolean = false; + public dead: boolean = false; + public rarityLevel: number = 0; + public maximumTimeToLive: number = 0; + public remainingTimeToLive: number = 0; + public remainingGrowTime: number = 0; + public publiclyBreedable: boolean = false; + + constructor(public readonly type: string) + {} +} diff --git a/src/api/room/widgets/AvatarInfoRentableBot.ts b/src/api/room/widgets/AvatarInfoRentableBot.ts new file mode 100644 index 0000000..77fb10c --- /dev/null +++ b/src/api/room/widgets/AvatarInfoRentableBot.ts @@ -0,0 +1,23 @@ +import { IAvatarInfo } from './IAvatarInfo'; + +export class AvatarInfoRentableBot implements IAvatarInfo +{ + public static RENTABLE_BOT: string = 'IRBI_RENTABLE_BOT'; + + public name: string = ''; + public motto: string = ''; + public webID: number = 0; + public figure: string = ''; + public badges: string[] = []; + public carryItem: number = 0; + public roomIndex: number = 0; + public amIOwner: boolean = false; + public amIAnyRoomController: boolean = false; + public roomControllerLevel: number = 0; + public ownerId: number = -1; + public ownerName: string = ''; + public botSkills: number[] = []; + + constructor(public readonly type: string) + {} +} diff --git a/src/api/room/widgets/AvatarInfoUser.ts b/src/api/room/widgets/AvatarInfoUser.ts new file mode 100644 index 0000000..270bfbd --- /dev/null +++ b/src/api/room/widgets/AvatarInfoUser.ts @@ -0,0 +1,49 @@ +import { IAvatarInfo } from './IAvatarInfo'; + +export class AvatarInfoUser implements IAvatarInfo +{ + public static OWN_USER: string = 'IUI_OWN_USER'; + public static PEER: string = 'IUI_PEER'; + public static BOT: string = 'IUI_BOT'; + public static TRADE_REASON_OK: number = 0; + public static TRADE_REASON_SHUTDOWN: number = 2; + public static TRADE_REASON_NO_TRADING: number = 3; + public static DEFAULT_BOT_BADGE_ID: string = 'BOT'; + + public name: string = ''; + public motto: string = ''; + public achievementScore: number = 0; + public webID: number = 0; + public xp: number = 0; + public userType: number = -1; + public figure: string = ''; + public badges: string[] = []; + public groupId: number = 0; + public groupName: string = ''; + public groupBadgeId: string = ''; + public carryItem: number = 0; + public roomIndex: number = 0; + public isSpectatorMode: boolean = false; + public allowNameChange: boolean = false; + public amIOwner: boolean = false; + public amIAnyRoomController: boolean = false; + public roomControllerLevel: number = 0; + public canBeKicked: boolean = false; + public canBeBanned: boolean = false; + public canBeMuted: boolean = false; + public respectLeft: number = 0; + public isIgnored: boolean = false; + public isGuildRoom: boolean = false; + public canTrade: boolean = false; + public canTradeReason: number = 0; + public targetRoomControllerLevel: number = 0; + public isAmbassador: boolean = false; + + constructor(public readonly type: string) + {} + + public get isOwnUser(): boolean + { + return (this.type === AvatarInfoUser.OWN_USER); + } +} diff --git a/src/api/room/widgets/AvatarInfoUtilities.ts b/src/api/room/widgets/AvatarInfoUtilities.ts new file mode 100644 index 0000000..b670bde --- /dev/null +++ b/src/api/room/widgets/AvatarInfoUtilities.ts @@ -0,0 +1,439 @@ +import { GetRoomEngine, GetSessionDataManager, GetTickerTime, IFurnitureData, IRoomModerationSettings, IRoomPetData, IRoomUserData, ObjectDataFactory, PetFigureData, PetType, RoomControllerLevel, RoomModerationSettings, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomTradingLevelEnum, RoomWidgetEnumItemExtradataParameter } from '@nitrots/nitro-renderer'; +import { GetRoomSession, IsOwnerOfFurniture } from '../../nitro'; +import { LocalizeText } from '../../utils'; +import { AvatarInfoFurni } from './AvatarInfoFurni'; +import { AvatarInfoName } from './AvatarInfoName'; +import { AvatarInfoPet } from './AvatarInfoPet'; +import { AvatarInfoRentableBot } from './AvatarInfoRentableBot'; +import { AvatarInfoUser } from './AvatarInfoUser'; + +export class AvatarInfoUtilities +{ + public static getObjectName(objectId: number, category: number): AvatarInfoName + { + const roomSession = GetRoomSession(); + + let id = -1; + let name: string = null; + let userType = 0; + + switch(category) + { + case RoomObjectCategory.FLOOR: + case RoomObjectCategory.WALL: { + const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, objectId, category); + + if(!roomObject) break; + + if(roomObject.type.indexOf('poster') === 0) + { + name = LocalizeText('${poster_' + parseInt(roomObject.type.replace('poster', '')) + '_name}'); + } + else + { + let furniData: IFurnitureData = null; + + const typeId = roomObject.model.getValue(RoomObjectVariable.FURNITURE_TYPE_ID); + + if(category === RoomObjectCategory.FLOOR) + { + furniData = GetSessionDataManager().getFloorItemData(typeId); + } + + else if(category === RoomObjectCategory.WALL) + { + furniData = GetSessionDataManager().getWallItemData(typeId); + } + + if(!furniData) break; + + id = furniData.id; + name = furniData.name; + } + break; + } + case RoomObjectCategory.UNIT: { + const userData = roomSession.userDataManager.getUserDataByIndex(objectId); + + if(!userData) break; + + id = userData.webID; + name = userData.name; + userType = userData.type; + break; + } + } + + if(!name || !name.length) return null; + + return new AvatarInfoName(objectId, category, id, name, userType); + } + + public static getFurniInfo(objectId: number, category: number): AvatarInfoFurni + { + const roomSession = GetRoomSession(); + const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, objectId, category); + + if(!roomObject) return null; + + const furniInfo = new AvatarInfoFurni(AvatarInfoFurni.FURNI); + + furniInfo.id = objectId; + furniInfo.category = category; + + const model = roomObject.model; + + if(model.getValue(RoomWidgetEnumItemExtradataParameter.INFOSTAND_EXTRA_PARAM)) furniInfo.extraParam = model.getValue(RoomWidgetEnumItemExtradataParameter.INFOSTAND_EXTRA_PARAM); + + const objectData = ObjectDataFactory.getData(model.getValue(RoomObjectVariable.FURNITURE_DATA_FORMAT)); + + objectData.initializeFromRoomObjectModel(model); + + furniInfo.stuffData = objectData; + + const objectType = roomObject.type; + + if(objectType.indexOf('poster') === 0) + { + const posterId = parseInt(objectType.replace('poster', '')); + + furniInfo.name = LocalizeText(('${poster_' + posterId) + '_name}'); + furniInfo.description = LocalizeText(('${poster_' + posterId) + '_desc}'); + } + else + { + const typeId = model.getValue(RoomObjectVariable.FURNITURE_TYPE_ID); + + let furnitureData: IFurnitureData = null; + + if(category === RoomObjectCategory.FLOOR) + { + furnitureData = GetSessionDataManager().getFloorItemData(typeId); + } + + else if(category === RoomObjectCategory.WALL) + { + furnitureData = GetSessionDataManager().getWallItemData(typeId); + } + + if(furnitureData) + { + furniInfo.name = furnitureData.name; + furniInfo.description = furnitureData.description; + furniInfo.purchaseOfferId = furnitureData.purchaseOfferId; + furniInfo.purchaseCouldBeUsedForBuyout = furnitureData.purchaseCouldBeUsedForBuyout; + furniInfo.rentOfferId = furnitureData.rentOfferId; + furniInfo.rentCouldBeUsedForBuyout = furnitureData.rentCouldBeUsedForBuyout; + furniInfo.availableForBuildersClub = furnitureData.availableForBuildersClub; + furniInfo.tileSizeX = furnitureData.tileSizeX; + furniInfo.tileSizeY = furnitureData.tileSizeY; + } + } + + if(objectType.indexOf('post_it') > -1) furniInfo.isStickie = true; + + const expiryTime = model.getValue(RoomObjectVariable.FURNITURE_EXPIRY_TIME); + const expiryTimestamp = model.getValue(RoomObjectVariable.FURNITURE_EXPIRTY_TIMESTAMP); + + furniInfo.expiration = ((expiryTime < 0) ? expiryTime : Math.max(0, (expiryTime - ((GetTickerTime() - expiryTimestamp) / 1000)))); + + /* let roomObjectImage = GetRoomEngine().getRoomObjectImage(roomSession.roomId, objectId, category, new Vector3d(180), 64, null); + + if(!roomObjectImage.data || (roomObjectImage.data.width > 140) || (roomObjectImage.data.height > 200)) + { + roomObjectImage = GetRoomEngine().getRoomObjectImage(roomSession.roomId, objectId, category, new Vector3d(180), 1, null); + } + + furniInfo.image = roomObjectImage.getImage(); */ + furniInfo.isWallItem = (category === RoomObjectCategory.WALL); + furniInfo.isRoomOwner = roomSession.isRoomOwner; + furniInfo.roomControllerLevel = roomSession.controllerLevel; + furniInfo.isAnyRoomController = GetSessionDataManager().isModerator; + furniInfo.ownerId = model.getValue(RoomObjectVariable.FURNITURE_OWNER_ID); + furniInfo.ownerName = model.getValue(RoomObjectVariable.FURNITURE_OWNER_NAME); + furniInfo.usagePolicy = model.getValue(RoomObjectVariable.FURNITURE_USAGE_POLICY); + + const guildId = model.getValue(RoomObjectVariable.FURNITURE_GUILD_CUSTOMIZED_GUILD_ID); + + if(guildId !== 0) furniInfo.groupId = guildId; + + if(IsOwnerOfFurniture(roomObject)) furniInfo.isOwner = true; + + return furniInfo; + } + + public static getUserInfo(category: number, userData: IRoomUserData): AvatarInfoUser + { + const roomSession = GetRoomSession(); + + const userInfo = new AvatarInfoUser((userData.webID === GetSessionDataManager().userId) ? AvatarInfoUser.OWN_USER : AvatarInfoUser.PEER); + + userInfo.isSpectatorMode = roomSession.isSpectator; + userInfo.name = userData.name; + userInfo.motto = userData.custom; + userInfo.achievementScore = userData.activityPoints; + userInfo.webID = userData.webID; + userInfo.roomIndex = userData.roomIndex; + userInfo.userType = RoomObjectType.USER; + + const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, userData.roomIndex, category); + + if(roomObject) userInfo.carryItem = (roomObject.model.getValue(RoomObjectVariable.FIGURE_CARRY_OBJECT) || 0); + + if(userInfo.type === AvatarInfoUser.OWN_USER) userInfo.allowNameChange = GetSessionDataManager().canChangeName; + + userInfo.amIOwner = roomSession.isRoomOwner; + userInfo.isGuildRoom = roomSession.isGuildRoom; + userInfo.roomControllerLevel = roomSession.controllerLevel; + userInfo.amIAnyRoomController = GetSessionDataManager().isModerator; + userInfo.isAmbassador = GetSessionDataManager().isAmbassador; + + if(userInfo.type === AvatarInfoUser.PEER) + { + if(roomObject) + { + userInfo.targetRoomControllerLevel = roomObject.model.getValue(RoomObjectVariable.FIGURE_FLAT_CONTROL); + userInfo.canBeMuted = this.canBeMuted(userInfo); + userInfo.canBeKicked = this.canBeKicked(userInfo); + userInfo.canBeBanned = this.canBeBanned(userInfo); + } + + userInfo.isIgnored = GetSessionDataManager().isUserIgnored(userData.name); + userInfo.respectLeft = GetSessionDataManager().respectsLeft; + + const isShuttingDown = GetSessionDataManager().isSystemShutdown; + const tradeMode = roomSession.tradeMode; + + if(isShuttingDown) + { + userInfo.canTrade = false; + } + else + { + switch(tradeMode) + { + case RoomTradingLevelEnum.ROOM_CONTROLLER_REQUIRED: { + const roomController = ((userInfo.roomControllerLevel !== RoomControllerLevel.NONE) && (userInfo.roomControllerLevel !== RoomControllerLevel.GUILD_MEMBER)); + const targetController = ((userInfo.targetRoomControllerLevel !== RoomControllerLevel.NONE) && (userInfo.targetRoomControllerLevel !== RoomControllerLevel.GUILD_MEMBER)); + + userInfo.canTrade = (roomController || targetController); + break; + } + case RoomTradingLevelEnum.FREE_TRADING: + userInfo.canTrade = true; + break; + default: + userInfo.canTrade = false; + break; + } + } + + userInfo.canTradeReason = AvatarInfoUser.TRADE_REASON_OK; + + if(isShuttingDown) userInfo.canTradeReason = AvatarInfoUser.TRADE_REASON_SHUTDOWN; + + if(tradeMode !== RoomTradingLevelEnum.FREE_TRADING) userInfo.canTradeReason = AvatarInfoUser.TRADE_REASON_NO_TRADING; + + // const _local_12 = GetSessionDataManager().userId; + // _local_13 = GetSessionDataManager().getUserTags(_local_12); + // this._Str_16287(_local_12, _local_13); + } + + userInfo.groupId = userData.groupId; + userInfo.groupBadgeId = GetSessionDataManager().getGroupBadge(userInfo.groupId); + userInfo.groupName = userData.groupName; + userInfo.badges = roomSession.userDataManager.getUserBadges(userData.webID); + userInfo.figure = userData.figure; + //var _local_8:Array = GetSessionDataManager().getUserTags(userData.webID); + //this._Str_16287(userData.webId, _local_8); + //this._container.habboGroupsManager.updateVisibleExtendedProfile(userData.webID); + //this._container.connection.send(new GetRelationshipStatusInfoMessageComposer(userData.webId)); + + return userInfo; + } + + public static getBotInfo(category: number, userData: IRoomUserData): AvatarInfoUser + { + const roomSession = GetRoomSession(); + const userInfo = new AvatarInfoUser(AvatarInfoUser.BOT); + + userInfo.name = userData.name; + userInfo.motto = userData.custom; + userInfo.webID = userData.webID; + userInfo.roomIndex = userData.roomIndex; + userInfo.userType = userData.type; + + const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, userData.roomIndex, category); + + if(roomObject) userInfo.carryItem = (roomObject.model.getValue(RoomObjectVariable.FIGURE_CARRY_OBJECT) || 0); + + userInfo.amIOwner = roomSession.isRoomOwner; + userInfo.isGuildRoom = roomSession.isGuildRoom; + userInfo.roomControllerLevel = roomSession.controllerLevel; + userInfo.amIAnyRoomController = GetSessionDataManager().isModerator; + userInfo.isAmbassador = GetSessionDataManager().isAmbassador; + userInfo.badges = [ AvatarInfoUser.DEFAULT_BOT_BADGE_ID ]; + userInfo.figure = userData.figure; + + return userInfo; + } + + public static getRentableBotInfo(category: number, userData: IRoomUserData): AvatarInfoRentableBot + { + const roomSession = GetRoomSession(); + const botInfo = new AvatarInfoRentableBot(AvatarInfoRentableBot.RENTABLE_BOT); + + botInfo.name = userData.name; + botInfo.motto = userData.custom; + botInfo.webID = userData.webID; + botInfo.roomIndex = userData.roomIndex; + botInfo.ownerId = userData.ownerId; + botInfo.ownerName = userData.ownerName; + botInfo.botSkills = userData.botSkills; + + const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, userData.roomIndex, category); + + if(roomObject) botInfo.carryItem = (roomObject.model.getValue(RoomObjectVariable.FIGURE_CARRY_OBJECT) || 0); + + botInfo.amIOwner = roomSession.isRoomOwner; + botInfo.roomControllerLevel = roomSession.controllerLevel; + botInfo.amIAnyRoomController = GetSessionDataManager().isModerator; + botInfo.badges = [ AvatarInfoUser.DEFAULT_BOT_BADGE_ID ]; + botInfo.figure = userData.figure; + + return botInfo; + } + + public static getPetInfo(petData: IRoomPetData): AvatarInfoPet + { + const roomSession = GetRoomSession(); + const userData = roomSession.userDataManager.getPetData(petData.id); + + if(!userData) return; + + const figure = new PetFigureData(userData.figure); + + let posture: string = null; + + if(figure.typeId === PetType.MONSTERPLANT) + { + if(petData.level >= petData.adultLevel) posture = 'std'; + else posture = ('grw' + petData.level); + } + + const isOwner = (petData.ownerId === GetSessionDataManager().userId); + const petInfo = new AvatarInfoPet(AvatarInfoPet.PET_INFO); + + petInfo.name = userData.name; + petInfo.id = petData.id; + petInfo.ownerId = petData.ownerId; + petInfo.ownerName = petData.ownerName; + petInfo.rarityLevel = petData.rarityLevel; + petInfo.petType = figure.typeId; + petInfo.petBreed = figure.paletteId; + petInfo.petFigure = userData.figure; + petInfo.posture = posture; + petInfo.isOwner = isOwner; + petInfo.roomIndex = userData.roomIndex; + petInfo.level = petData.level; + petInfo.maximumLevel = petData.maximumLevel; + petInfo.experience = petData.experience; + petInfo.levelExperienceGoal = petData.levelExperienceGoal; + petInfo.energy = petData.energy; + petInfo.maximumEnergy = petData.maximumEnergy; + petInfo.happyness = petData.happyness; + petInfo.maximumHappyness = petData.maximumHappyness; + petInfo.respect = petData.respect; + petInfo.respectsPetLeft = GetSessionDataManager().respectsPetLeft; + petInfo.age = petData.age; + petInfo.saddle = petData.saddle; + petInfo.rider = petData.rider; + petInfo.breedable = petData.breedable; + petInfo.fullyGrown = petData.fullyGrown; + petInfo.dead = petData.dead; + petInfo.rarityLevel = petData.rarityLevel; + petInfo.skillTresholds = petData.skillTresholds; + petInfo.canRemovePet = false; + petInfo.publiclyRideable = petData.publiclyRideable; + petInfo.maximumTimeToLive = petData.maximumTimeToLive; + petInfo.remainingTimeToLive = petData.remainingTimeToLive; + petInfo.remainingGrowTime = petData.remainingGrowTime; + petInfo.publiclyBreedable = petData.publiclyBreedable; + + if(isOwner || roomSession.isRoomOwner || GetSessionDataManager().isModerator || (roomSession.controllerLevel >= RoomControllerLevel.GUEST)) petInfo.canRemovePet = true; + + return petInfo; + } + + private static checkGuildSetting(userInfo: AvatarInfoUser): boolean + { + if(userInfo.isGuildRoom) return (userInfo.roomControllerLevel >= RoomControllerLevel.GUILD_ADMIN); + + return (userInfo.roomControllerLevel >= RoomControllerLevel.GUEST); + } + + private static isValidSetting(userInfo: AvatarInfoUser, checkSetting: (userInfo: AvatarInfoUser, moderation: IRoomModerationSettings) => boolean): boolean + { + const roomSession = GetRoomSession(); + + if(!roomSession.isPrivateRoom) return false; + + const moderation = roomSession.moderationSettings; + + let flag = false; + + if(moderation) flag = checkSetting(userInfo, moderation); + + return (flag && (userInfo.targetRoomControllerLevel < RoomControllerLevel.ROOM_OWNER)); + } + + private static canBeMuted(userInfo: AvatarInfoUser): boolean + { + const checkSetting = (userInfo: AvatarInfoUser, moderation: IRoomModerationSettings) => + { + switch(moderation.allowMute) + { + case RoomModerationSettings.MODERATION_LEVEL_USER_WITH_RIGHTS: + return this.checkGuildSetting(userInfo); + default: + return (userInfo.roomControllerLevel >= RoomControllerLevel.ROOM_OWNER); + } + } + + return this.isValidSetting(userInfo, checkSetting); + } + + private static canBeKicked(userInfo: AvatarInfoUser): boolean + { + const checkSetting = (userInfo: AvatarInfoUser, moderation: IRoomModerationSettings) => + { + switch(moderation.allowKick) + { + case RoomModerationSettings.MODERATION_LEVEL_ALL: + return true; + case RoomModerationSettings.MODERATION_LEVEL_USER_WITH_RIGHTS: + return this.checkGuildSetting(userInfo); + default: + return (userInfo.roomControllerLevel >= RoomControllerLevel.ROOM_OWNER); + } + } + + return this.isValidSetting(userInfo, checkSetting); + } + + private static canBeBanned(userInfo: AvatarInfoUser): boolean + { + const checkSetting = (userInfo: AvatarInfoUser, moderation: IRoomModerationSettings) => + { + switch(moderation.allowBan) + { + case RoomModerationSettings.MODERATION_LEVEL_USER_WITH_RIGHTS: + return this.checkGuildSetting(userInfo); + default: + return (userInfo.roomControllerLevel >= RoomControllerLevel.ROOM_OWNER); + } + } + + return this.isValidSetting(userInfo, checkSetting); + } +} diff --git a/src/api/room/widgets/BotSkillsEnum.ts b/src/api/room/widgets/BotSkillsEnum.ts new file mode 100644 index 0000000..b879cdc --- /dev/null +++ b/src/api/room/widgets/BotSkillsEnum.ts @@ -0,0 +1,18 @@ +export class BotSkillsEnum +{ + public static GENERIC_SKILL: number = 0; + public static DRESS_UP: number = 1; + public static SETUP_CHAT: number = 2; + public static RANDOM_WALK: number = 3; + public static DANCE: number = 4; + public static CHANGE_BOT_NAME: number = 5; + public static SERVE_BEVERAGE: number = 6; + public static INCLIENT_LINK: number = 7; + public static NUX_PROCEED: number = 8; + public static CHANGE_BOT_MOTTO: number = 9; + public static NUX_TAKE_TOUR: number = 10; + public static NO_PICK_UP: number = 12; + public static NAVIGATOR_SEARCH: number = 14; + public static DONATE_TO_USER: number = 24; + public static DONATE_TO_ALL: number = 25; +} diff --git a/src/api/room/widgets/ChatBubbleMessage.ts b/src/api/room/widgets/ChatBubbleMessage.ts new file mode 100644 index 0000000..44d848c --- /dev/null +++ b/src/api/room/widgets/ChatBubbleMessage.ts @@ -0,0 +1,54 @@ +export class ChatBubbleMessage +{ + public static BUBBLE_COUNTER: number = 0; + + public id: number = -1; + public width: number = 0; + public height: number = 0; + public elementRef: HTMLDivElement = null; + public skipMovement: boolean = false; + + private _top: number = 0; + private _left: number = 0; + + constructor( + public senderId: number = -1, + public senderCategory: number = -1, + public roomId: number = -1, + public text: string = '', + public formattedText: string = '', + public username: string = '', + public location: { x: number, y: number } = null, + public type: number = 0, + public styleId: number = 0, + public imageUrl: string = null, + public color: string = null + ) + { + this.id = ++ChatBubbleMessage.BUBBLE_COUNTER; + } + + public get top(): number + { + return this._top; + } + + public set top(value: number) + { + this._top = value; + + if(this.elementRef) this.elementRef.style.top = (this._top + 'px'); + } + + public get left(): number + { + return this._left; + } + + public set left(value: number) + { + this._left = value; + + if(this.elementRef) this.elementRef.style.left = (this._left + 'px'); + } +} diff --git a/src/api/room/widgets/ChatBubbleUtilities.ts b/src/api/room/widgets/ChatBubbleUtilities.ts new file mode 100644 index 0000000..0ccbb14 --- /dev/null +++ b/src/api/room/widgets/ChatBubbleUtilities.ts @@ -0,0 +1,69 @@ +import { AvatarFigurePartType, AvatarScaleType, AvatarSetType, GetAvatarRenderManager, GetRoomEngine, PetFigureData, TextureUtils, Vector3d } from '@nitrots/nitro-renderer'; + +export class ChatBubbleUtilities +{ + public static AVATAR_COLOR_CACHE: Map = new Map(); + public static AVATAR_IMAGE_CACHE: Map = new Map(); + public static PET_IMAGE_CACHE: Map = new Map(); + + private static placeHolderImageUrl: string = ''; + + public static async setFigureImage(figure: string): Promise + { + const avatarImage = GetAvatarRenderManager().createAvatarImage(figure, AvatarScaleType.LARGE, null, { + resetFigure: figure => this.setFigureImage(figure), + dispose: () => + {}, + disposed: false + }); + + if(!avatarImage) return null; + + const isPlaceholder = avatarImage.isPlaceholder(); + + if(isPlaceholder && this.placeHolderImageUrl?.length) return this.placeHolderImageUrl; + + figure = avatarImage.getFigure().getFigureString(); + + const imageUrl = avatarImage.processAsImageUrl(AvatarSetType.HEAD); + const color = avatarImage.getPartColor(AvatarFigurePartType.CHEST); + + if(isPlaceholder) this.placeHolderImageUrl = imageUrl; + + this.AVATAR_COLOR_CACHE.set(figure, ((color && color.rgb) || 16777215)); + this.AVATAR_IMAGE_CACHE.set(figure, imageUrl); + + avatarImage.dispose(); + + return imageUrl; + } + + public static async getUserImage(figure: string): Promise + { + let existing = this.AVATAR_IMAGE_CACHE.get(figure); + + if(!existing) existing = await this.setFigureImage(figure); + + return existing; + } + + public static async getPetImage(figure: string, direction: number, _arg_3: boolean, scale: number = 64, posture: string = null) + { + let existing = this.PET_IMAGE_CACHE.get((figure + posture)); + + if(existing) return existing; + + const figureData = new PetFigureData(figure); + const typeId = figureData.typeId; + const image = GetRoomEngine().getRoomObjectPetImage(typeId, figureData.paletteId, figureData.color, new Vector3d((direction * 45)), scale, null, false, 0, figureData.customParts, posture); + + if(image) + { + existing = await TextureUtils.generateImageUrl(image.data); + + this.PET_IMAGE_CACHE.set((figure + posture), existing); + } + + return existing; + } +} diff --git a/src/api/room/widgets/ChatMessageTypeEnum.ts b/src/api/room/widgets/ChatMessageTypeEnum.ts new file mode 100644 index 0000000..1a5296b --- /dev/null +++ b/src/api/room/widgets/ChatMessageTypeEnum.ts @@ -0,0 +1,6 @@ +export class ChatMessageTypeEnum +{ + public static CHAT_DEFAULT: number = 0; + public static CHAT_WHISPER: number = 1; + public static CHAT_SHOUT: number = 2; +} diff --git a/src/api/room/widgets/DimmerFurnitureWidgetPresetItem.ts b/src/api/room/widgets/DimmerFurnitureWidgetPresetItem.ts new file mode 100644 index 0000000..1a2759f --- /dev/null +++ b/src/api/room/widgets/DimmerFurnitureWidgetPresetItem.ts @@ -0,0 +1,9 @@ +export class DimmerFurnitureWidgetPresetItem +{ + constructor( + public id: number = 0, + public type: number = 0, + public color: number = 0, + public light: number = 0) + {} +} diff --git a/src/api/room/widgets/DoChatsOverlap.ts b/src/api/room/widgets/DoChatsOverlap.ts new file mode 100644 index 0000000..092ce5d --- /dev/null +++ b/src/api/room/widgets/DoChatsOverlap.ts @@ -0,0 +1,7 @@ +import { ChatBubbleMessage } from './ChatBubbleMessage'; + +export const DoChatsOverlap = (a: ChatBubbleMessage, b: ChatBubbleMessage, additionalBTop: number, padding: number = 0) => +{ + return !((((a.left + padding) + a.width) < (b.left + padding)) || ((a.left + padding) > ((b.left + padding) + b.width)) || ((a.top + a.height) < (b.top + additionalBTop)) || (a.top > ((b.top + additionalBTop) + b.height))); +} + \ No newline at end of file diff --git a/src/api/room/widgets/FurnitureDimmerUtilities.ts b/src/api/room/widgets/FurnitureDimmerUtilities.ts new file mode 100644 index 0000000..f55fc87 --- /dev/null +++ b/src/api/room/widgets/FurnitureDimmerUtilities.ts @@ -0,0 +1,30 @@ +import { GetRoomEngine } from '@nitrots/nitro-renderer'; +import { GetRoomSession } from '../../nitro'; + +export class FurnitureDimmerUtilities +{ + public static AVAILABLE_COLORS: number[] = [ 7665141, 21495, 15161822, 15353138, 15923281, 8581961, 0 ]; + public static HTML_COLORS: string[] = [ '#74F5F5', '#0053F7', '#E759DE', '#EA4532', '#F2F851', '#82F349', '#000000' ]; + public static MIN_BRIGHTNESS: number = 76; + public static MAX_BRIGHTNESS: number = 255; + + public static savePreset(presetNumber: number, effectTypeId: number, color: number, brightness: number, apply: boolean): void + { + GetRoomSession().updateMoodlightData(presetNumber, effectTypeId, color, brightness, apply); + } + + public static changeState(): void + { + GetRoomSession().toggleMoodlightState(); + } + + public static previewDimmer(color: number, brightness: number, bgOnly: boolean): void + { + GetRoomEngine().updateObjectRoomColor(GetRoomSession().roomId, color, brightness, bgOnly); + } + + public static scaleBrightness(value: number): number + { + return ~~((((value - this.MIN_BRIGHTNESS) * (100 - 0)) / (this.MAX_BRIGHTNESS - this.MIN_BRIGHTNESS)) + 0); + } +} diff --git a/src/api/room/widgets/GetDiskColor.ts b/src/api/room/widgets/GetDiskColor.ts new file mode 100644 index 0000000..989f294 --- /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/IAvatarInfo.ts b/src/api/room/widgets/IAvatarInfo.ts new file mode 100644 index 0000000..23fb47b --- /dev/null +++ b/src/api/room/widgets/IAvatarInfo.ts @@ -0,0 +1,4 @@ +export interface IAvatarInfo +{ + type: string; +} diff --git a/src/api/room/widgets/ICraftingIngredient.ts b/src/api/room/widgets/ICraftingIngredient.ts new file mode 100644 index 0000000..cb2b031 --- /dev/null +++ b/src/api/room/widgets/ICraftingIngredient.ts @@ -0,0 +1,6 @@ +export interface ICraftingIngredient +{ + name: string; + iconUrl: string; + count: number; +} diff --git a/src/api/room/widgets/ICraftingRecipe.ts b/src/api/room/widgets/ICraftingRecipe.ts new file mode 100644 index 0000000..dd99291 --- /dev/null +++ b/src/api/room/widgets/ICraftingRecipe.ts @@ -0,0 +1,6 @@ +export interface ICraftingRecipe +{ + name: string; + localizedName: string; + iconUrl: string; +} diff --git a/src/api/room/widgets/IPhotoData.ts b/src/api/room/widgets/IPhotoData.ts new file mode 100644 index 0000000..9a7b846 --- /dev/null +++ b/src/api/room/widgets/IPhotoData.ts @@ -0,0 +1,42 @@ +export interface IPhotoData +{ + /** + * creator username + */ + n?: string; + + /** + * creator user id + */ + s?: number; + + /** + * photo unique id + */ + u?: number; + + /** + * creation timestamp + */ + t?: number; + + /** + * photo caption + */ + m?: string; + + /** + * photo image url + */ + w?: string; + + /** + * owner id + */ + oi?: number; + + /** + * owner name + */ + o?: string; +} \ No newline at end of file diff --git a/src/api/room/widgets/MannequinUtilities.ts b/src/api/room/widgets/MannequinUtilities.ts new file mode 100644 index 0000000..9368a79 --- /dev/null +++ b/src/api/room/widgets/MannequinUtilities.ts @@ -0,0 +1,38 @@ +import { AvatarFigurePartType, GetAvatarRenderManager, IAvatarFigureContainer } from '@nitrots/nitro-renderer'; + +export class MannequinUtilities +{ + public static MANNEQUIN_FIGURE = [ 'hd', 99999, [ 99998 ] ]; + public static MANNEQUIN_CLOTHING_PART_TYPES = [ + AvatarFigurePartType.CHEST_ACCESSORY, + AvatarFigurePartType.COAT_CHEST, + AvatarFigurePartType.CHEST, + AvatarFigurePartType.LEGS, + AvatarFigurePartType.SHOES, + AvatarFigurePartType.WAIST_ACCESSORY + ]; + + public static getMergedMannequinFigureContainer(figure: string, targetFigure: string): IAvatarFigureContainer + { + const figureContainer = GetAvatarRenderManager().createFigureContainer(figure); + const targetFigureContainer = GetAvatarRenderManager().createFigureContainer(targetFigure); + + for(const part of this.MANNEQUIN_CLOTHING_PART_TYPES) figureContainer.removePart(part); + + for(const part of targetFigureContainer.getPartTypeIds()) figureContainer.updatePart(part, targetFigureContainer.getPartSetId(part), targetFigureContainer.getPartColorIds(part)); + + return figureContainer; + } + + public static transformAsMannequinFigure(figureContainer: IAvatarFigureContainer): void + { + for(const part of figureContainer.getPartTypeIds()) + { + if(this.MANNEQUIN_CLOTHING_PART_TYPES.indexOf(part) >= 0) continue; + + figureContainer.removePart(part); + } + + figureContainer.updatePart((this.MANNEQUIN_FIGURE[0] as string), (this.MANNEQUIN_FIGURE[1] as number), (this.MANNEQUIN_FIGURE[2] as number[])); + }; +} diff --git a/src/api/room/widgets/PetSupplementEnum.ts b/src/api/room/widgets/PetSupplementEnum.ts new file mode 100644 index 0000000..eb23687 --- /dev/null +++ b/src/api/room/widgets/PetSupplementEnum.ts @@ -0,0 +1,5 @@ +export class PetSupplementEnum +{ + public static WATER: number = 0; + public static LIGHT: number = 1; +} diff --git a/src/api/room/widgets/PostureTypeEnum.ts b/src/api/room/widgets/PostureTypeEnum.ts new file mode 100644 index 0000000..21352d7 --- /dev/null +++ b/src/api/room/widgets/PostureTypeEnum.ts @@ -0,0 +1,5 @@ +export class PostureTypeEnum +{ + public static POSTURE_STAND: number = 0; + public static POSTURE_SIT: number = 1; +} diff --git a/src/api/room/widgets/RoomDimmerPreset.ts b/src/api/room/widgets/RoomDimmerPreset.ts new file mode 100644 index 0000000..86600d5 --- /dev/null +++ b/src/api/room/widgets/RoomDimmerPreset.ts @@ -0,0 +1,35 @@ +export class RoomDimmerPreset +{ + private _id: number; + private _type: number; + private _color: number; + private _brightness: number; + + constructor(id: number, type: number, color: number, brightness: number) + { + this._id = id; + this._type = type; + this._color = color; + this._brightness = brightness; + } + + public get id(): number + { + return this._id; + } + + public get type(): number + { + return this._type; + } + + public get color(): number + { + return this._color; + } + + public get brightness(): number + { + return this._brightness; + } +} diff --git a/src/api/room/widgets/RoomObjectItem.ts b/src/api/room/widgets/RoomObjectItem.ts new file mode 100644 index 0000000..f4fb2d6 --- /dev/null +++ b/src/api/room/widgets/RoomObjectItem.ts @@ -0,0 +1,28 @@ +export class RoomObjectItem +{ + private _id: number; + private _category: number; + private _name: string; + + constructor(id: number, category: number, name: string) + { + this._id = id; + this._category = category; + this._name = name; + } + + public get id(): number + { + return this._id; + } + + public get category(): number + { + return this._category; + } + + public get name(): string + { + return this._name; + } +} diff --git a/src/api/room/widgets/UseProductItem.ts b/src/api/room/widgets/UseProductItem.ts new file mode 100644 index 0000000..d3e2088 --- /dev/null +++ b/src/api/room/widgets/UseProductItem.ts @@ -0,0 +1,12 @@ +export class UseProductItem +{ + constructor( + public readonly id: number, + public readonly category: number, + public readonly name: string, + public readonly requestRoomObjectId: number, + public readonly targetRoomObjectId: number, + public readonly requestInventoryStripId: number, + public readonly replace: boolean) + {} +} diff --git a/src/api/room/widgets/VoteValue.ts b/src/api/room/widgets/VoteValue.ts new file mode 100644 index 0000000..ecf4336 --- /dev/null +++ b/src/api/room/widgets/VoteValue.ts @@ -0,0 +1,8 @@ +export const VALUE_KEY_DISLIKE = '0'; +export const VALUE_KEY_LIKE = '1'; + +export interface VoteValue +{ + value: string; + secondsLeft: number; +} diff --git a/src/api/room/widgets/YoutubeVideoPlaybackStateEnum.ts b/src/api/room/widgets/YoutubeVideoPlaybackStateEnum.ts new file mode 100644 index 0000000..3a885d1 --- /dev/null +++ b/src/api/room/widgets/YoutubeVideoPlaybackStateEnum.ts @@ -0,0 +1,9 @@ +export class YoutubeVideoPlaybackStateEnum +{ + public static readonly UNSTARTED = -1; + public static readonly ENDED = 0; + public static readonly PLAYING = 1; + public static readonly PAUSED = 2; + public static readonly BUFFERING = 3; + public static readonly CUED = 5; +} diff --git a/src/api/room/widgets/index.ts b/src/api/room/widgets/index.ts new file mode 100644 index 0000000..6c50c83 --- /dev/null +++ b/src/api/room/widgets/index.ts @@ -0,0 +1,26 @@ +export * from './AvatarInfoFurni'; +export * from './AvatarInfoName'; +export * from './AvatarInfoPet'; +export * from './AvatarInfoRentableBot'; +export * from './AvatarInfoUser'; +export * from './AvatarInfoUtilities'; +export * from './BotSkillsEnum'; +export * from './ChatBubbleMessage'; +export * from './ChatBubbleUtilities'; +export * from './ChatMessageTypeEnum'; +export * from './DimmerFurnitureWidgetPresetItem'; +export * from './DoChatsOverlap'; +export * from './FurnitureDimmerUtilities'; +export * from './GetDiskColor'; +export * from './IAvatarInfo'; +export * from './ICraftingIngredient'; +export * from './ICraftingRecipe'; +export * from './IPhotoData'; +export * from './MannequinUtilities'; +export * from './PetSupplementEnum'; +export * from './PostureTypeEnum'; +export * from './RoomDimmerPreset'; +export * from './RoomObjectItem'; +export * from './UseProductItem'; +export * from './VoteValue'; +export * from './YoutubeVideoPlaybackStateEnum'; diff --git a/src/api/user/GetUserProfile.ts b/src/api/user/GetUserProfile.ts new file mode 100644 index 0000000..13c67aa --- /dev/null +++ b/src/api/user/GetUserProfile.ts @@ -0,0 +1,7 @@ +import { UserProfileComposer } from '@nitrots/nitro-renderer'; +import { SendMessageComposer } from '../nitro'; + +export function GetUserProfile(userId: number): void +{ + SendMessageComposer(new UserProfileComposer(userId)); +} diff --git a/src/api/user/index.ts b/src/api/user/index.ts new file mode 100644 index 0000000..1c609ea --- /dev/null +++ b/src/api/user/index.ts @@ -0,0 +1 @@ +export * from './GetUserProfile'; diff --git a/src/api/utils/CloneObject.ts b/src/api/utils/CloneObject.ts new file mode 100644 index 0000000..6cd8d3e --- /dev/null +++ b/src/api/utils/CloneObject.ts @@ -0,0 +1,14 @@ +export const CloneObject = (object: T): T => +{ + if((object == null) || ('object' != typeof object)) return object; + + // @ts-ignore + const copy = new object.constructor(); + + for(const attr in object) + { + if(object.hasOwnProperty(attr)) copy[attr] = object[attr]; + } + + return copy; +} diff --git a/src/api/utils/ColorUtils.ts b/src/api/utils/ColorUtils.ts new file mode 100644 index 0000000..c32f8fb --- /dev/null +++ b/src/api/utils/ColorUtils.ts @@ -0,0 +1,65 @@ +export class ColorUtils +{ + public static makeColorHex(color: string): string + { + return ('#' + color); + } + + public static makeColorNumberHex(color: number): string + { + let val = color.toString(16); + return ( '#' + val.padStart(6, '0')); + } + + public static convertFromHex(color: string): number + { + return parseInt(color.replace('#', ''), 16); + } + + public static uintHexColor(color: number): string + { + const realColor = color >>>0; + + return ColorUtils.makeColorHex(realColor.toString(16).substring(2)); + } + + /** + * Converts an integer format into an array of 8-bit values + * @param {number} value value in integer format + * @returns {Array} 8-bit values + */ + public static int_to_8BitVals(value: number): [number, number, number, number] + { + const val1 = ((value >> 24) & 0xFF) + const val2 = ((value >> 16) & 0xFF); + const val3 = ((value >> 8) & 0xFF); + const val4 = (value & 0xFF); + + return [ val1, val2, val3, val4 ]; + } + + /** + * Combines 4 8-bit values into a 32-bit integer. Values are combined in + * in the order of the parameters + * @param val1 + * @param val2 + * @param val3 + * @param val4 + * @returns 32-bit integer of combined values + */ + public static eight_bitVals_to_int(val1: number, val2: number, val3: number, val4: number): number + { + return (((val1) << 24) + ((val2) << 16) + ((val3) << 8) + (val4| 0)); + } + + public static int2rgb(color: number): string + { + color >>>= 0; + const b = color & 0xFF; + const g = (color & 0xFF00) >>> 8; + const r = (color & 0xFF0000) >>> 16; + const a = ((color & 0xFF000000) >>> 24) / 255; + + return 'rgba(' + [ r, g, b, 1 ].join(',') + ')'; + } +} diff --git a/src/api/utils/ConvertSeconds.ts b/src/api/utils/ConvertSeconds.ts new file mode 100644 index 0000000..f559dea --- /dev/null +++ b/src/api/utils/ConvertSeconds.ts @@ -0,0 +1,9 @@ +export const ConvertSeconds = (seconds: number) => +{ + let numDays = Math.floor(seconds / 86400); + let numHours = Math.floor((seconds % 86400) / 3600); + let numMinutes = Math.floor(((seconds % 86400) % 3600) / 60); + let numSeconds = ((seconds % 86400) % 3600) % 60; + + return numDays.toString().padStart(2, '0') + ':' + numHours.toString().padStart(2, '0') + ':' + numMinutes.toString().padStart(2, '0') + ':' + numSeconds.toString().padStart(2, '0'); +} diff --git a/src/api/utils/FixedSizeStack.ts b/src/api/utils/FixedSizeStack.ts new file mode 100644 index 0000000..af8e09a --- /dev/null +++ b/src/api/utils/FixedSizeStack.ts @@ -0,0 +1,65 @@ +export class FixedSizeStack +{ + private _data: number[]; + private _maxSize: number; + private _index: number; + + constructor(k: number) + { + this._data = []; + this._maxSize = k; + this._index = 0; + } + + public reset(): void + { + this._data = []; + this._index = 0; + } + + public addValue(k: number): void + { + if(this._data.length < this._maxSize) + { + this._data.push(k); + } + else + { + this._data[this._index] = k; + } + + this._index = ((this._index + 1) % this._maxSize); + } + + public getMax(): number + { + let k = Number.MIN_VALUE; + + let _local_2 = 0; + + while(_local_2 < this._maxSize) + { + if(this._data[_local_2] > k) k = this._data[_local_2]; + + _local_2++; + } + + return k; + } + + public getMin(): number + { + let k = Number.MAX_VALUE; + + let _local_2 = 0; + + while(_local_2 < this._maxSize) + { + if(this._data[_local_2] < k) k = this._data[_local_2]; + + _local_2++; + } + + return k; + } +} diff --git a/src/api/utils/FriendlyTime.ts b/src/api/utils/FriendlyTime.ts new file mode 100644 index 0000000..7acb39c --- /dev/null +++ b/src/api/utils/FriendlyTime.ts @@ -0,0 +1,47 @@ +import { LocalizeText } from './LocalizeText'; + +export class FriendlyTime +{ + private static MINUTE: number = 60; + private static HOUR: number = (60 * FriendlyTime.MINUTE); + private static DAY: number = (24 * FriendlyTime.HOUR); + private static WEEK: number = (7 * FriendlyTime.DAY); + private static MONTH: number = (30 * FriendlyTime.DAY); + private static YEAR: number = (365 * FriendlyTime.DAY); + + + public static format(seconds: number, key: string = '', threshold: number = 3): string + { + if(seconds > (threshold * FriendlyTime.YEAR)) return FriendlyTime.getLocalization(('friendlytime.years' + key), Math.round((seconds / FriendlyTime.YEAR))); + + if(seconds > (threshold * FriendlyTime.MONTH)) return FriendlyTime.getLocalization(('friendlytime.months' + key), Math.round((seconds / FriendlyTime.MONTH))); + + if(seconds > (threshold * FriendlyTime.DAY)) return FriendlyTime.getLocalization(('friendlytime.days' + key), Math.round((seconds / FriendlyTime.DAY))); + + if(seconds > (threshold * FriendlyTime.HOUR)) return FriendlyTime.getLocalization(('friendlytime.hours' + key), Math.round((seconds / FriendlyTime.HOUR))); + + if(seconds > (threshold * FriendlyTime.MINUTE)) return FriendlyTime.getLocalization(('friendlytime.minutes' + key), Math.round((seconds / FriendlyTime.MINUTE))); + + return FriendlyTime.getLocalization(('friendlytime.seconds' + key), Math.round(seconds)); + } + + public static shortFormat(seconds: number, key: string = '', threshold: number = 3): string + { + if(seconds > (threshold * FriendlyTime.YEAR)) return FriendlyTime.getLocalization(('friendlytime.years.short' + key), Math.round((seconds / FriendlyTime.YEAR))); + + if(seconds > (threshold * FriendlyTime.MONTH)) return FriendlyTime.getLocalization(('friendlytime.months.short' + key), Math.round((seconds / FriendlyTime.MONTH))); + + if(seconds > (threshold * FriendlyTime.DAY)) return FriendlyTime.getLocalization(('friendlytime.days.short' + key), Math.round((seconds / FriendlyTime.DAY))); + + if(seconds > (threshold * FriendlyTime.HOUR)) return FriendlyTime.getLocalization(('friendlytime.hours.short' + key), Math.round((seconds / FriendlyTime.HOUR))); + + if(seconds > (threshold * FriendlyTime.MINUTE)) return FriendlyTime.getLocalization(('friendlytime.minutes.short' + key), Math.round((seconds / FriendlyTime.MINUTE))); + + return FriendlyTime.getLocalization(('friendlytime.seconds.short' + key), Math.round(seconds)); + } + + public static getLocalization(key: string, amount: number): string + { + return LocalizeText(key, [ 'amount' ], [ amount.toString() ]); + } +} diff --git a/src/api/utils/GetLocalStorage.ts b/src/api/utils/GetLocalStorage.ts new file mode 100644 index 0000000..769df6d --- /dev/null +++ b/src/api/utils/GetLocalStorage.ts @@ -0,0 +1,11 @@ +export const GetLocalStorage = (key: string) => +{ + try + { + JSON.parse(window.localStorage.getItem(key)) as T ?? null + } + catch(e) + { + return null; + } +} diff --git a/src/api/utils/LocalStorageKeys.ts b/src/api/utils/LocalStorageKeys.ts new file mode 100644 index 0000000..6c92279 --- /dev/null +++ b/src/api/utils/LocalStorageKeys.ts @@ -0,0 +1,5 @@ +export class LocalStorageKeys +{ + public static CATALOG_PLACE_MULTIPLE_OBJECTS: string = 'catalogPlaceMultipleObjects'; + public static CATALOG_SKIP_PURCHASE_CONFIRMATION: string = 'catalogSkipPurchaseConfirmation'; +} diff --git a/src/api/utils/LocalizeBadgeDescription.ts b/src/api/utils/LocalizeBadgeDescription.ts new file mode 100644 index 0000000..acf3795 --- /dev/null +++ b/src/api/utils/LocalizeBadgeDescription.ts @@ -0,0 +1,10 @@ +import { GetLocalizationManager } from '@nitrots/nitro-renderer'; + +export const LocalizeBadgeDescription = (key: string) => +{ + let badgeDesc = GetLocalizationManager().getBadgeDesc(key); + + if(!badgeDesc || !badgeDesc.length) badgeDesc = `badge_desc_${ key }`; + + return badgeDesc; +} diff --git a/src/api/utils/LocalizeBageName.ts b/src/api/utils/LocalizeBageName.ts new file mode 100644 index 0000000..50d6ac5 --- /dev/null +++ b/src/api/utils/LocalizeBageName.ts @@ -0,0 +1,10 @@ +import { GetLocalizationManager } from '@nitrots/nitro-renderer'; + +export const LocalizeBadgeName = (key: string) => +{ + let badgeName = GetLocalizationManager().getBadgeName(key); + + if(!badgeName || !badgeName.length) badgeName = `badge_name_${ key }`; + + return badgeName; +} diff --git a/src/api/utils/LocalizeFormattedNumber.ts b/src/api/utils/LocalizeFormattedNumber.ts new file mode 100644 index 0000000..fab30d4 --- /dev/null +++ b/src/api/utils/LocalizeFormattedNumber.ts @@ -0,0 +1,6 @@ +export function LocalizeFormattedNumber(number: number): string +{ + if(!number || isNaN(number)) return '0'; + + return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' '); +}; diff --git a/src/api/utils/LocalizeShortNumber.ts b/src/api/utils/LocalizeShortNumber.ts new file mode 100644 index 0000000..30975ec --- /dev/null +++ b/src/api/utils/LocalizeShortNumber.ts @@ -0,0 +1,36 @@ +export function LocalizeShortNumber(number: number): string +{ + if(!number || isNaN(number)) return '0'; + + let abs = Math.abs(number); + + const rounder = Math.pow(10, 1); + const isNegative = (number < 0); + + let key = ''; + + const powers = [ + { key: 'Q', value: Math.pow(10, 15) }, + { key: 'T', value: Math.pow(10, 12) }, + { key: 'B', value: Math.pow(10, 9) }, + { key: 'M', value: Math.pow(10, 6) }, + { key: 'K', value: 1000 } + ]; + + for(const power of powers) + { + let reduced = abs / power.value; + + reduced = Math.round(reduced * rounder) / rounder; + + if(reduced >= 1) + { + abs = reduced; + key = power.key; + + break; + } + } + + return ((isNegative ? '-' : '') + abs + key); +} diff --git a/src/api/utils/LocalizeText.ts b/src/api/utils/LocalizeText.ts new file mode 100644 index 0000000..68d0273 --- /dev/null +++ b/src/api/utils/LocalizeText.ts @@ -0,0 +1,6 @@ +import { GetLocalizationManager } from '@nitrots/nitro-renderer'; + +export function LocalizeText(key: string, parameters: string[] = null, replacements: string[] = null): string +{ + return GetLocalizationManager().getValueWithParameters(key, parameters, replacements); +} diff --git a/src/api/utils/PlaySound.ts b/src/api/utils/PlaySound.ts new file mode 100644 index 0000000..0f9a39d --- /dev/null +++ b/src/api/utils/PlaySound.ts @@ -0,0 +1,24 @@ +import { MouseEventType, NitroSoundEvent } from '@nitrots/nitro-renderer'; +import { DispatchMainEvent } from '../events'; + +let canPlaySound = false; + +export const PlaySound = (sampleCode: string) => +{ + if(!canPlaySound) return; + + DispatchMainEvent(new NitroSoundEvent(NitroSoundEvent.PLAY_SOUND, sampleCode)); +} + +const eventTypes = [ MouseEventType.MOUSE_CLICK ]; + +const startListening = () => +{ + const stopListening = () => eventTypes.forEach(type => window.removeEventListener(type, onEvent)); + + const onEvent = (event: Event) => ((canPlaySound = true) && stopListening()); + + eventTypes.forEach(type => window.addEventListener(type, onEvent)); +} + +startListening(); diff --git a/src/api/utils/ProductImageUtility.ts b/src/api/utils/ProductImageUtility.ts new file mode 100644 index 0000000..b12c43d --- /dev/null +++ b/src/api/utils/ProductImageUtility.ts @@ -0,0 +1,58 @@ +import { CatalogPageMessageProductData, GetRoomEngine } from '@nitrots/nitro-renderer'; +import { FurniCategory } from '../inventory'; + +export class ProductImageUtility +{ + public static getProductImageUrl(productType: string, furniClassId: number, extraParam: string): string + { + let imageUrl: string = null; + + switch(productType) + { + case CatalogPageMessageProductData.S: + imageUrl = GetRoomEngine().getFurnitureFloorIconUrl(furniClassId); + break; + case CatalogPageMessageProductData.I: + const productCategory = this.getProductCategory(CatalogPageMessageProductData.I, furniClassId); + + if(productCategory === 1) + { + imageUrl = GetRoomEngine().getFurnitureWallIconUrl(furniClassId, extraParam); + } + else + { + switch(productCategory) + { + case FurniCategory.WALL_PAPER: + break; + case FurniCategory.LANDSCAPE: + break; + case FurniCategory.FLOOR: + break; + } + } + break; + case CatalogPageMessageProductData.E: + // fx_icon_furniClassId_png + break; + } + + return imageUrl; + } + + public static getProductCategory(productType: string, furniClassId: number): number + { + if(productType === CatalogPageMessageProductData.S) return 1; + + if(productType === CatalogPageMessageProductData.I) + { + if(furniClassId === 3001) return FurniCategory.WALL_PAPER; + + if(furniClassId === 3002) return FurniCategory.FLOOR; + + if(furniClassId === 4057) return FurniCategory.LANDSCAPE; + } + + return 1; + } +} diff --git a/src/api/utils/Randomizer.ts b/src/api/utils/Randomizer.ts new file mode 100644 index 0000000..1f67a12 --- /dev/null +++ b/src/api/utils/Randomizer.ts @@ -0,0 +1,28 @@ +export class Randomizer +{ + public static getRandomNumber(count: number): number + { + return Math.floor(Math.random() * count); + } + + public static getRandomElement(elements: T[]): T + { + return elements[this.getRandomNumber(elements.length)]; + } + + public static getRandomElements(elements: T[], count: number): T[] + { + const result: T[] = new Array(count); + let len = elements.length; + const taken = new Array(len); + + while(count--) + { + var x = this.getRandomNumber(len); + result[count] = elements[x in taken ? taken[x] : x]; + taken[x] = --len in taken ? taken[len] : len; + } + + return result; + } +} diff --git a/src/api/utils/RoomChatFormatter.ts b/src/api/utils/RoomChatFormatter.ts new file mode 100644 index 0000000..a24cdf5 --- /dev/null +++ b/src/api/utils/RoomChatFormatter.ts @@ -0,0 +1,75 @@ +const allowedColours: Map = new Map(); + +allowedColours.set('r', 'red'); +allowedColours.set('b', 'blue'); +allowedColours.set('g', 'green'); +allowedColours.set('y', 'yellow'); +allowedColours.set('w', 'white'); +allowedColours.set('o', 'orange'); +allowedColours.set('c', 'cyan'); +allowedColours.set('br', 'brown'); +allowedColours.set('pr', 'purple'); +allowedColours.set('pk', 'pink'); + +allowedColours.set('red', 'red'); +allowedColours.set('blue', 'blue'); +allowedColours.set('green', 'green'); +allowedColours.set('yellow', 'yellow'); +allowedColours.set('white', 'white'); +allowedColours.set('orange', 'orange'); +allowedColours.set('cyan', 'cyan'); +allowedColours.set('brown', 'brown'); +allowedColours.set('purple', 'purple'); +allowedColours.set('pink', 'pink'); + +const encodeHTML = (str: string) => +{ + return str.replace(/([\u00A0-\u9999<>&])(.|$)/g, (full, char, next) => + { + if(char !== '&' || next !== '#') + { + if(/[\u00A0-\u9999<>&]/.test(next)) next = '&#' + next.charCodeAt(0) + ';'; + + return '&#' + char.charCodeAt(0) + ';' + next; + } + + return full; + }); +} + +export const RoomChatFormatter = (content: string) => +{ + let result = ''; + + content = encodeHTML(content); + //content = (joypixels.shortnameToUnicode(content) as string) + + if(content.startsWith('@') && content.indexOf('@', 1) > -1) + { + let match = null; + + while((match = /@[a-zA-Z]+@/g.exec(content)) !== null) + { + const colorTag = match[0].toString(); + const colorName = colorTag.substr(1, colorTag.length - 2); + const text = content.replace(colorTag, ''); + + if(!allowedColours.has(colorName)) + { + result = text; + } + else + { + const color = allowedColours.get(colorName); + result = '' + text + ''; + } + break; + } + } + else + { + result = content; + } + + return result; +} diff --git a/src/api/utils/SetLocalStorage.ts b/src/api/utils/SetLocalStorage.ts new file mode 100644 index 0000000..02aa8f3 --- /dev/null +++ b/src/api/utils/SetLocalStorage.ts @@ -0,0 +1 @@ +export const SetLocalStorage = (key: string, value: T) => window.localStorage.setItem(key, JSON.stringify(value)); diff --git a/src/api/utils/SoundNames.ts b/src/api/utils/SoundNames.ts new file mode 100644 index 0000000..4459651 --- /dev/null +++ b/src/api/utils/SoundNames.ts @@ -0,0 +1,9 @@ +export class SoundNames +{ + public static CAMERA_SHUTTER = 'camera_shutter'; + public static CREDITS = 'credits'; + public static DUCKETS = 'duckets'; + public static MESSENGER_NEW_THREAD = 'messenger_new_thread'; + public static MESSENGER_MESSAGE_RECEIVED = 'messenger_message_received'; + public static MODTOOLS_NEW_TICKET = 'modtools_new_ticket'; +} diff --git a/src/api/utils/WindowSaveOptions.ts b/src/api/utils/WindowSaveOptions.ts new file mode 100644 index 0000000..9aa8456 --- /dev/null +++ b/src/api/utils/WindowSaveOptions.ts @@ -0,0 +1,5 @@ +export interface WindowSaveOptions +{ + offset: { x: number, y: number }; + size: { width: number, height: number }; +} diff --git a/src/api/utils/index.ts b/src/api/utils/index.ts new file mode 100644 index 0000000..1824add --- /dev/null +++ b/src/api/utils/index.ts @@ -0,0 +1,19 @@ +export * from './CloneObject'; +export * from './ColorUtils'; +export * from './ConvertSeconds'; +export * from './FixedSizeStack'; +export * from './FriendlyTime'; +export * from './GetLocalStorage'; +export * from './LocalStorageKeys'; +export * from './LocalizeBadgeDescription'; +export * from './LocalizeBageName'; +export * from './LocalizeFormattedNumber'; +export * from './LocalizeShortNumber'; +export * from './LocalizeText'; +export * from './PlaySound'; +export * from './ProductImageUtility'; +export * from './Randomizer'; +export * from './RoomChatFormatter'; +export * from './SetLocalStorage'; +export * from './SoundNames'; +export * from './WindowSaveOptions'; diff --git a/src/api/wired/GetWiredTimeLocale.ts b/src/api/wired/GetWiredTimeLocale.ts new file mode 100644 index 0000000..49025fe --- /dev/null +++ b/src/api/wired/GetWiredTimeLocale.ts @@ -0,0 +1,8 @@ +export const GetWiredTimeLocale = (value: number) => +{ + const time = Math.floor((value / 2)); + + if(!(value % 2)) return time.toString(); + + return (time + 0.5).toString(); +} diff --git a/src/api/wired/WiredActionLayoutCode.ts b/src/api/wired/WiredActionLayoutCode.ts new file mode 100644 index 0000000..5282dc5 --- /dev/null +++ b/src/api/wired/WiredActionLayoutCode.ts @@ -0,0 +1,29 @@ +export class WiredActionLayoutCode +{ + public static TOGGLE_FURNI_STATE: number = 0; + public static RESET: number = 1; + public static SET_FURNI_STATE: number = 3; + public static MOVE_FURNI: number = 4; + public static GIVE_SCORE: number = 6; + public static CHAT: number = 7; + public static TELEPORT: number = 8; + public static JOIN_TEAM: number = 9; + public static LEAVE_TEAM: number = 10; + public static CHASE: number = 11; + public static FLEE: number = 12; + public static MOVE_AND_ROTATE_FURNI: number = 13; + public static GIVE_SCORE_TO_PREDEFINED_TEAM: number = 14; + public static TOGGLE_TO_RANDOM_STATE: number = 15; + public static MOVE_FURNI_TO: number = 16; + public static GIVE_REWARD: number = 17; + public static CALL_ANOTHER_STACK: number = 18; + public static KICK_FROM_ROOM: number = 19; + public static MUTE_USER: number = 20; + public static BOT_TELEPORT: number = 21; + public static BOT_MOVE: number = 22; + public static BOT_TALK: number = 23; + public static BOT_GIVE_HAND_ITEM: number = 24; + public static BOT_FOLLOW_AVATAR: number = 25; + public static BOT_CHANGE_FIGURE: number = 26; + public static BOT_TALK_DIRECT_TO_AVTR: number = 27; +} diff --git a/src/api/wired/WiredConditionLayoutCode.ts b/src/api/wired/WiredConditionLayoutCode.ts new file mode 100644 index 0000000..58cae5d --- /dev/null +++ b/src/api/wired/WiredConditionLayoutCode.ts @@ -0,0 +1,29 @@ +export class WiredConditionlayout +{ + public static STATES_MATCH: number = 0; + public static FURNIS_HAVE_AVATARS: number = 1; + public static ACTOR_IS_ON_FURNI: number = 2; + public static TIME_ELAPSED_MORE: number = 3; + public static TIME_ELAPSED_LESS: number = 4; + public static USER_COUNT_IN: number = 5; + public static ACTOR_IS_IN_TEAM: number = 6; + public static HAS_STACKED_FURNIS: number = 7; + public static STUFF_TYPE_MATCHES: number = 8; + public static STUFFS_IN_FORMATION: number = 9; + public static ACTOR_IS_GROUP_MEMBER: number = 10; + public static ACTOR_IS_WEARING_BADGE: number = 11; + public static ACTOR_IS_WEARING_EFFECT: number = 12; + public static NOT_STATES_MATCH: number = 13; + public static FURNI_NOT_HAVE_HABBO: number = 14; + public static NOT_ACTOR_ON_FURNI: number = 15; + public static NOT_USER_COUNT_IN: number = 16; + public static NOT_ACTOR_IN_TEAM: number = 17; + public static NOT_HAS_STACKED_FURNIS: number = 18; + public static NOT_FURNI_IS_OF_TYPE: number = 19; + public static NOT_STUFFS_IN_FORMATION: number = 20; + public static NOT_ACTOR_IN_GROUP: number = 21; + public static NOT_ACTOR_WEARS_BADGE: number = 22; + public static NOT_ACTOR_WEARING_EFFECT: number = 23; + public static DATE_RANGE_ACTIVE: number = 24; + public static ACTOR_HAS_HANDITEM: number = 25; +} diff --git a/src/api/wired/WiredDateToString.ts b/src/api/wired/WiredDateToString.ts new file mode 100644 index 0000000..825adc8 --- /dev/null +++ b/src/api/wired/WiredDateToString.ts @@ -0,0 +1 @@ +export const WiredDateToString = (date: Date) => `${ date.getFullYear() }/${ ('0' + (date.getMonth() + 1)).slice(-2) }/${ ('0' + date.getDate()).slice(-2) } ${ ('0' + date.getHours()).slice(-2) }:${ ('0' + date.getMinutes()).slice(-2) }`; diff --git a/src/api/wired/WiredFurniType.ts b/src/api/wired/WiredFurniType.ts new file mode 100644 index 0000000..447e970 --- /dev/null +++ b/src/api/wired/WiredFurniType.ts @@ -0,0 +1,7 @@ +export class WiredFurniType +{ + public static STUFF_SELECTION_OPTION_NONE: number = 0; + public static STUFF_SELECTION_OPTION_BY_ID: number = 1; + public static STUFF_SELECTION_OPTION_BY_ID_OR_BY_TYPE: number = 2; + public static STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT: number = 3; +} diff --git a/src/api/wired/WiredSelectionVisualizer.ts b/src/api/wired/WiredSelectionVisualizer.ts new file mode 100644 index 0000000..18edbf7 --- /dev/null +++ b/src/api/wired/WiredSelectionVisualizer.ts @@ -0,0 +1,85 @@ +import { GetRoomEngine, IRoomObject, IRoomObjectSpriteVisualization, RoomObjectCategory, WiredFilter } from '@nitrots/nitro-renderer'; + +export class WiredSelectionVisualizer +{ + private static _selectionShader: WiredFilter = new WiredFilter({ + lineColor: [ 1, 1, 1 ], + color: [ 0.6, 0.6, 0.6 ] + }); + + public static show(furniId: number): void + { + WiredSelectionVisualizer.applySelectionShader(WiredSelectionVisualizer.getRoomObject(furniId)); + } + + public static hide(furniId: number): void + { + WiredSelectionVisualizer.clearSelectionShader(WiredSelectionVisualizer.getRoomObject(furniId)); + } + + public static clearSelectionShaderFromFurni(furniIds: number[]): void + { + for(const furniId of furniIds) + { + WiredSelectionVisualizer.clearSelectionShader(WiredSelectionVisualizer.getRoomObject(furniId)); + } + } + + public static applySelectionShaderToFurni(furniIds: number[]): void + { + for(const furniId of furniIds) + { + WiredSelectionVisualizer.applySelectionShader(WiredSelectionVisualizer.getRoomObject(furniId)); + } + } + + private static getRoomObject(objectId: number): IRoomObject + { + const roomEngine = GetRoomEngine(); + + return roomEngine.getRoomObject(roomEngine.activeRoomId, objectId, RoomObjectCategory.FLOOR); + } + + private static applySelectionShader(roomObject: IRoomObject): void + { + if(!roomObject) return; + + const visualization = (roomObject.visualization as IRoomObjectSpriteVisualization); + + if(!visualization) return; + + for(const sprite of visualization.sprites) + { + if(sprite.blendMode === 'add') continue; + + if(!sprite.filters) sprite.filters = []; + + sprite.filters.push(WiredSelectionVisualizer._selectionShader); + + sprite.increaseUpdateCounter(); + } + } + + private static clearSelectionShader(roomObject: IRoomObject): void + { + if(!roomObject) return; + + const visualization = (roomObject.visualization as IRoomObjectSpriteVisualization); + + if(!visualization) return; + + for(const sprite of visualization.sprites) + { + if(!sprite.filters) continue; + + const index = sprite.filters.indexOf(WiredSelectionVisualizer._selectionShader); + + if(index >= 0) + { + sprite.filters.splice(index, 1); + + sprite.increaseUpdateCounter(); + } + } + } +} diff --git a/src/api/wired/WiredStringDelimeter.ts b/src/api/wired/WiredStringDelimeter.ts new file mode 100644 index 0000000..bc4cf2e --- /dev/null +++ b/src/api/wired/WiredStringDelimeter.ts @@ -0,0 +1 @@ +export const WIRED_STRING_DELIMETER: string = '\t'; diff --git a/src/api/wired/WiredTriggerLayoutCode.ts b/src/api/wired/WiredTriggerLayoutCode.ts new file mode 100644 index 0000000..fd758df --- /dev/null +++ b/src/api/wired/WiredTriggerLayoutCode.ts @@ -0,0 +1,17 @@ +export class WiredTriggerLayout +{ + public static AVATAR_SAYS_SOMETHING: number = 0; + public static AVATAR_WALKS_ON_FURNI: number = 1; + public static AVATAR_WALKS_OFF_FURNI: number = 2; + public static EXECUTE_ONCE: number = 3; + public static TOGGLE_FURNI: number = 4; + public static EXECUTE_PERIODICALLY: number = 6; + public static AVATAR_ENTERS_ROOM: number = 7; + public static GAME_STARTS: number = 8; + public static GAME_ENDS: number = 9; + public static SCORE_ACHIEVED: number = 10; + public static COLLISION: number = 11; + public static EXECUTE_PERIODICALLY_LONG: number = 12; + public static BOT_REACHED_STUFF: number = 13; + public static BOT_REACHED_AVATAR: number = 14; +} diff --git a/src/api/wired/index.ts b/src/api/wired/index.ts new file mode 100644 index 0000000..6590adf --- /dev/null +++ b/src/api/wired/index.ts @@ -0,0 +1,8 @@ +export * from './GetWiredTimeLocale'; +export * from './WiredActionLayoutCode'; +export * from './WiredConditionLayoutCode'; +export * from './WiredDateToString'; +export * from './WiredFurniType'; +export * from './WiredSelectionVisualizer'; +export * from './WiredStringDelimeter'; +export * from './WiredTriggerLayoutCode'; diff --git a/src/assets/images/achievements/back-arrow.png b/src/assets/images/achievements/back-arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..e795c0ee17a2c814a38a2162768cea21d1415327 GIT binary patch literal 331 zcmeAS@N?(olHy`uVBq!ia0vp^ia@Ny!3-pyt#kMcq!^2X+?^QKos)S9^9T5ZxB}^$H*fy`|39ay8OWaA{>2MOv6ck+1p@{DGyLAh7Yr2OEbxdd zW?urU&rLVW89&r!frI5JWMbB%utImyY{}>HFbLOx7 RSP$|ngQu&X%Q~loCIDqWec1p2 literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/arrow-left-icon.png b/src/assets/images/avatareditor/arrow-left-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f94a7dfdd74fedb7e359d24ca60df046b92f5d96 GIT binary patch literal 198 zcmeAS@N?(olHy`uVBq!ia0vp^GC(ZK!3HElnfhXYRIjItV~9uR(J6s^2NXD*6F>e7 zFN;(xN1y`;ub@ac-@%FyZMczwQ!VaBim-gV} zHLN{n1gjlyY=}}Qkh5mO3C!NTWW<2{{ rd%>5 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/ca-selected-icon.png b/src/assets/images/avatareditor/ca-selected-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b118c3ed606a0dcc4f80f16b734481d1f9648bf5 GIT binary patch literal 335 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~%3?x6Bmj(hU#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6-M0X`wFrB?h)O2i)>Q2zhGKd^J^+8h7VHtYdP%3nFu1f)1hg8YL2Ljc3Q z=Dm?XG0p;y$YKTtzQZ8Qcszea3Q%y3r;B5VMsV!}PO-xZ94>_`@BKeEx3%TT(wBS0 z3il}(ZW9S#x>R88_g^y&59HriCRJ9%+b1R4``wC&@9GvDvU%kAX) UJFE9tGtk`(p00i_>zopr0JCL=CjbBd literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/cc-icon.png b/src/assets/images/avatareditor/cc-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4a8844e49c173bce47e93ac4fdc082b9a02db93b GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^@<1%h!3-o*cen#d2FBtbcPEB*=VV?2IV|apzK#qG z8~eHcB(eheYymzYuB%tC1~R=BGZKLmQ%R6t@PCG<+YP*cJkA1-$YKTtzQZ8Qcszea z3Q(}$)5S5w!hdRiAm0H69;dJWrpIM6&Rr?L&n0zFT3+%bLz(X}id_del(Jc>k3~o& z{JGATetkQuUQ&+jzGltcwW|GgW^zxCrXQQP@P<-Ez4wz){j>kh>eqgr_rhiiTUCDT z=Vy;>Fa3V@k7wSbbrYm!?_C*o>Yj3F@a^*N>R*>wC7e&M{w#c)k?-L2k1Q*m|6%y^ XjcH^3xig=DPGj(N^>bP0l+XkKEAnbY literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/cc-selected-icon.png b/src/assets/images/avatareditor/cc-selected-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3493751af159b7b23c4ca77cc536e885758b500d GIT binary patch literal 338 zcmeAS@N?(olHy`uVBq!ia0vp^@<1%h!3-o*cen#d2FBtbcPEB*=VV?2IV|apzK#qG z8~eHcB(ehe`~f~8t_(&YD-K_(o4O&acoIIKNf-ZeOfgnYrMkJ8MttJO!Qv?bK^! z-xCF9KTbuP$4%w7 zUvqvz<*}&(*72Di#e|mp@^9R?N6YKe?A$+F_i+V%>L_0nUnH7;mdUc@W?04NJ&}H0 bAEV?qbh90lIj_kH^ag{ctDnm{r-UW|;Xr}e literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/ch-icon.png b/src/assets/images/avatareditor/ch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ef7da1131b33a74fbc636893bee218a0e8fc1e9f GIT binary patch literal 228 zcmeAS@N?(olHy`uVBq!ia0vp^vOp}s!3-n~8`%1Q6k~CayA#8@b22Z19F}xPUq=Rp zjs4tz5?O(Kwg8_H*VU_61DW268HqrOsU*lR_&>wb?FL>z9%q3^WHAE+-(e7DJf6QI z1t=Kl>Ealo5&X7~k?(*42lLba^W{V~SQz|1pm)n*^&_EV$rU_h0pd>j-2u&e-)(vP zNUl+1c3;|ef%UGYAEzee**{vhYQ6ZE<@M9e+aKobv|r+@T_0t6sc)AOzgZ4b(3$x= R{ehM+c)I$ztaD0e0su4!N%{Z) literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/ch-selected-icon.png b/src/assets/images/avatareditor/ch-selected-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c5e9f3402c61cf8041f5555d09e633395f0c6e1c GIT binary patch literal 260 zcmeAS@N?(olHy`uVBq!ia0vp^vOp}s!3-n~8`%1Q6k~CayA#8@b22Z19F}xPUq=Rp zjs4tz5?O(K{s5m4*BSwVTb@q;GivuMDFMX}b<7QvtYXYby9AtTu4%PmiZLn^zX1eC2N zny$Rfa7$GxKl@yvo`u)qpgb%0<)^Z(7kr+`+!65I%;K%5wT{!z6$;XmHyLdYn;E!v xs$;9E8Ef?C=eN?=-+lD{+WeZ0`5St7Ficv=@`S(Y+I*nJ44$rjF6*2UngCQATr>ax literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/clear-icon.png b/src/assets/images/avatareditor/clear-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e0d50abca8bec826229748f4b83deb79888594af GIT binary patch literal 272 zcmeAS@N?(olHy`uVBq!ia0vp^(jd&i3?z4Pv7`ej#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6-$0X`wF_4V~f4(;E#ZY5Aa=f>$fAax}{e!>6$|7UQ}TKNUY=PdAuEM{Qf zI}E~%$MaXD00nD3T^vI+f@9BK6l*ZxU<%0o|Ns0=2i97NzT1(7&)+!8`k!-~vZ3`& zMqB7q&NAW6c~&mnS{@mb4_%jPsh2$%&9G zmoNLZE=rwryL_l@|E*lxWQo}PtW$!Y%=830gu&C* K&t;ucLK6Vke`HAj literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/cp-icon.png b/src/assets/images/avatareditor/cp-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5e460f153c0b0988a9505db216b28110b07db358 GIT binary patch literal 252 zcmeAS@N?(olHy`uVBq!ia0vp^azHG>!3-pCZQt7qq!^2X+?^QKos)S9vjzBsxUOEk8p!lk%t!=MOeH~n!T%YaZa44(@;D1TB8wRq_zr_GaKbQY1a@}Fm6}m~`>&q8vu)N;TVYE!|+a`u&wO72?R>qmV qDeV;h-&3mp_?i0O4ry20=Zxl?n5-6Pvfcn%&fw|l=d#Wzp$P!U99262 literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/cp-selected-icon.png b/src/assets/images/avatareditor/cp-selected-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a067085d27617cfad160ea776fe1a47c29d505a6 GIT binary patch literal 280 zcmeAS@N?(olHy`uVBq!ia0vp^azHG>!3-pCZQt7qq!^2X+?^QKos)S93kCRuxXzzHf7!BSjvd+ue;ds_F9H;HRZvU-Qfwtbe!>4ifMN6gCHsLQ zoCO|{#S9F5he4R}c>anMpkS@1i(`mJ@Y)H1d<_ab&Z1BLg>Q@9e&Ea(=h}gjE$ zrMtLq_Vb|0Dlz3n6QX(BHB1lsO>$A0_TuQ1UcdcMCt2OkUMgO9g;k=zkjY<`+w1Kw Skx-yR7(8A5T-G@yGywo)XJ@+r literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/ea-icon.png b/src/assets/images/avatareditor/ea-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c227ae10719909356e47d91a1e909c2cbeb4f16f GIT binary patch literal 251 zcmeAS@N?(olHy`uVBq!ia0vp^%0Mi@!3-oNn{1`*#dk*TvxAN4P<&NW+VbBrjj7P;QtIyw;Ol?d7K3vk;M!Qe1}1p@p%4< z6rf;%r;B5VMsR7LHy?u{kJItr@l_3%`rYm?V)g2|ZzR9bE{5xkV7rX@dY?&sNwHeW zSN4QiJ*xQj|&KtsII?S=#xz@NXRLSDxzI>gztmzv^kx13es6AH78|{>K_<6k* o^!+p0h3^gHS)UcX!YUeGT0cbaar>mdKI;Vst0AOQM?*IS* literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/ea-selected-icon.png b/src/assets/images/avatareditor/ea-selected-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e7678c4a6e23d14dce0ebe95250d35b4a9d2878a GIT binary patch literal 298 zcmeAS@N?(olHy`uVBq!ia0vp^%0Mi@!3-oNn{1`g#vs+T!FNUtKIw~m;V3%zvsa_AlGRJgD{X{D+%%o1`7WF&#-y_lKnst z&H|6fVg?4j!ywFfJby(BP_W(fgu4wvK>mrA%^C$L3{&Ts;&@aBEh~;MRnH|2FRjvt zdO!6#|KFAsu|6LAln$Ix(La>LSFVdQ&MBb@00~QN0RR91 literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/fa-icon.png b/src/assets/images/avatareditor/fa-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9b72ff5e388aab53852cf97e5cf7e0ddc54e8172 GIT binary patch literal 234 zcmeAS@N?(olHy`uVBq!ia0vp^(m*W2!3-q5W43JqQjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`*#dk*TvxAN4N|U{kqD%iN`m}?|1&(@Zr}yvaTa()7BevL9R^{>I6wS1U2bcM<}RJzH-BHSlk)88@eo+xZ`JbACQvcx@2m8x z>hj+b1??}UvYcGIY07rVBwe;SGc!LOx$d{SCG}YHh98TgE6YD#o0^k#=Ka~ch-a*q X?lT^D)k}>9TE*b$>gTe~DWM4fpL|kq literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/fa-selected-icon.png b/src/assets/images/avatareditor/fa-selected-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a1d26b61c58fa6c489d55f6f26d790f9af6a9ad0 GIT binary patch literal 286 zcmeAS@N?(olHy`uVBq!ia0vp^(m*W2!3-q5W43JqQjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1``2&1HTvb(7gMxxeN=i0v+z1q#rQ&x2NU@d#`33(60*2q)_=14~oCO|{ z#S9F5he4R}c>anMpkTYFi(`mJaPNd@K2}8zXU3cVXDxqf*m%-(=ih)+fdWTe=LoI3 z_QgbU)y!2I+Y4tXPEgX{$i|ZtGvSfw!_*a@3z>}+*mJs>FP=PS>7o&8$bDG&&^O^J zYIkmLyJsDnUiUKo+SVxLbBgJU$~5es9$Ud3>{Gka)8bJ3`31=5EbxddW?978mMmrn2&YB1n%zIgBdxk=Nn_{Z)rI44jetFe?#Ys#DeX`_&g z4U5~{L=`$iJEB^@vM=m3p6{r}+&bg0i+A;$m9`Hp!|m70ukz7Yld6=G_R)Ltfo#i7 zZT+IBVqL6i9cMYTZjXzyExt2B-)HUN-2wk<7u$p;_b|`aWIpWb+{zAg27{-opUXO@ GgeCy3+F73f literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/ha-icon.png b/src/assets/images/avatareditor/ha-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f0a819181a29792921dad2a3f95d3c8ff3d10ee4 GIT binary patch literal 241 zcmeAS@N?(olHy`uVBq!ia0vp^l0YoR!3-ps5|6h4DaPU;cPEB*=VV?2IV|apzK#qG z8~eHcB(eheYymzYuB%tC1~R=BGZKLmQ%R6t@PCG<+YP*cJkA1-$YKTtzQZ8Qcszea z3Q#c3)5S4FBY18er|(%y z{=_9$w%KyEub0!G5_Va3d5?Ed=dBWH?j1)iL|EQ>YM3Q*n0sTPNV)N)-v4J4t3UFb euC6ot?y~2F5aTSBDtA7hjSQZyelF{r5}E*2KvHM` literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/ha-selected-icon.png b/src/assets/images/avatareditor/ha-selected-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4c81ece52017e6bc405c53de21fe9909f3681724 GIT binary patch literal 285 zcmeAS@N?(olHy`uVBq!ia0vp^l0YoR!3-ps5|6h4DaPU;cPEB*=VV?2IV|apzK#qG z8~eHcB(ehe`~f~8u1;o}{~sN?wxe@FaT-u;r|NotAjMh|*Eat0&tY{yy{OV_~cAMCEyDJ73ob_9aCrs(XlKibbR-`ORD7 zH&N?w+(geuf`L!GJ1jnZF@MOPe(dg?;O=UZj+U=t(l3%ftG4I8UZPNWT>kIm%?;89 X>seNroV%kAbPvjzBsxUOEk8p!lk%t!=MOeH~n!T%YaZa44(@;D1TB8wRq_zr_Gpyr+tv%qUoUrjoH&hw1Q2zL$r9Zej3r^>bP0 Hl+XkKr9@%l literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/he-selected-icon.png b/src/assets/images/avatareditor/he-selected-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..32633559b9cba03803a5ac7f0b9d669bd521393f GIT binary patch literal 338 zcmeAS@N?(olHy`uVBq!ia0vp^@<1%j!3-ofKU^XUq!^2X+?^QKos)S9G*n&l!lGtJnsL;GUC&%xhDGtY}W+}ig4<_4gA5tn-+km4)}@(cbC z1q{Ld6+$4v0*}aI1_r*vAk26?e?hZT5S0u5P8{tNGp?zmVn zd%uUEoL;g(?#XHXi_AngqZbMCPM;!EB~>h^xBOk5(Qe&_*-_3!?UI-f1Z6iZRpzl znW47qnD)CqRy*t0IqSsDUFttxZ~9szu_w%&&+mAXRrHSCk9yklZ02a&{68Ne|6&eb V`=SVwe?Vt5c)I$ztaD0e0st?%gCYO` literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/hr-icon.png b/src/assets/images/avatareditor/hr-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..de299902af5abc9ea6f8cebf1174138a5d050cd4 GIT binary patch literal 257 zcmeAS@N?(olHy`uVBq!ia0vp^vOp}!!3-pS-MOj_q!^2X+?^QKos)S9vjzBsxUOEk8p!lk%t!=MOeH~n!T%YaZa44(@;D1TB8wRq_zr_G- x=A1mb%3EuL{k@dTcOIu%uNZE!IDFVpzlM`3y6(@BdY}s!JYD@<);T3K0RY~WTu}f3 literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/hr-selected-icon.png b/src/assets/images/avatareditor/hr-selected-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c694b82a5097b6f2ff485fe696db74429cb2b0e0 GIT binary patch literal 348 zcmeAS@N?(olHy`uVBq!ia0vp^vOp}!!3-pS-MOj_q!^2X+?^QKos)S9^9T5ZxB}_6wziOvkfYny0@;58Eam|z){-E<;Qv6t@Ov9yFi?QAz$3Dl zfr0NZ2s0kfUy%Y7JnHG<7@`rJJBd-KMS+J|`^SItcr)Lp?~e6wz1`Muao6Ha=Wd+j zDP0)4Rc!JHoAtR>2M@_-?O3$8b^A2V+#hYT`Q99L4LNixhPl_~(w?h#t^_>gRp%-# zdcc46>6(Q#A9l4TIXEjH^!sqiu=CW9D^K^R{5Dg&xr_bTnS1B%MJ#g&b_#dnD=m;x zw>isMzT%O%)!z40ek@E+4zv@tKM-)nVDc(W;E lm6K|w6kqZ?yWiv5U&gxMY-$PxpFttQ;OXk;vd$@?2>^AXjfwyO literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/lg-icon.png b/src/assets/images/avatareditor/lg-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0bdd750e4ec304e7725bcef1891d2957e09f4d77 GIT binary patch literal 196 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv-!3-oLy1%&rDaPU;cPEB*=VV?2IV|apzK#qG z8~eHcB(eheYymzYuB%tC1~R=BGZKLmQ%R6t@PCG<+YP*cJkA1-$YKTtzQZ8Qcszea z3Q*9})5S4FBRDxBCB=c&fl#~JO0!)j<&Nw|?%yjAKhD{IF iNiroFFeDorFfbf?$;hl_Ccq6egu&C*&t;ucLK6Unk}_KW literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/lg-selected-icon.png b/src/assets/images/avatareditor/lg-selected-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7a2853b0272603ea329037f4fbe060b4b8ab4ca9 GIT binary patch literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv-!3-oLy1%&rDaPU;cPEB*=VV?2IV|apzK#qG z8~eHcB(ehe`~f~8u0T4fXM|AsWF)Cq{A|P~c%rKKu9ln>{)a3orP)d(3;>v|#6L&O^C2 zJ+THFLM4)KQX@l@A1s>Qt+ZU(C-_<9(dOy1uhz%h)7p0Vv36`paLej1Z)g2u-puNK Xk8$b8p!dx{3m80I{an^LB{Ts5YVA@) literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/loading-icon.png b/src/assets/images/avatareditor/loading-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..50d132b3e4e950ee4f6ab1622d9ebcb51fc157b2 GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^qChOk!3HGnT}^!pq-s1}978-h-%hp`YB1n%xw!G` zKlY~#7Gi}mEjpYfX{S>@A1W6X46^#_=zopr0597~LjV8( literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/male-icon.png b/src/assets/images/avatareditor/male-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..95a1b35267aeeba204ae4c2076fe2c26a0daa767 GIT binary patch literal 228 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw{3?%2B3|#`G7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`1M1^9%xu3o(w$n;jsNCZ+$B|(0{{~4ZcH}C@TI14-?iy0XB4ude`@%$Aj zK*2~)7sn8d;I)0;d<=>l&JX`h5BtrSbnLE) literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/male-selected-icon.png b/src/assets/images/avatareditor/male-selected-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..85debbb3ef2634ce46e8790558cc1c98c4b66ff9 GIT binary patch literal 256 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw{3?%2B3|#`G7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`211o(uw0_jCh{s--N4Pah(T>d>cMXHD{tH>YCql~P|BA!Aznz{IpcBNkeQjEnx?oJHr&dIz4iKRRGIx;Y9 z?C1WI$O`1M1^9%xu3o(w$n;jsNCZ+$B|(0{{~4ZcH}C@TI14-?iy0XB4ude`@%$Aj zKtWee7sn8d;HiC#Tnq|4OxOS0-(6BNRj@=ypmcGDlA3DE>72lh`(jh0+_(K-6eapZ vT6tR7A(?xdS=l!io9=M@Gi@!Sv9i^fe+(Z@mIyBd8pz=3>gTe~DWM4fPHa9M literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/sh-selected-icon.png b/src/assets/images/avatareditor/sh-selected-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..12c6deb1fb8d7e519e7cb0a09125ddaf7d5b599e GIT binary patch literal 266 zcmeAS@N?(olHy`uVBq!ia0vp^szA)e!3-o>cBNkeQjEnx?oJHr&dIz4iKRRGIx;Y9 z?C1WI$O`0(2Ka=yP8JZq;x7Fu(+((b`SRue|NjFSD_<0e0x9;AAirRs2uM)Mo7oL0 z#981GSYvNhADcs zKkBpUxMa1kV$FfIJg)AN%YCwAWYrn=9V)v%P0IgfE_d288PDjtjN+il6TB3syJiFm y|FHTS%qeDMS=Xw#_C`)dYSE%4Uk}%RW~gaA#WF34(QN_HS_V&7KbLh*2~7a2cUs5* literal 0 HcmV?d00001 diff --git a/src/assets/images/avatareditor/spotlight-icon.png b/src/assets/images/avatareditor/spotlight-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8755373c5083cd5c8956dc4279b0d608c46e6d16 GIT binary patch literal 11373 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/campaign/available.png b/src/assets/images/campaign/available.png new file mode 100644 index 0000000000000000000000000000000000000000..1cc8fa6219b2b4ff56e4c8885a945087f240321b GIT binary patch literal 1118 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�k3-p>X_y`} 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 VBLJ_-_G175002ovPDHLkV1hQ-?a2TD literal 0 HcmV?d00001 diff --git a/src/assets/images/campaign/campaign_opened.png b/src/assets/images/campaign/campaign_opened.png new file mode 100644 index 0000000000000000000000000000000000000000..9ec1bb30bed39dec6fee41a5bdec960426270574 GIT binary patch literal 744 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/campaign/locked.png b/src/assets/images/campaign/locked.png new file mode 100644 index 0000000000000000000000000000000000000000..3805e50234425d507d37f332ebdfac58893849d5 GIT binary patch literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^S|H591|*LjJ{b+97JIrlhE&{od-EXg0Rj_^|XUoodbBOldh^Av|0UeLaAs?KfJs}@0v!#O1sw06wfaxt-!-|7FE-m&Qu2iZyVZp!msfIR zF{e7NHM~-OSl{CHTAu2!`dgd@6&t6lDp}%XwDMNqsuNRJ9a}0V{bXMLDY+wY!f))> Un*QSa2Xrojr>mdKI;Vst04zgSJpcdz literal 0 HcmV?d00001 diff --git a/src/assets/images/campaign/locked_bg.png b/src/assets/images/campaign/locked_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..93927dd5a877ab6ca31bfd8d289da1a252a39180 GIT binary patch literal 5414 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/campaign/unavailable.png b/src/assets/images/campaign/unavailable.png new file mode 100644 index 0000000000000000000000000000000000000000..dc134c3f12faa3d00aa4302bfa0ec7a5ca81608e GIT binary patch literal 448 zcmV;x0YCnUP)KNfDQ5dg1UGAYMfQUf;Uh)ngA-7Z1vq1^sisOZ0IbmyeMagsll2%NTpG!d;~iY!UQuJ;u6dX5T{`FFs4Erdhm98 zd&pnWSs3a`rOPPMJHIsJJ`u!aDPjtUXKN3lXl+514Uq02>IR4yr3{E^m97hj^`w>s zQEd-M=WiMH?KTEviGi8&^*jz_$#0nsS4IhtijzcbK*moYW{or2gQU&80|H2|p?msb z6(E50T)6`T5I_I{1Q0*~0R#|+?jInOWqy@qQn(Y&t|07)h{-GJL5OgffFj1rw576&cr68X_~ttRzGv$XS^2Nq0wK qQXw)y{>6S}9#bmFPwGU0V|fCdK~2vw4K_dk0000 literal 0 HcmV?d00001 diff --git a/src/assets/images/campaign/unlocked_bg.png b/src/assets/images/campaign/unlocked_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..31c35ba172146dd1af89db8539d582b033c9dde9 GIT binary patch literal 8798 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 UIckVaaR2}S07*qoM6N<$f)^J^t^fc4 literal 0 HcmV?d00001 diff --git a/src/assets/images/catalog/diamond_info_illustration.gif b/src/assets/images/catalog/diamond_info_illustration.gif new file mode 100644 index 0000000000000000000000000000000000000000..d082ef4d7bab28c5ba2721c86b3420873483fcb7 GIT binary patch literal 3883 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 BUg7`% literal 0 HcmV?d00001 diff --git a/src/assets/images/catalog/target-price.png b/src/assets/images/catalog/target-price.png new file mode 100644 index 0000000000000000000000000000000000000000..8639afd55a62dac6f6cafdd4c33c4195fcefa083 GIT binary patch literal 3562 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 LNkvXXu0mjfkP_;u literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_0.png b/src/assets/images/chat/chatbubbles/bubble_0.png new file mode 100644 index 0000000000000000000000000000000000000000..da685365b15b4c8852768fa83a4c7f4032ae3a9a GIT binary patch literal 5025 zcmeAS@N?(olHy`uVBq!ia0vp^#y~8=!3HG5PaVAvq*#ibJVQ8upoSx*1IXtr@Q5r1 z(hx8=NzI6XLD<>T#W5s;lhH!>_!~x;92)%}jXxN<7;Z-w0DI!hnKLwT{H$5C7)nb^ zsbaROsw&JJxF6sQpv-@eC;0jKVT!0qcXxL)%$zxss%GA~bB6)yDX>47pr+H*BUJSP zl3^+;Dh#)8-v)_*QxwcINJ@q-6XY3itbn}m@#DuK(?1BDdGL$JmrJv>BH&wxC1_Uu`>hd_K<0-AdU zfqcsik45NvO39ap+srj%D8a!s+zkg3t3+Z7vtU1Hz>gxJ`_wL;^ zwe#)Uw^TRZ!omV%Cb-*-JO%&~1b07a;VD5uL2w%_DJcnL55)lL36ME(KfoDirVq)X Yrp?c?G=9fdCW8Fp>FVdQ&MBb@02Mod761SM literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_0_1_33_34_pointer.png b/src/assets/images/chat/chatbubbles/bubble_0_1_33_34_pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..e7a4740b2fc72549fb992a49db54d7400815a3fa GIT binary patch literal 127 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRR!3HD+k8e2&q})7R978x}mY&_nd%%Ii`C|Re zM!`M*9poC@cs(Y&2<|HGod1F8mYK1jW13PYx5FjDKbOxRU$<)8-S@eAH3dyu8<)T1 bVBEpjTdy4PmwjbC&_o7LS3j3^P6RK!t$(9Kxk3Z#ij#>vjomg=2SD-+MU_3W!Fse%V)p&7bo8_m{BWa ze8$*vIp-N^mQ#TShHjJ28JHPwH#*(COk7HA-K@O%o3HfE@Hu@4=z0cES3j3^P6X1^@s6oR$`P0004qNkl9C6o)^eNI3vfwnVK&PQa22bclKdW?Ct0bBit=klM_VdV}T=t!FC1MyDv*ySWQa zA^ed^K#(Y(U2q4a=qCJzMEoZCJBaRBdX0vJ6 zn~JFYs-kmP0FD+^W~b6uE#4GbX?8AL#dMX-o+%>?Lw>(s_Rl~4do0V^ABt_GE#~>* zF{k$fAw-StoQ9T|vbWxah$ytg2qAcSd*$xt7Sl9=^tai3KJSF6YXMMP3qXThQ~|iC zJ<|h>yg$ivVltTk7?lb@*HFiUB6*`C9mi>GVc+-hJg@e7r_(9KI+BqwR;!f+s9Tip r`?VK}SOg>jQgqj)b5GVb8Q=c^ylJe^;k}b200000NkvXXu0mjfu9C`- literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_10.png b/src/assets/images/chat/chatbubbles/bubble_10.png new file mode 100644 index 0000000000000000000000000000000000000000..cbf25a8346f14473a170a7591d20270290883291 GIT binary patch literal 778 zcmV+l1NHogP)~_9m zGUQ>78PM0~$29$XPp{8U6OM50QNT{`Z!a@I$ov_{<=W_A&t4Qz03N;z-VGA2jSBYa zNdn5i3ET{JR2oMQveyEbAmN-TjnAV2Jtv%N>?K|!&m~?T8Yt5fSvN^ee+RPgkhDdj z;cUy`*pnmI(-@BdEYlash_3^51z29wYMkyyjR}Sm5QC1*LtQP6*;>y%V%&FI#$~Wx zg-S|lZLXF&W7Sgwo>V1a0Jnl9%mNs)R}JJ1DE?xqCd% zh~5TJqOe8fNwY0mUE>5L25V{*E`jl^KO7DTQvfjPY1DW#-D`QBJJxg!igBP(6}TOMHjl1=!U+s6|}{$O)*$bG;K3>re%>6r=3zt*Pk& z$n2>Cnu}4DwKi`{Pv-8`@IW#u0bRr>YSUUDYyIed)z2<5S_AOjq$x&616oU6bANd6 z;5={n(up1&C?!3W^Y+G8Gs62(^x|t1$z)u@%{$l2H7c`ms;F+5ZO+Pryy_d zbe!sy+-Ae^#$cCh!+H*|dEV4v%D&LEd4RpaKVw&5dBnE(I)07*qo IM6N<$f)hh)u>b%7 literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_10_pointer.png b/src/assets/images/chat/chatbubbles/bubble_10_pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..f7bd858733761b97ed9e75816a7a4ff468bdd513 GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)H!3HEvS)PKZKu;IPkcwN$2?+^5=1-c`)adw# z=fC~kKmF?%j<9#%x7hj1eiPFM7J-H%l49Sd{dEsxn9R^=+`y8+m6gOaLG{3-0#*gj k63!h=6I9HAWC|ZcVRzDUt0TEzfrc}9y85}Sb4q9e0Qzq%wg3PC literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_11.png b/src/assets/images/chat/chatbubbles/bubble_11.png new file mode 100644 index 0000000000000000000000000000000000000000..a8026d6708200ddce03e38dbfe0c12de677e81ee GIT binary patch literal 242 zcmV3XOoGqgNJy9|#UK-A3qcnXA3&x~BpfIKoRJOlCs$PQY1gf5<;mq%#s8G3qz z=AMCNdRpfdx+Sx)r}Xm>T?zntd1$nr8TAYuJwtO3(b_W@{TZjDuZE3h;2xp{hE<|H skN;C0;~fW}m8YofA(BUFhT2dQ0F4642Q+|LZU6uP07*qoM6N<$f+4bHaR2}S literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_11_pointer.png b/src/assets/images/chat/chatbubbles/bubble_11_pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..d6c4482a78ccdaa019d7a008eef931d52fe29636 GIT binary patch literal 104 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRR!3HD+k8e2&r1U*q978H@B_|{#{IGxjvz}+q zcjXqglg195HCzX}0z@7KH7pdE&LqN0ogggbd@TS`mnqaU;uD+ozMtF*T9-*0MaCm6g zcm^e154B{5mWODU;gJJ~nwcL@fiUWsk?R?dM}~!GK%M~EK}(O&#WVEs2+ci1Pmj>t zGq6lg>%2m@WcE5@E8RRqmjZxZ9vZD@Mm#*?*+(Wd$uuAmD s+Ur!uc*g-~+dM6aWAK literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_12_pointer.png b/src/assets/images/chat/chatbubbles/bubble_12_pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..309a55063678d9809a728aed7d9dbf5fa64d62cd GIT binary patch literal 104 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRR!3HD+k8e2&r1U*q978H@B_|{#{PgT4u0(fDMN#c4}d+Whb%O~SK#qcMj)rA;XSC!ZHJBE{Nv#@+cF`*GW=s>rZPKR)U8JIjX2zxw z5Hw@k2nd?-+!mFcT@HL#N}_<6nIs+2+3x^YGbCCwVSixNkoeUc_6K-wOIil_|7gxG z4xlb?4$I!hpU@Aq&&)i(yq32cL+IZbbsIIZDi@++k7;5&=JClE}b4UMX6-qgnW rDnZ=lv~g`DUTy-WG&H_4m#wCEIpAYN*DL<700000NkvXXu0mjf>%yyr literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_13_pointer.png b/src/assets/images/chat/chatbubbles/bubble_13_pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..65be4f232033e08ba2a912bb5830a50835d05d83 GIT binary patch literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRR!3HD+k8e2&qzpV=978H@B_|{#{McWAEuN<) z^SitO@1*nwp&ud*q6;;SEM;(3)MM4)`l#s;*1^PJd&T_*r_AK$2IP@p;Te!8Kz7j5BXsc$y*xs5&(PB&w2zq? z^vNsq%QLI!=OOx(W~+u#X*ODEj(P@H%Xx@-2BSZ7I)3w})-i%#NHCz)!7cnqgl(N|xDxx39 zxI8p$JcE+1hgvd2%R{uw@W=rK@(eXHK7j&Z)H5U3Ga!!)3(tT&0kVUZ9-)h8=;aZb zdxoAKp}A*ZnV!~pg>K1=o*tr00U)>a^6)9mkUcb7X^v#i(A-0`_6$aUMkl3q*mwr+ xAzEN~CEB}y`WW9h0IfVlbq|p`N;A}kngIBu)OI9?ObY-2002ovPDHLkV1m|oWk~=4 literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_15_pointer.png b/src/assets/images/chat/chatbubbles/bubble_15_pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..b4a69e1d03724598b4e63f46ee475c8771bcf868 GIT binary patch literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRR!3HD+k8e2&qzpV=978H@B_|{#{5Wq`Zp$Wi zyIRnv`HAEL7CY7iR}HR0%>w})-i%#NHCz)!7cnsO#F&5Gt!h>X)XU)M>gTe~DWM4f DHq9K> literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_16.png b/src/assets/images/chat/chatbubbles/bubble_16.png new file mode 100644 index 0000000000000000000000000000000000000000..ae0a23eb5d0cb11cc10a0af1eb2541890a755010 GIT binary patch literal 716 zcmV;-0yF)IP)>slBFTvp$#Eec7^HjR-Tm*LPp20nfHEn|xnAcY zcAb4*i2E7Kq%0-7c4;_1wAQ(Z1=*txavBz{8br{UB*Pb30F>;SZQp!p+cHU)eYPP_ z#y3L%v<4*jE^b|!0mvjVcfSCzO4@qGUjRPwDEue`sL}E4j$lCmt%2u0(K;4ll$}k6 zi%X#DmCqE8K29CHA5u=Xz%9%r$k{&T@u4>(J`5L^0Bm971ItO@ z=l8ZhP&lnl8Fnz$(>ipzNIBVZL@j_Vq)f`nmkgAhO~4k_?)SX2WRkQ`&Rr%)*hGE~ zu7Bn>q!+0kRahl$0W3H|t_G*b8jWV1cE9KTot)EX)=^}Q166m;eW2=MLGBAt?Vv~Fj;MPR)9C_;EXCKkh?P~{%9jlH)){RPMbH7bR@!fsCsnPRjL#BL<(#0*?cI9|CJa39p?O;E+u&(^{&yY( z%{vc*rp2rjUGK4|ncQvP5wlVh9l5FMJc-iTh{!WE-hJdt24?R1F8(ge9IP&?Df{l}^Uxgw yeEs!hW|NjlE{{N4bI%r~M=OXCApjB~QTfVhFfaMbB(F9kI8WHV_ tsux8UXtX5ea0Q4w3O>;Aq*jE1q4RXgr=;*7{6HfaJYD@<);T3K0RRIhFVz45 literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_17.png b/src/assets/images/chat/chatbubbles/bubble_17.png new file mode 100644 index 0000000000000000000000000000000000000000..51930255e8d9c2a90105793072468de7db6bff7e GIT binary patch literal 806 zcmV+>1KIqEP)sqXSx0D^S31m!R z?8VkNb#9n&)VJ9z=-0u47rsB**a%|)_hvM5U-xHTAMYN_FyR0JAUOD9Z7t|CfG$bMBCG>e`$B)DK z)7@R)vdElIr?!$7s_Un>qeYduuFAQG76PPJPJT}j>`M`74sP;l%RPHHofFQze7xdr zuC5xX-#W&QWj|E;5`g5PwWRKYX%McmUdW*S{N)fW{%=8rthThXBb?RX96! z2w=EzfDvwQ^1zG0>L|jMIoUR3VbWGeP8SM?K6CQ$wwM5naQs^cL5h$R!?a1b6kw== zoS+Fw2esf3j-eVWAQp_Rb+ZjPlaqJVk0o9KH7|9^rf_=GF+eK|*i?~86rH~gV$M3A zO#J-CB^O=c~K`P2`}?mu=@^DFqYN-9PlYbY5rjmR5wDsxh`y5|dQ_jU+mGA)h_R%$9W>+!_qw7lRg5M8KoWGREQq8_UT*GElsoki^;K8S@m68$=}r-EDfOIf*Ai zQpx4=43*=-C*%$l=-Aom8AerfP1yUQX2x=lZ8-~UTyk`JdH5f8$5mB#%*ZjA^`vc0 zyPNmUiz?<(r+TLqEna#jL(`p2?Ks~Y-Ml27#Y>l;Fr1%I-x2WTkj_$3HB;$?w_Ew9 st}{2B=P0QnnIxxJ_a$9}FPV`cR_aIaUcDF%praW)UHx3vIVCg!01iP`tN;K2 literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_18_pointer.png b/src/assets/images/chat/chatbubbles/bubble_18_pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..f36452570096924bd08dfba4df72e6224c0cee73 GIT binary patch literal 119 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRM!3HF?&tBaMr0hIh978H@B_|wU`7g=Q@L#>B z_y6PIAOH7HzyAOK>*@m%vz<0@p6GJODQV?!^^jPuB*2^ec#BZmM=Lqb!;B0XZSFl@ T-JElQCNX%r`njxgN@xNAsvaof literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_19.png b/src/assets/images/chat/chatbubbles/bubble_19.png new file mode 100644 index 0000000000000000000000000000000000000000..c0dfb4120d82925391dc392e7d580d48441fd57c GIT binary patch literal 688 zcmV;h0#E&kP)8nu5XXNEG&oEB2GY(>jc2bx0HK;Z)^h+ofya(|0=m6s#*qF@*v)QnZVkmXeOH(V`$1$@s9l}$ML$n?e!8#1>$>&8;A?D6+cRQ9@X4lMP|9sTc^05Q2 zCNMe(0DFy)s|c@Nc}@~U&zRvt$QT1ppr*=j;&9fcm>0#H8@;#QD?ZrUVW4^k%{mOx zLBPe;6;LLDIwVQlZ@1)!1IsKM)JuSP2|#(STr!&wsmIAW3|WY7N7CpWPvw_+G^nXE zUVUAz(=1JSP7=OcU)!~pJm-A1k~-DcbPQ<3kkpuU7&6fs6-5B3C}N^D>oBb7jSVg# zC0T5irZ#ogv6#Gg32RKP;YzWO){4B?cqGX8?7H9}ZkNdiUGyKR5P0{P^`c_9&ZD^ZD`;C=H%P5i+mi=6GZoMfNIw zeR$y8-5uY5n##s~;?u{{JJ6ElZjQ&wysM|H>kWYu5pbI9$`jlfD`(!NGX^yN#`+C# W!WowT+TcO}0000_FCyg68N?K2F9q4jkh*S(?HE38Pu$rl))k0K3+eMt2fj=Nf)Nz^hZ=eATp00i_ I>zopr04rS|IRF3v literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_2.png b/src/assets/images/chat/chatbubbles/bubble_2.png new file mode 100644 index 0000000000000000000000000000000000000000..dc33507bbabd7f4e1d9ee674d535fc21ef661c7e GIT binary patch literal 436 zcmV;l0ZaagP)?lxHA|@+u)7s+r&iE>6My_qVTE(;iB30LS0B;K7UXXp|UJ< zQ4lV?dw*6JVHS&}nh2HWxr@SBfLOsmcy3iyZZKCYVd##ppAaSl0u>cbehFitRv0Ayvf2KCU`}@cLzn8J1Res$*G(IY6rJi4rGuk@ zq0%&UtH&qDeHGVDQ91~n{4X(_w;EE zWC;V1vMdkJGkt5})G)Kz-62ePY-W*b3NTn)OMrkH{{vY6&|nhB3U2Hjm)ri*XbyB6 e>e%Phx$y3Z6x!)`r^%~g1Fib%91O&wNAJ61c5Iz)%8VF%!AT~D`d%{z!*F6yD zi2<-j7$}F?7{o(dqPbx3dKe&0b`{nM0i>HN7YX%L2p~Rys_=R^APqgfI)nq_;|~iq z*04ajk^X_4fQmd>0@Tw63!tJ$uoS2#Z)G4HLbb~2$)UB5L#sDX5pHahbaa}+K>TNT z!Po)0y0vmxwHhMYW6*ExN4;dOVNzZ6^m?xS2Z%GsS8cQuM5^%u)!+_Y892Xh00000 LNkvXXu0mjfh_0006HNkleNl21;g&?%+(G{Xy>H~qFtK+?oG%A>DEaeGpZrX6TyuMDmYj^Zb)z{3sN;PFMHaiI3gY1Zi0J_+&{ge#lq`7E=2p62>NDp-O%{2)SwOX*yd6u7 zx_W-AQTK@laHFB&9h}@ZiIOK?Gyr-{JWhau5d=R7JD|MV^*Xp+>Zfjx8Fn*Mput!| zluQWH;1I?dNJr($dntg42IR7G1aZx?9ZuU9mv?4jHcB#tSy?|4WlgLDku4A*TnB_1 z@|cIV(d!;e?k$CSdTDXvC>T52ifCI>UCVEbsu$X|6U6(W4*spdR1|l?0Y(ELt_;9W z70?!jby4t+LX;GKB2rrlEPdEu%I2EKN-zUe;E9!g9FQq4vryIxjw=Zj<+@#i!Zi2O zZ4JkuuVMx&h9V%Y&juwh2625hC@%k~v*>;?P3dD3MF)ncn5T3Vh0CNEU%nlbj48LE z9b3SbKe)EQGARhdab^mkw-!M$SQ_dML*UPTo;crSRVD`U+tW^8K|GZ)78&q Iol`;+0P&9>W&i*H literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_22.png b/src/assets/images/chat/chatbubbles/bubble_22.png new file mode 100644 index 0000000000000000000000000000000000000000..a77a733da9ba31cf2a56f899597fdebbd775c9a8 GIT binary patch literal 650 zcmV;50(Jd~P)NmL_GO6U;>xKJ#JaP|LGq;@2YmsZzzuoTp`t{|hTiLLq zS3m$KU|c8iG@I4(ama5Va2MklzYDR){`~?lXhu?Vk94%HG>I-2PzhWi^7WR#<&lFxCcr>2g}2e0mDTZ z48VZlvIy>5-`}(*9|3e{I}V_d4<$u_u$JVj#!Y$RVGCaYSb`~ljuW>@D!ojg*Te+`-o$u668+t@ksFOFJ?zO%z2I$)&_&dF1}b(k$ZG^d`i zU^5TR(YOzTa!f~FsAD_PkjEy3!yvk!g78NaR7dduOVF(d-H?H+K7p+4XaPV`wt~rj k5rXU!k}M8HPvIxe7pdX}6WVxd7ytkO07*qoM6N<$f}=|+q29rTOXGgRss^2!PN6U%CBg)fA1BHrynt>APH1 z$)m{ra}1V35m;97m|$9kBT`J}eatNw7tMpe(K#)^3~kK(s7csnU~~~!@hB>{Z&d|D zoEoYonAqdi08>qeby$aWScg>rD~~G-i(M7yHsp$6M!Kca2;O&`?+^7Xy*YzYKy6&^ ebs#hlZ@dBj4K0^#>o*|)0000}BG8YA+ zx$CcB+XmilcEr4HTVVrwS|6#;tpT%t-he(=E3$LM@G2EqChsSD&@tfi;W~ugU%uXV zKf`?|M}*+3Q09JnWDg6z9IWoDLo-z(0qp~aE`XT>cD8|sg?YCF3K{}GrnH3GOpgbL z8ngY z16`ZSgE5JjzH?QUR@`!XScPqy$=#ET)RWmsK+cc-fT4h=97U7Ik1r`^!G^Y;p?lRF a{=Wc}RcuTGPz2Th00007nRr@vh%C@JvYa7O5opx3S|dg_38_awy$qhNelF{r5}E){ CXB>wB literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_25.png b/src/assets/images/chat/chatbubbles/bubble_25.png new file mode 100644 index 0000000000000000000000000000000000000000..60dcaad550d550390c9826820ca5f228152e8887 GIT binary patch literal 280 zcmeAS@N?(olHy`uVBq!ia0vp^#y~8`!3HE7C#xL-QujSw978H@y`7QBcSwP!wOk?Z zq2LbgixtXWndAjxR|9Y{V>4-ztrKd*{C6<0u^|F?H!`L?O+-=e3rc17SR~bCH(Ixg( z`;dYM$H5pwx65AzGHMMr>|7e;m6mPUbJzlg*6=Rtl=poxyYhU<@5XO}Vc3bL)g zvE|2($G7sI_5Kxp#_4jH>7p|Vd&u=w- amcH>PZF9iPCmBGmGI+ZBxvX1|%O$WD@{VUY;(FAr-fh6B?SXPUZQ3(U93_ zMOoU9`a<5r|Nl=9H1wFG;`)E1*01ft%Q)5uoN)+b31L{2SX6M?L1I1=PwNhm1sX?| dGejx^Ex93|{m1(6w^*R544$rjF6*2UngHzNE3p6o literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_26.png b/src/assets/images/chat/chatbubbles/bubble_26.png new file mode 100644 index 0000000000000000000000000000000000000000..0b43dec579f989b8977501ff4d3fb33db664cb74 GIT binary patch literal 557 zcmV+|0@D47P)NklXEJ#i43_IteJZ)(pH}sHYh!JYx5wk^>SQ~Vwq7Fi!%4h9Uli$Z zx)S<9`E)l8F$)w1J>9I!^POQ38l$|~U#qUi!zPY8r7^T)opHp_Kl%a`$`S$M<$e~* zhs$vQ6Q^nz9f)+mI~`!lS#G|3XVX0o(R<3TBR=N z?|r~f)*W5h8fQvfh+P1Cn|Z*{o!E-jz?8c1-7|v~2V;1_(48chTo>&BEC{ZsH%x13 zR+thORDFUZ!IZdQLF{tu36czxtmR#~A=if<(^&Y@Y6sv$G%euVQjF vPq8xqK=43=FXN>=YT|)L*HP2II0XCvN!!F>HoWUJ00000NkvXXu0mjfJZ$}W literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_26_pointer.png b/src/assets/images/chat/chatbubbles/bubble_26_pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..d97093f43ef7fd1245ee3304bd1a776c6bb41709 GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRQ!3HGLSWET+sTfZe$B>F!$rGkd_~FlC+|Usm z%=7R1agTrh|L>CSNvsjz4gd1D#56*{<*>-19~x>0GbXSY$*<#B!tHk0$5rfj0824{ xmy!m9VSvcNBN7)FVifgQHMl-%I)rsFF~q0T_4n`p+yk_S!PC{xWt~$(696E{Ft`8! literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_27.png b/src/assets/images/chat/chatbubbles/bubble_27.png new file mode 100644 index 0000000000000000000000000000000000000000..57de9a9ceec777bfaa4e79d8d171b12d337a49ca GIT binary patch literal 632 zcmV-;0*C#HP)S@VkzVrKd)76fW%Qy8joulE9cf5)qF-}NQU$|vWD6;v4``fv&tB2_-t+{2-*)i zZ?$PfbvXnJ1YQ%=vE8($MFN;Z{@lEcmDdTAD(;&a=l&`4FczPLl!D^#1xg_E*wEO;q*{LEg^i{ZS%^tT}a6 z&4wBLE29AM^kpcJ99*}$2?1KF5oMZFm_fPO+%^!Q_q;&L%>yWpHBw-cLNQQR{03l3 z^4hA=Kr#SOoyo`7w@IMUM&8137B!fWjKE{QX04xNLjBT`LPhK7p$X|sS{X}W1t@>B zCNkN1bztxYf-`SHQ!uX2v5?uR%gYH5{iy~WHigrTX9}bVO?|ND3}Q0?q{igxQ8%F4 zwK+JWdb%Qua-+P2q%zkEHd&*{oc_4Rn}JhsRvk~#b*-;B{WR4(p8p~>c5G+_uYsFt zXOxB3z_kdij;#$n=Bt%z)3!BeRI4Qe8;Ip{Sv*etF4@%rm&TU*pN~6(h{hM`B};&A zu0CkuF^|#fK`=vD%OZj4OgybSL>6cqS7UFN3G6pUXO@geCxK C79C&! literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_28.png b/src/assets/images/chat/chatbubbles/bubble_28.png new file mode 100644 index 0000000000000000000000000000000000000000..3337b797ba1d578960c8ef1954a65980a9958fbd GIT binary patch literal 758 zcmVE@3hzA)9txK_Ej3sn(Y%(cG zRtF~ob*n?EPze+=TnjO2p2PLspZvRql0SZyd++5P@Au8`{eJIvPtc3(Gd7EeumLF} zCTzBYqn3_nHZY%k8*RAx)`6f4_o#E6I%c?258xlZn#~@)lBh?s{oadyZW;lX& zt|sWloqHUA$g{HZ{@R8Q;^_E9i4p)hAUhXr#EuydBCXtR4bL9YEf1BMvUTQ;i{OHew}1`o(j1s}(=)vV;>m0Q?xf&?&zH z3r+$ZxQrJND*#U1$n6+nPItH4(yJ^<&7jh0Q8uEaO7YRN+n8~K#SsGb>oQ+Y4-nZr z0GUjNvJrKKsOKyqxJ=9HUv89~c-UXtAoXwyk+qQ#Gaw}SPK2I4=YLB4{ajT0dF<2^ z%T5C5HfVosgH$fh-cQpV^LqwMhs`!JV!Gn}Oj?LS^LHAhII;$*Tpp@1p6>p}UlF)l zY_mO3(6oerFJ=@fcEF~)wDCMcP>^LJN^#~_(^C@yU7k_ft!zXo8&OY)0eG1zy%Voq@Bjb+07*qoM6N<$f}K!Qk^lez literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_28_pointer.png b/src/assets/images/chat/chatbubbles/bubble_28_pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..850b99e4cdd0afea8496aa778aad81d92d0ab176 GIT binary patch literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRR!3HD+k8e2&qzpV=978H@B_|{#{LsHv$Id+a z-Y>3Yj-Pk}nC3HWP!eg~A(EhRWGREIq8;lAu0RHcS(?@Y{~rYB0rfI?y85}Sb4q9e E0Bk}X;Q#;t literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_29.png b/src/assets/images/chat/chatbubbles/bubble_29.png new file mode 100644 index 0000000000000000000000000000000000000000..9eb5aecb27e30bf89f6976d0362f6e07136248de GIT binary patch literal 476 zcmV<20VDp2P){Myvujl*EHqr13O8@=}9}g75+oEe%XNSRn^r$!x!@OS80O;xN;#cC|Xqx8h zlcdmyiMD&>q*mR{ANu=xBb6u`z7$yK2AHX)WTF>|nhV%wP-K%_TxvPN08$`YPS zLRHKs(p&FbbC60M5zzgnj3S3Hd%2YBczOz2hdG5{(x*y)tqR9(^PDHv9>gaV5!OZo ST8Li&0000B~5+2h_;m>FVdQ&MBb@00(y* ANdN!< literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_2_31_pointer.png b/src/assets/images/chat/chatbubbles/bubble_2_31_pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..ad9db877d2d2273810fee85242f3d13020b09341 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRZ!3HF6%}!4MQk9-Ajv*3LbI)z$YH{FUxzK5^ z5HmSxHS@+#;%nZ&-oE+PH|$CdqyFVDb79-V&N;?jkr%G+ zS~P|A_{RH9QQ;*F0*NfQzx@lJykuseY2m#15Z{vWUDp2gE-&8Yto_^Il947NSo6K) duxQ+7{v~fh&ZyZ6bO9Z~;OXk;vd$@?2>^bAM8p69 literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_3.png b/src/assets/images/chat/chatbubbles/bubble_3.png new file mode 100644 index 0000000000000000000000000000000000000000..62988096471082bc942fccd97e7e154521e667a5 GIT binary patch literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^#y~8=!3HG5PaVAvq&9oHIEGZ*N=`Vy_m7{^N1#D; z-4ekvrbqAF{R3HrKZ)EpwD;5>lY-PNmf<0Je${;TDAP4 zfU!c&L8rbvxkcWL4*LAP*Nhj>TevtqA%FYb)g!A%GR&q<{JVvfX-*|boFyt=akR{040%KaR2}S literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_30.png b/src/assets/images/chat/chatbubbles/bubble_30.png new file mode 100644 index 0000000000000000000000000000000000000000..581fc7092383b74d88163b4b78e33fd44149d569 GIT binary patch literal 427 zcmV;c0aX5pP)7xV6os?*HM-NO572>uArHjJ-Zx-C-8!-`^8hS}rBYccRY-k=kRPLCbkM~(B%#+K zk+5`h9Vv?M$MLnXqsY@t4_Z{!_~LXJ1<~`%tD3SbvnmDQpvm${T}WWDTC0grNs<^Z z2nXK(e5i{s-@89G5h_hnnxR-b=))l-O}EhZ#kQTP zRWn@8_)9ZU*>__nYX-?lxrx?U$vUw#%X*OuKri;jRW#* Vl)PsvQPltd002ovPDHLkV1jXT!b$)D literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_30_pointer.png b/src/assets/images/chat/chatbubbles/bubble_30_pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..8660de9cd45074fe049ddbafea51560f98f53e27 GIT binary patch literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRZ!3HF6%}!4MQemDhjv*Ddk^>A4e#dY9`00Oc zy56D(x6j8l9d-P~^S_ZHR&f)P0E4zuSjUQjGLS$ sJehA_A0T>w!9%c1bO(cn;w}k>4_%!)PtTkb1=_&i>FVdQ&MBb@0PjgM$N&HU literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_32.png b/src/assets/images/chat/chatbubbles/bubble_32.png new file mode 100644 index 0000000000000000000000000000000000000000..598d8c8513310d3bd8b7bcc7b20f6d1d88dcde78 GIT binary patch literal 399 zcmV;A0dW3_P)H7IBada()*2_C%*RuP%NgUw`iHjrJ%fPu&4v$L5u zpZ#T-eAOdB@M;=fj=LnFzHj#`3PPmn^=gT0d?eq1H3Wrpi$hpWH;OFi$ z$tGf_LkF&|52?ub2-WlH#0wB9tFi+S@Mf z1up`4hS9!zNssp#tN4`xp-sk3DWp&zGLqg;kl`uRC(R^16Ebe0`;09%tL9#knx08y zLc=c%=49Lyll08e3BXEvhUhU z(=#EH3V$*~<{3b$i%~^}Zvps%aR|Oi#=8Ze$Z)AG^Q|J&+0VrAz6F`>^`Y-JW7iAF t(2rw7JqM8Hm6&xCnMD6nnz0=IZ&sk_7Z@~Zn002ovPDHLkV1n#8vIqbG literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_32_pointer.png b/src/assets/images/chat/chatbubbles/bubble_32_pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..a68ddfbe095a8612e48973355c343d966c9b9447 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRR!3HD+k8e2&5-9M9EM{Qf76M_$OLy!300o^q zT^vIyZY2j88vKsm`tj5M+;qJ~4{o22YdY%qiO1oCe5~TErvE(u|1Z{Z31^tbF8G3L oqUZt*r?8GS49hM;}y6Jju*Duek{8O*23U_MneKy~ydfcXW!8ga-iPr6|M0000aB^>EX>4U6ba`-PAVE-2F#rGvnd3@N%}XuHOjal;%1_J8 zN##-i17i~|6H60IqeKG(0}BHPFf=eQHUyGJK(;wlDA51~n3$WT0in5BvY9D}&jkQa zx)o>}E!d0z00C-AL_t(Ijg{2BO2beT!14b{ty>YXP*9gn(V?R+(5~*?i(7n$pl=|U zp-{T_1$1g(!l7i!qJq%Qg^C`Bc+*xnF{#B$2PKwr&thU45mpjC6yz z-0noB)KPOC0hrL?lOF&SC1mXc#RX01ASfUF+JUA+$f_3f_CcDxO;l1Ks~H_tS|B47 z5H4x(&JU7N7o{P&In_ovsz=(i@NzStoZ!*Qo;$wKw|h=+UwL|2GairGY&IlGvipsJ zs>QR38Ay81^!t5=!y&WT3=zT3nSqvU>$0we3}C(%vq_^Y+1jljSxk9oFc_qLRqb~^ zpOZvB0_X#0#C#Xl>6&BCY1Bq!pv7VV>^5@kYF^RIK|vv+I>eMhWB@)*Le^;tQ;<>h zXJsUhk%DbPRu7j@mVf84dec5^J5xBvhE07*qoM6N<$g2Ia9i~s-t literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_34_extra.png b/src/assets/images/chat/chatbubbles/bubble_34_extra.png new file mode 100644 index 0000000000000000000000000000000000000000..9a67674fce00d69741e1e7031faaddef61fb2cff GIT binary patch literal 310 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S1|*9D%+3HQmSQK*5Dp-y;YjHK^5d&QB1(c1 z%M}WW^3yVNQWZ)n3sMyv0|E>g7&vA=_VPdLt$WHROhe<8|LHTHPlFhklZEw!y@kCO zyD&2|b2D=om>3wCu}pGv*k&1`agl*_#*EoZ85i!1+%!|O<2);a|1QxCY26z=K&zTO zT^vI!df!etDcGRE3xhPe)qf|JEWaW9`t#|y%L|!B7G$c2 zoQbnl+bW#CKCU5T>#3l(|6g1WXt?HLl*6JGbDH(^5z~Wd-Vb*JUBlq%>gTe~DWM4f DYME^R literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_35.png b/src/assets/images/chat/chatbubbles/bubble_35.png new file mode 100644 index 0000000000000000000000000000000000000000..e4e7ea6535c74e7ad017aaea1a9d75b3ab17896d GIT binary patch literal 869 zcmV-r1DgDaP)`$6J?D4M?)l#D@BVi0bZ8S% z9P2g^&p29}hFvUB5JPwd=e@f}4(wA%8bh_}%hQI2^Vosrt|BGuS7v^aLJAp&2G zO%AvNLA;Ge0Pxt2v=2@J@bLMtYF;Y%hr%q~>08$tEke~+AxLI~Ds_A{Hpq!~+?_!H zMtfQ~?!3ilPYcg_d#Q3bWdFGH763k1a#8;NF#Dgp+Y%vyZc0GLgAguhGz zF#PHjT_cOM-7ZM~u2V#ha0Os))s_4b7aqHjvMV>a(E1oa3ducomiECZJa!}bB`&_c z=-d`}zD-U8NOv@y5tN;CrZfh?oW_^rs860Ql8R|_90pcX%xub&WApRB)4I9!qE~j@ zfoO^We0|X=%do&~%2V~*2d5b5XkdK&V>HzQ+fxVx`n3ivmICriTv$!nTr`<-(;fd%nD^g* zZrP7zBI2A8uEY|2bY1nSM9Uo^}T#=C!gCwPScIgkfhFy5_*v?;|ZCG3IZ@RS8QUNsgckb;Yy-=CMh!_<QHkAAx$an|5ie-cndOC*?c2>}7+XCQ_l&No?@i=x#R=8C z;M-4p`22&bb*FH5zeE{N6pLk#$mTK4DH|r_jel*1s34m9-l!%&f301G62+7(FRW01 v(v0E3)|Kaw*QyetI*4J-zjAgMaeMy@0)1*9_Y&f#00000NkvXXu0mjf0pXw& literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_35_pointer.png b/src/assets/images/chat/chatbubbles/bubble_35_pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..a8e8c32b3cbd81cb63f1ee2d88c844e52de043c7 GIT binary patch literal 126 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRR!3HD+k8e2&5-9M9EM{Qf76M_$OLy!300j*^ zT^vIyZY3uqB>Y&v_nQRUH>1&`=Y2gl8IVVYg=avX zh!mr(N9a-j(90tzd18p zJcHZ?7^07*qoM6N<$f*2ZWEC2ui literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_36_extra.png b/src/assets/images/chat/chatbubbles/bubble_36_extra.png new file mode 100644 index 0000000000000000000000000000000000000000..8e72fe4473385f4b1c57f61067dbbff079746133 GIT binary patch literal 239 zcmVi-k002ovPDHLkV1h^rVyplF literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_36_pointer.png b/src/assets/images/chat/chatbubbles/bubble_36_pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..caa9e3c073c3510bf9d1e6c6922878bc078d7ddb GIT binary patch literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRR!3HD+k8e2&qzpV=978H@B_|{#{LsH!!NELx z%^v<`j-Pk}nC3HWP!eg~A(EhRWGREIq8;lAu0RF`l}L-5ht@U60QE9>y85}Sb4q9e E09p(krvLx| literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_37.png b/src/assets/images/chat/chatbubbles/bubble_37.png new file mode 100644 index 0000000000000000000000000000000000000000..43e609e069808b308c1343069d7da426e0ee1525 GIT binary patch literal 376 zcmV-;0f+vHP)tmq+O1dy$3fm5=&mWIrv^NjpM^+k{vY;q`DNUELd_TcvWEI#Nh%=ndd~w(0#}9bQaIAw`kfHP#X8|Ew%O_FXaKn WJ|gYeFWUG30000(h5RHqw>?<52n)~bj>C@dI2U~1dw|#haZl}pXh5kRkzRg=D zaWeSJXO6Wt&8M9YA2Zf?S+zpdc#Dn8Zk~P_hbw3PRB*Kfp6`h{-(X{;W5fK3FVdQ&MBb@0M{{bYybcN literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_38_extra.png b/src/assets/images/chat/chatbubbles/bubble_38_extra.png new file mode 100644 index 0000000000000000000000000000000000000000..73cfcafb14687a81d3bea369138cf03398bedabd GIT binary patch literal 228 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S1|*9D%+3HQ&H|6fVg?3oArNM~bhqvgP;i>3 zi(^Q|tz-*@cgGuenGe>-NGz9&Y4drL@oRtK%^Dsaq2s9 X)4ly}b1z5)x{$%s)z4*}Q$iB}Ub9r% literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_38_pointer.png b/src/assets/images/chat/chatbubbles/bubble_38_pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..402e543ca69d975be80de75f43d6fe44fd0a7dfd GIT binary patch literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRR!3HD+k8e2&qzpV=978H@B_|{#{P_R$sVmQ) z$B%;zcqgSd2>lRg5M8KoWGREQq8_UT*GEllRg5M8KoWGREQq8_UT*GEl6ro(FDslh5nB_^Pei1kf*>FDi=?u2?lv&;=}(`#Ymo_pGRor8C)J3 zHl9IA*F!Ctq2(dkWq9NOqGsmDQy`3bX5@MXkxvEtwg&E}@%;=u!aC%R{5}%&2GR=oy-Oh}NFL=+8)ol?@xuz&%6@468)_ s_PwV%#ybu`D^F3|LnM#V47H&q0J+T?s)~cAk^lez07*qoM6N<$f}_f2xc~qF literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_4_pointer.png b/src/assets/images/chat/chatbubbles/bubble_4_pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..beb69192f4597350c940b0b6bb9f4ca288e89903 GIT binary patch literal 104 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRR!3HD+k8e2&r1U*q978H@B_|{#{E#nyX3sY7 zhk#S_6UhZEcB~1m8eD~%2Ld|08M~ZnxF(1$VqmECb9HZ?a0FxmgQu&X%Q~loCIBb4 B9F710 literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_5.png b/src/assets/images/chat/chatbubbles/bubble_5.png new file mode 100644 index 0000000000000000000000000000000000000000..fa33a77638e162f486fee5a7b86a78369d2fbab0 GIT binary patch literal 372 zcmV-)0gL{LP)Zi8ts2v=2~qWZ(s`oV{XTth#h7OE)%lUVu&+vXg-!65Y&D zC#@PNs^l{S^_wi~beCUuKF6onk3Fz|PN-uW7QoJ9k?1K_Ra@(Rp#OPjOR}*j*75#4AEF#LR0IW~?Eq|xT0IKY?Y6cR5 z%JOx=rMS2U0000~Gj=)Ea7_?h#K3S&+hg)Rmpk8qS{XcD{an^LB{Ts5 Dd-oqc literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_6.png b/src/assets/images/chat/chatbubbles/bubble_6.png new file mode 100644 index 0000000000000000000000000000000000000000..ad6a0f2e94cfa710cb3834199355687365ccdda5 GIT binary patch literal 402 zcmV;D0d4+?P)r}X{V1+Kq zfrIoa*0C;4k^3cLUh4ImLJ05WBWcn+i_v6b`u`KwPaPRRyW@yt54@1I{Y@>gPMasH zX$>`itle>#Tc~SEHi+-~?4BKIjT!YKC1nBB@_tbfDH@9n4!g&Y@2J}3;nPKpd{qF_ zI4O5Fza;8K0LALe3=WVGQrlN0m!fjptTGsp@m=3U8hM$(3+Y}zX}c^ZAu6Lp^3z5f zF&Wb$ASh!yHqEUi?k{eoQVby`lg0_QV*?ly68&bMKh2&WUkV{9RK5-*p4F$In9Ol7I*3tJIq>(t{b*8l(j07*qoM6N<$f@T)4gTe~DWM4f DB4QmO literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_7.png b/src/assets/images/chat/chatbubbles/bubble_7.png new file mode 100644 index 0000000000000000000000000000000000000000..8ced1de793b0861d31d192023cda8d8c535264c6 GIT binary patch literal 231 zcmVsp zq>?w(vZ41L5`pBLRa2pO5ESMP)D31=*R7S=p@(n{U;iM@=6^U42IjvQfpmcpgzSNY zP%tbA9K(dbF>BMKdxchJAp{zTasaSE2BG z`8b~e@1*nwp&ud*q6;;SEM;(3)MM4)`l#s;*1^QE__=+(P}793K)no}u6{1-oD!M< DO`jdm literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_8.png b/src/assets/images/chat/chatbubbles/bubble_8.png new file mode 100644 index 0000000000000000000000000000000000000000..2d370694a46d13c645bb7f7c89847f5f82f025e8 GIT binary patch literal 1206 zcmV;n1WEgeP)sTQo$P%|HSs;=%q?SNj!3;)%>_`ayW}5UK8Ft{0rgj5R4jF+-u9+aNU#%r4%%$9(z0+NAy=Bsu5gIp>$> zI9ll=`W45pQ5IDk(w7F(K1ULtBZ*1(v3T#Z`0+WC^jZA!xD%m+VA4KE61XTD z!qMNvXLG%gz@z(^>y7f569H5VCIccV@aR4OeU<_2lQzzu`2c`Fe(vVBu|Lx%JXFqC zfllXxz(oPzbIf9&w1H&+bG;F>V-|q#CbT>mwKC`aoe$Fwp$#Mt{fI53rMl&*Ft}ZL z9RNeyUa|TAw+8)PGLXpG?Bq>L07(e|$s1BAfql}3N%s~2HCOfo$dgek0I#^_m{L5% z>UEJAj|-#C;TW3tKBwhu&2xnTSl;}LL4TLvki#t9xuXW8u<|)(@i}Jk_=m7h+6q7b z0W#+v1rSg&h{~yc?u*Y?%h?(}vwRB~M~E=nzS9nj3LuceEMIoFjgG}r0wn!a8pRa_ zRKfa3+DVMZMKvq6iiR2NCw_K` z2H-Ai+;cKr6{q9oIVwMT%+*RC?AV#EinH#nL*-mxZCnGyij1D%Ruhhnu=35zVvHf!MC;qH0nl;t900-9tpdZct+IPYmR!-KB2Zkx)h&Xn zTWl^|V5WD8;OZ7Ly-V0a+Oq8WhpE6b09LOHTS!aijv8A?i~jOI0KEC{I{<7hTp)CJ z7=W4HC3O0B*4=d|^#K~Z8|7Sojzl%f{#PZaI?=AvOP{~^#G4th#Fsz44@h|I3|Gxt7Kr9kt!_ zyxMxT6dVAIMBIWS{{k-!(&_hEC5M-Mp`kJS`OG%6m4Sc*E;O=rBJRPCP{G>&1GpLT U?-Q;uMgRZ+07*qoM6N<$g3`uASO5S3 literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_8_pointer.png b/src/assets/images/chat/chatbubbles/bubble_8_pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..f786e7e1828fa6aa1b00e71b242b6d579c8342b8 GIT binary patch literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRR!3HD+k8e2&qzpV=978H@B_|{#{D^RxhFxRFUQg|N}VS`L!*z(J`R0&bFlp|W{rRPmy-WpobC_m z5WhDW`f~5M*YnYBKLLQ3k4U|Q0HNDF5mTUhZ~+zpp26kACBY&1r1;eo(Itt zl8mJ@;I-LjJFi;7yUog5TR|jd?*n%z?@te2@m|N#@UptAMtaZ7GXy;V|5<-*C9vGK zz)Lx|kkjLK?93j3YhbiBcuB9Gswg-B$C&yYz*D^WDCgD^c?x0)wFfhVV> zErIW3amoAK2R;X{j$p>h$D=`v?;Njr#WlEo#$C2n9s3W1M3&%L?0rCPwXvVs`j)l-eq p@UgTs`?9fC5%A>ub2mc;%P;tSVBrZLF8u%i002ovPDHLkV1iZB?2!Nf literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/chatbubbles/bubble_9_pointer.png b/src/assets/images/chat/chatbubbles/bubble_9_pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..9bc5919bc9be443ebb40c9e8de396111547e482b GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)L!3HEN&baUbsYp*3$B>F!$r(1Wf8<-t@2+D^ zmXL3ew3(Irzqz1t+PQi*Hnt5+GbCb!R6hO}WuC#7xI)5-L8Ws|10(OB|NB{+HhM35 umDV85d%0DIQ=B2$MEN4C;oC!M91PMkvwk0$apf)05(ZCKKbLh*2~7aBJuf5x literal 0 HcmV?d00001 diff --git a/src/assets/images/chat/styles-icon.png b/src/assets/images/chat/styles-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..74cd9ecda07ae47f809a664c9e240418e1d74d5a GIT binary patch literal 314 zcmV-A0mc4_P)kV|=VV8AbheR-L z@-oZ7K4yoHVdw)Iq6_+_3E42#!2IjDAi)SiIl&%qu?EJpc7I7f-Q2XJ=+HunV7)5#a2r~n0+t4fMvvYD2U za2ZEdqTq>0N{Txd`)&KO$>ecr0HlnAh^ffkR*V?n`T_G~Rj)N8;ed#_Bnud!1}#a- z%p5M`l0cLFuM%9K3Za-=M%?TPjYs9XE?S3n9Yf>z^afhwvUZ#c>eB;^(as{jB1 M07*qoM6N<$g67DBaR2}S literal 0 HcmV?d00001 diff --git a/src/assets/images/floorplaneditor/door-direction-0.png b/src/assets/images/floorplaneditor/door-direction-0.png new file mode 100644 index 0000000000000000000000000000000000000000..8c272a0a97a0d985431a751038632ad75d1602fe GIT binary patch literal 742 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0((hBK~!i%?T|Ze z+fWciwSjENdvKR7fVYs|Wr{2yND~)s)3|k!b!7p{jH0oh=N^7VihL9=Z~zG;XYQQC zigQft>6t7pv!@{<>(}3}C+L~6=Nyp};xhEEakCK_B#oeNjhl%aPm&(~+f}L$!ddU&@Q9e$T4&@$r%F8q}@g}k@9pT$+!gCL0yd1M#|Dr zbo`XiPU>c?CQ^=$pkwERI#5?*u}B#zO~-XXbB540i@FhctwiU6b60%2W(=X+6_6|A{SSRa!UAxU&<7D=5i6_+C~lSHQ`k<@W&aXIoaNp#jMk~+;T zE=PuuL}yJRsl&C!<;ZZ7=qxFcb}lV0N0yO9=b9pE$J)f@$a0eC%qfy)u3cP?lp%@E zlp<+HZQ^pI97%L85lP?FE-pvPl0;{SNP1^$;&P-sNp#vo5?dFSBgc?Lr%U9miOZ4W zNTQQP-nzIPIhG_kySVnq@g(ub^UE%-BO;?oAiZnsIY;D#Kze5EX$Z+G+0!!>h&vqq Y0T^NF8ouP(KmY&$07*qoM6N<$f}nd)!~g&Q literal 0 HcmV?d00001 diff --git a/src/assets/images/floorplaneditor/door-direction-1.png b/src/assets/images/floorplaneditor/door-direction-1.png new file mode 100644 index 0000000000000000000000000000000000000000..52e488f68b7ca09a5af96a5550bc83ae327df9df GIT binary patch literal 738 zcmV<80v-K{P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0(VJ7K~!i%?T|52 z<3JEaeSy6Yci3z*s<;9<;RbtvEfNSwNk|FyTpWPyHZ-*RKS>5#(#Y1YdR2C5rswzD zG@RGMnmtp+Rn~MwWZk`7Z=r9-n)isD5LcnI#@R+>kTins8fOz(o+MrUyV!xw&@AI* zBg>MMDy~9zXtr@Ok>yAN$?Ah<&|Ai0Bg@dzBft=4Hs0 z@%e`?)xoV2eMwdq`|O#|1lgJK_T&EX`Ss_o%Pn+?hPOoYBvC}(&FPw#2|qW#|FFPd zi6WyP$;#wi0-BS_;(}g*OB5NsNSc$o2XsF};yML7B+*%d$mlb+I43(ZcK&Q3iB8KR zqsN5ef{(~YlIXN3lDY&#=MEKDBZo<%(~`)c4)p!l;%ekFNpxBfNpI#Z!Pw$zWEe?w zS`q0`hpENY$Z(SA%qf!IoLgLtJVp|oDMiv7t%<9V$4R0yrbwFEy0{uCLlT{1iliA! z6IUbUNTPFyNV>ChaWztwBsyOaNoP(?T#b|`iB6wL;?%{}$TB3+84`JJ;%a0$lIT>C z=Ps^BmL-YKDy~1WJW2ff@U)5>h{)&?NN0^T?-4m6kiHpfIzqBCYxc|n#BDbJ0qsxk UxMV#=Pyhe`07*qoM6N<$f}g=q2LJ#7 literal 0 HcmV?d00001 diff --git a/src/assets/images/floorplaneditor/door-direction-2.png b/src/assets/images/floorplaneditor/door-direction-2.png new file mode 100644 index 0000000000000000000000000000000000000000..da1a1cb52a0f309307d4ccb9b335b0496ad0c88d GIT binary patch literal 750 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/floorplaneditor/door-direction-6.png b/src/assets/images/floorplaneditor/door-direction-6.png new file mode 100644 index 0000000000000000000000000000000000000000..fda613acc0bb4344cb77de60b9a7b7e649caa7e3 GIT binary patch literal 747 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0)RkJA9#QbasSM` zUBh`vY^h8Zm)X)0k@f5U_dWERvE@A?C&XpwTjOdYGDsRhZ;h*o98Z$&{@orx-%yva z+Q_jaC5y|@8|pSz6FH6~kgPtagJv0vjT}Qqlbj(?C(Sk%6Iq^)BpG9%8MKShY-CwF zijJQWnn}AE%|w=?Bk0&Up&e*f<6@CzXz61|4WaC+g?ear<7|=Pv}Da8r;vMVp)R_` zm@P7lmZIgI_tUV(Fo9Q%Gw%H0AI*D25;iO$p_ zTY_%t6X%r9jDvsnkVI!qBI$;K;)0LJk0jAqvPjx_thgNcm?Sz&5=lEwEiOkMlSHRw zk+jp?;&P;nBswjLq#e#JE=S5qqH{@+H1pEpa%31ubj~S~X0#?QM~0I`XHJpS*}AwK zS%xG!Q;MXHrHRXtSIi4gwJpb9m9f`>35=h?~TizpbLLmKSZ0QKeD%nz* d1&G`2UIAD*=^CG#rx5@E002ovPDHLkV1iyePc;Al literal 0 HcmV?d00001 diff --git a/src/assets/images/floorplaneditor/door-direction-7.png b/src/assets/images/floorplaneditor/door-direction-7.png new file mode 100644 index 0000000000000000000000000000000000000000..96fa8e483b9a17d70c89a2ea3ee67262033a1e95 GIT binary patch literal 698 zcmV;r0!96aP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0#8XqK~!i%?U1oi z!$1&4-2e^I0~r}Jv_MWsKmiO11f(RS1k^@$?-v4xxZQ0hH^k?1-Mt6Jr-)h2H z2bK&>6c<_25s~%seb+$Wj3t|hoFpznXN}oLWRNt1?i#a+lqX5IUzc0Z8JcBO8!1at zNE-dG4b3*HiIgJ=eUhBMGxU~GY@`g8&PiE4Uo^!3=k7T4woy!Ec`BKcki#jFt|UnsLY$L(!=YV`vqhGnrQ4-qIg+vuL*8mb zyBjBqJWfkenM686GWKDYH``Db<7kn`XenAgLifgi-ipvY>Sl}<8BR;kvN>qRA?o6&E9iNupDdNZN61aWQh4 zBsx^yaz6#mHkM(K)3^dShzhV&rj>=p0id z&78Wp7+HoSI%A5Y8KsGfk>yCDbBIW~Q@XeqS(YR^yNIMSwTX+7HG6G_xAE=J0b zM5j;W+{DF5Ig;o^k#iRpBV|dVvxv(_%9F&Ww~s|!PeewSKsswI*+k?df%MH-(h-u? gX34-5Aa1q#0oVTQcxWGecK`qY07*qoM6N<$f{}SLG5`Po literal 0 HcmV?d00001 diff --git a/src/assets/images/floorplaneditor/icon-door.png b/src/assets/images/floorplaneditor/icon-door.png new file mode 100644 index 0000000000000000000000000000000000000000..1b56bb2b56619b00671d9a8db92c13152961597b GIT binary patch literal 806 zcmV+>1KIqEP)aB^>EX>4U6ba`-PAVE-2F#rGvnd3@N%}XuHOjal;%1_J8 zN##-i17i~|6H60IqeKG(0}BHPFf=eQHUyGJK(;wlDA51~n3$WT0in5BvY9D}&jkQa zx)o>}E!d0z00MGJL_t(oh3%I=Xj4HP$G;~op~Rt3uuq?qKspE^kU(l(MXZxhaOe`n zf`W8#bLrT@ZVqX|!GJCfg@RK9rK>i96iPtBKoI*ULLo{Y!Oe5XyYt=U-o3lzIr+ZJ zyWhRv-TQF&yWjmDkSI~2ME|$QnB!?I6dI0k0O?{ei)?0Qj46P2=PS0G?NAm~=g6zF z@$in7|6Z$uY-R@QTf1Q_xTxxUe*d;L-1^oo_F5gQF}HFVeCz+#`X~XfOL1e@uV_iV`jN&02zT#OS+Y+sCbcV=n{|1P3sx<19^`P{lR*2J zFG(=wWXbR3nA8HM%Abb%qS6gLJOFWAt&fegfQ{8O0HCzyS>-|93AU9Mu(amc&E|vA z;*EjrUup8T#gb-L7Upnz*0V}Asyp`eyElTpy!0T_#|Y3=q1BA9#^WD?y}T6d6I3iW ziCoRF(@X(=v0yhg=cP_Y*aBwqlXzQS$RW_6%0-jwqx`r`W6 kwbi))=!p^~N;Hc80F`vvfW6oebpQYW07*qoM6N<$f-F>ep8x;= literal 0 HcmV?d00001 diff --git a/src/assets/images/floorplaneditor/icon-tile-down.png b/src/assets/images/floorplaneditor/icon-tile-down.png new file mode 100644 index 0000000000000000000000000000000000000000..352c48dffb33a7eafce5ade65e44fbb469a859d6 GIT binary patch literal 609 zcmV-n0-pVeP)aB^>EX>4U6ba`-PAVE-2F#rGvnd3@N%}XuHOjal;%1_J8 zN##-i17i~|6H60IqeKG(0}BHPFf=eQHUyGJK(;wlDA51~n3$WT0in5BvY9D}&jkQa zx)o>}E!d0z00FH@L_t(oh3(fdOT$1I2H-d1-l339=u($fLc6(}-PuiW@(*+ohv3pb zkV)t!bko`HZY`nhlBtkQaPSv=4kqcPNiLWB4ytf3lqQ$E?;b9>&;m9#Ha7n%lzxoG zF;s^DV7s0I00tKmWA(WO2mm(gDV($t5q^)!3{-buH7u=|Bh&UF1KM_g6jtbE2&&pq z4OTUZ32o}IfA}j_NEya}N~hUwrX`R#NfSuCPAHBO>kNjITFa>fh>zC@0C0Kjm5LUi zPT^~RAi(mbbe!Yt>&v2ai=Urv)S$vB*@|VbyN8!XN2^WJ8p?pR1u8*_2u_7k2c`>@ zLPabW#s%gcpbRwuV-FOP|9Z)w0*pO}5uh7{0D#ZOdjP<}`SF2%5@WYq;XC>Q0Cc=A zdgBq^&(7I%NMF=Vu)~Iq*Om3tf@<$#KZ)~`-Evjx&>N2m=enzT(wdegnk@iSaB^>EX>4U6ba`-PAVE-2F#rGvnd3@N%}XuHOjal;%1_J8 zN##-i17i~|6H60IqeKG(0}BHPFf=eQHUyGJK(;wlDA51~n3$WT0in5BvY9D}&jkQa zx)o>}E!d0z00CJ^L_t(oh3%F-PQySDM&BS>3eEu$U8HadT4*Rh(9)%$q;W?Jf`SIx zoB~Vfq9o^_-~dCBO|stg`Y%R8XkL?*cRcg7{$vLX!!QiPFe;E9dugJlhJ|hB?|0S- z1eWs*fTO`U5`Frs7eNJB%rnHPsKRoQffhso_{^u9_33EXct{t)QZWHU z+*($ms&G6QcNo!4uu^PIOQA5IQ6`%RHWKqG%UoSeDw9=$+we>AYD&z<&s^PJRVGt{ zx?E(QrdyWDDIkfx9@tB5+!z zXJ3M~q9SRrHo1$2?Z`^>;Q}a#t$XY6`7pZ4J>uH=fBx^5VHk#C{BwQ)UCESH8~f0n P00000NkvXXu0mjftU=#n literal 0 HcmV?d00001 diff --git a/src/assets/images/floorplaneditor/icon-tile-unset.png b/src/assets/images/floorplaneditor/icon-tile-unset.png new file mode 100644 index 0000000000000000000000000000000000000000..3f5e21817a80ef76a79f15eb063775a3c71bfc42 GIT binary patch literal 544 zcmV+*0^j|KP)aB^>EX>4U6ba`-PAVE-2F#rGvnd3@N%}XuHOjal;%1_J8 zN##-i17i~|6H60IqeKG(0}BHPFf=eQHUyGJK(;wlDA51~n3$WT0in5BvY9D}&jkQa zx)o>}E!d0z00C@CL_t(oh3%FxZi6rshJSZ$B@P0pYkG^$j8QI-iKQb8Q!aoqcFrxr z8YLVgQaZm57-}3p;{a8q>U|Ot{223L{J?-92!bF8{{hn5Ser+30Kn5EonhzHoJ%z4 zvOjjUqwUru!{t3{h8QmA5=wP|6d!72_CB_b%xk}VKD=eRtyiXRd+ zWr21M1Hk6vb0}#@aJlU?>sM!snqo%()X7*9T)rp-fT|*+s>r&9f^)HOiz3%KE?=Bq iTW$%0AP9o+w|oP&*#(U`6+X}a0000aB^>EX>4U6ba`-PAVE-2F#rGvnd3@N%}XuHOjal;%1_J8 zN##-i17i~|6H60IqeKG(0}BHPFf=eQHUyGJK(;wlDA51~n3$WT0in5BvY9D}&jkQa zx)o>}E!d0z00DPNL_t(oh3(hBPQx$|2Jr6?uTiN}7gC{i;{m#NX67BbFtX$wkl2{n z%gzR>nk=1=`XI-EZR{lY?=c|xlcK7L&-YWEI4V$6Q&aP=!s-wU3C_iv^9ca3pDo1Z zmICtTe8T7Z2LNEcUWp+kfDTSa3Rw!A#OX*NOMqUSPAM`MXdVcisU^0Pi@Z#xeTVk5 zh1pJq*a6DacHdHM_~8{@4Pf##(jF(u@!G4-H_uk#p8p zsjeH?@OuK(+otc@5y);50@>FYZk;`kT7gN6_a1zEIRF4|w%4Oc9Vj^;&IY7m=9H=< z`kCLCs3m@UxQ&BaRlZ+L!|v{%gN~$!BpF%&O9e(jnHe;p%z@H7cx literal 0 HcmV?d00001 diff --git a/src/assets/images/floorplaneditor/preview_tile.png b/src/assets/images/floorplaneditor/preview_tile.png new file mode 100644 index 0000000000000000000000000000000000000000..607f450134966c1af638374d5da5a42ddeeeadbb GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@RheNKY5XkcwN$2@zopr0NjHuCIA2c literal 0 HcmV?d00001 diff --git a/src/assets/images/floorplaneditor/selected_height_icon.png b/src/assets/images/floorplaneditor/selected_height_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f763fde5aa1e825a263cbdc303cf66fd666a780c GIT binary patch literal 175 zcmeAS@N?(olHy`uVBq!ia0vp^JU}eK!3HFi66di4Db50q$YKTtzQZ8Qcszea3Q#c7 z)5S5Q;?~{?2YC-Da4^ig&40P1+3s$`!kjA(7Zyx7_G1VC&Ta(}j|-J7E;^#N$q9U` zJY0CSrI>WrD{<=F?24)EbK2DR#C^}y8TICOxXb2nI%z-sW45QkmU9u0{+opN%vTmY VXpCB}y9sC+gQu&X%Q~loCICHTJ3{~f literal 0 HcmV?d00001 diff --git a/src/assets/images/friends/friends-spritesheet.png b/src/assets/images/friends/friends-spritesheet.png new file mode 100644 index 0000000000000000000000000000000000000000..aa72325dbd012f8af61c098fd6cb3f0dd0fcd1fc GIT binary patch literal 3494 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 U=O{OcDF6Tf07*qoM6N<$f)M<&NB{r; literal 0 HcmV?d00001 diff --git a/src/assets/images/friends/icon-accept.png b/src/assets/images/friends/icon-accept.png new file mode 100644 index 0000000000000000000000000000000000000000..da56941a0d624f6c76fae89e559ad24824207984 GIT binary patch literal 174 zcmeAS@N?(olHy`uVBq!ia0vp@K+MO%1|+}KPrC%9I14-?iy0WWg+Q3`(%rg0K*0o0 z7srr_TggBE|F>u6IPSv!SG^@M>!DQKz8VjK!zPO|JZ`LP5?0P`$v(84F;e;BEQbaa zUxS8VVIQGbW=CHG))!J7S%;P}uAg6!d0a|ZZ?xB_YE8$f~~_`5!cVlD~t3;zF~;rc0Fpjrmb0*}aI1_r*vAk26? ze??0#jxr!{#lz+A1LHJYD@<);T3K0RY{KI$!_* literal 0 HcmV?d00001 diff --git a/src/assets/images/friends/icon-bobba.png b/src/assets/images/friends/icon-bobba.png new file mode 100644 index 0000000000000000000000000000000000000000..eaea89573d4520b0e2f808d522f2653d64305969 GIT binary patch literal 169 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~i!3HGN^yhQ|sRBF!TQ4PYH5hQPCiw5( z{U$_iZPi8Qi=Dp~vj^$?oRzGkc3t67vukK>xPwiDYJ9H3>|;ur0kW(Ug&u74f3LaW zdmiHpZiWzUgFmxn8Efz7DLb@0{-Iv|kG12+ru*ItRx#K_PF~$pXPLtL>MZZA1-ESs TnliG1b~AXo`njxgN@xNA$niZ) literal 0 HcmV?d00001 diff --git a/src/assets/images/friends/icon-chat.png b/src/assets/images/friends/icon-chat.png new file mode 100644 index 0000000000000000000000000000000000000000..631b7bc268fac863d11e8332e45bb0331cd47cfa GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^fS;1Lc4Vf}1jF6+9%+|7FRXt$`9q0~)K!E2WxhMrGtIA6 vTTfmbvwv>n_sX4CKQr27%`y+_f6L!laH5pg?A1!3BN#kg{an^LB{Ts5z3)i! literal 0 HcmV?d00001 diff --git a/src/assets/images/friends/icon-deny.png b/src/assets/images/friends/icon-deny.png new file mode 100644 index 0000000000000000000000000000000000000000..c74e5c067c071acdd2203781c6eb45028f1088ef GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp@K+MO%1|+}KPrC%9I14-?iy0WWg+Q3`(%rg0K*4xV z7srr_TggBE|F>u6IPSv!SG^@M>!DQKz8VjK!zPO|cBHKA6;{q}$!79Az_2MP#cY}D z;uunKEBVKH2MMNw^(j3{?Y@R~XJ52E*wU-Syd+;iM9_p`w#o^H8Hz5F43`^tgL4?n zoG?AKu zvOBX2J=jvQ&_mTr(IN;v)XNGAB6v`2Q7`r)J$R5_JoKbq1nHsmW&T;DUFKpS$;->{ z`+dLfPrh8NFCE*z@7aAE$L+6GE6ePDkeyFYPO$6Z?OWH`?TM&*GU2$X1KGKY`|#=v z$L+o4H&)YC=M5XtK!ljOMC=9;Lv!4bxo!l}8cBgmntoW~|M>bF9{9M#zgci(Cn}Sc zU)_qy%GOc?ZLJ{-^K-9(BVC&j1SEx^8??j3?w0r-ug%ukw#0)TBwZ`<^O-}i>eNA* z#sm~ZLqLjN1g0e_hAwNyt3Z_%OOlI{q6xBM%Z6Pr!QkMTw-|f&a%FMgi#?V2R+>h( zBy~C+v7?DJZc2(}F%DHyRe>RdWHU@*R|u0sLxu`TQ0zykPeYI~LYJB0u{GSK@-;-s+|5owtubUj97VS|L}p`kO3 zMz+y<-0nq+kwn@gV6cP!RYj0ZLDm|oY%7MX>VmA>vfPI{tP?Oy;do$*eQ$FdsN>kR zFiByENUc)hnJCfsvF#GelTBg>1>#wPjtpIJ*@l2L=*kAtRa}HUUCh2h(R!A^%)Z~a zm?Fk8%8^|(O|{@+B$x&vg6!#v;9|m}b&FVcvE>;A57~~#KKpiH`(f5h6=sZ{Yie#$ zHw6fFPtX<35{e$t1k*JUhB9Vibsk`3do&Ip>l8nLO(I2MlL!B*DbqHM9g11bwXB~R zJBy;~C#-mzgD+%-oEfazKIpZb4N=zZB_3sE&h+-T{YPYPJe_^}PKz+o9g5$FCDcnh zFedX&mgzCtOM0CA1a9mse!SBEui^)(MJ*UM2^*ghpM@i3gQi~?>AyAWy?baV4dHCk zWy{fsWe=kvO+rQ;vk|Q2FTcme_qAGOzEOM%{r%vNW8}% z9|ZZyW9_4-uY7WF7T@`EX7BmiD>uN^iTBs?Cvq2+9|5#x4(Rz`<+iwcc2DlB=?nKR z?-Ds}`Xl%A>6_0iy)=9Mw=2ib=H|Pt=cDrT@y?ytuJ=^!r)z9w!j@!QT;u&e>_nX=OrnazD*?#@> GxqkpM;wUZv literal 0 HcmV?d00001 diff --git a/src/assets/images/friends/icon-friendbar-visit.png b/src/assets/images/friends/icon-friendbar-visit.png new file mode 100644 index 0000000000000000000000000000000000000000..fac57d9a67b21a01f5bf9cf9efb4f2cb72f014b3 GIT binary patch literal 2150 zcmcIleQXnD9KO+fK=zMKu6^%) zpZEDazu)`3zqXc@u1KEo#0&&Ml5JLt1OBGU&$J}?-TnBU4g^tHxT2!6g7W9e(dVsN zoxzGAndfMiE5(+bIRJpmHE?EW)(j!yXldE(aJ@3vced|z-`6|7MBnSIth}5y=d#k- zkL+$kGL$DjtwP%ye?F)l=(*6i-Z;FrXK3B=-vX7lI_~JkP3g(S@iYF79bBY7sr4J{g;gsfvgr3E!f%yZUE0~!ouWHH}*9HZ1 zQEXn>5#9tuya$}fo)TjhYM^k6;E#opC$HY53W3yxhjnR4R2#3bThEQ=VM2qtl5MuH4_%Sn&3? zMsRay(-*remRSvSR;kX=)LZvKT3W^Id(r5PJf(8|6y?cFVl*1N!d`|J@dAtHs0}!+(VI|Xu3BTzlR85- zswFkK1Zg5PI-Jx{q=CvYqOlJKqX~?Ma#%`YvEZ8-^GQ;OB8W&NqK@d)yx=7?xw()- zOK7z?MBrjwPy)4hP|O%-u&^R6a3P7~gQ(01+#-P%%6at|*ZBUGr2^Nf~1tCJC zCgqgIfp+_&p@BdgElR~<7$v6nfyAP-F2oWJR^)30nk^2qK`CQA7(>VRhH3==Xr>HJ zuzofGVImZ!iH8dLc!?K%{6q4HFUPY9QAGj^Bwldxynp! zrq~wN0wmUqX-TaHCyh9%b81OSW2EGs9C!eO>x>4ImNWsk5rEMwA=|S82k#E>Ph^!UkEcs%T8b! zEH=$Opum$$WsElBVsK!xaSU#GXKVe?Q`j#*maIu)*|NbSCHqDk?QL^c=Y8X7IDAT+-THm^mGoYz zeqDKWhWV!-#A?f8!=B3K%dPgTra$iA>caY^yYy3SCYA0PLXW;{X5v literal 0 HcmV?d00001 diff --git a/src/assets/images/friends/icon-heart.png b/src/assets/images/friends/icon-heart.png new file mode 100644 index 0000000000000000000000000000000000000000..5dd1015e96c2e0e20406db65649298b01fb76c0f GIT binary patch literal 201 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~i!3HGN^yhQ|sR^Dgjv*DdrcOG@+n~VXI`gQZ zMP-oyzsek$Lz9gyT9hrCltb+{ZftZq=#q#xy}p!CWwZ!9j>-L8zQ9Xa5|$l zyy4TVG^O{(FLoZAqZ-SoAJDU3=c>*YZjoJ0$v0Q2d;H+E2V+CI%iulfD9T@GFn zJ9C6QJa^`ZZE%@An^jlK(M#gz)WUn3oXk)5)Eo4QcnHPs0=kXC)78&qol`;+00&4& Ad;kCd literal 0 HcmV?d00001 diff --git a/src/assets/images/friends/icon-new-message.png b/src/assets/images/friends/icon-new-message.png new file mode 100644 index 0000000000000000000000000000000000000000..46d23f5a29640cf90cb3a5a5436ed9eda89d8be8 GIT binary patch literal 219 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh3?wzC-F*zC7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`0(2Ka=yT3A^82Lfxuo3cV%C51kI{0NkpI*mUGNU@g$`2~Yy|Nm!@@@94e z3UL;AL>2=rISj&#$MaXD00nJ4T^vIs!l(8*@-ZlI91ckMU%x52<;dc14wsKMv+Yh; z!>#w}-lZkGzf2Rnl(Ov0&mI4d?rdQ6xwmXmHnU9Z6Go?wV|Lqt<}i4=`njxgN@xNA D{W(nH literal 0 HcmV?d00001 diff --git a/src/assets/images/friends/icon-none.png b/src/assets/images/friends/icon-none.png new file mode 100644 index 0000000000000000000000000000000000000000..ececd9ee44c9653be780ff796ece6214d652e479 GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~i!3HGN^yhQ|Db50q$YKTtZXpn6ymYtj4^S}K z)5S5Q;#TsH|Nrfoc~+O?{q@&rd+B&lC){Le29I&d1nC0|Q`!^hz%PYfeXVxrZ rzWnU|gPgu(Esw_zU;e~z`O3IxDN|Ep+QDx?gBd(s{an^LB{Ts57UODL literal 0 HcmV?d00001 diff --git a/src/assets/images/friends/icon-profile-sm.png b/src/assets/images/friends/icon-profile-sm.png new file mode 100644 index 0000000000000000000000000000000000000000..60107e1e19380fdb9d104d71a27f97ceedcec6ea GIT binary patch literal 257 zcmeAS@N?(olHy`uVBq!ia0vp@K+Mg-3?wJ#d=CavjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCilo1AIbUfwWWiCX4b}_gu}Jo16dt|NrvkLxz$}>+^fhpFhqpx1Y%eq^3k+ z=_w$^TN30K3{(RL47M+n+JF+A1s;*b3=DjSL74G){)!Z!pqHnMV~9k!Z(lUu0R;}0 z?b|*@?EPQ4dsf6Ql>-L$35yo}UG?IE-#fLcDN=WNB4)%gZIXOWLz*d$m2*bjS5{b?!r-B+uQ2US3Sn z-dz_`oR{)aDvu#TJ5OFEEQ!U6gSz1Q-=t`WPY%ylg&jix9hbnU?J? zn9+O395^u6pH#<&RZ%0_AI0195+X352k^X+HC-tmA&R^bn)|OQ0xv?m;Rw;;8^j0Y zINoJB5a$B|sWMy`4~YRr;OL<62+q=sNYP=636eA;(SpQ>@bX0q1Go@y!uv(P{=7YSpkrtFO6S#HL8Ilf>bTG-%5+g_)Ptu%3(sGzD-X2}Ok^xs=F|fD4C6HYlJCJ|wWBQq+ac zM=f>451@a(RJfX@B93Z|B%TX19LKOED~1@72b@BR3L@u2v>Jwx5n$O%#J1DX(YFJ# zH?sPwG{hL6i(nhYtSAjt_Sg#>CCAbnwlr$t_50Y~7PDy1yP0H{)Q1Ee8kn`r|7 zS523dwH(>fP|SmVJtKBxw^w&j@(SfIWB@*0o@I5sSauRn{c4X8s-I@45#`tVO`^9t zoh8~_1|rfGl3#+kR?5o(2X>@Uq*qa1)UD*ZV02~it4sa=D!v?AH3Q5vMD3Fze0M0n z(UcNH{WoXDy&IZR1&%tMKdrV{w5T>|Xd-F{wczEogS%1tz7mUeB*QO%@G8^rDZQcF zZAb}1V@qv|d7|OPp$piCzRhd)kMB4(_x{!9+MerQFU~bTuzhNF`iZ}<8Apse$AAC$ z;;yAata0IunX8ktQ-`*^_QLjewwx+VIGyp?&+hR0&Yc5iwS~`zUq3!S|K-S;jryMZ z-(J2x0Z#}=4kzwCc^W&P=f7H;KJPXT)qV5K`atLSn-6bhzi^-0H@Bnax04&+{dL=9 z!{lDR{-y8sCcZs-y#41*coPEXK`DfeZ_=ks|C7Rmme!PH>pP9aIchi!1 z`Jr`Pzbx)M)pOvF$6J@bpFTFQ=cD~j@gp!kV%&ra@yj%_jTnX1ehmKc1}R`$2&Vm|ka!T55^XP{05H literal 0 HcmV?d00001 diff --git a/src/assets/images/friends/icon-warning.png b/src/assets/images/friends/icon-warning.png new file mode 100644 index 0000000000000000000000000000000000000000..3a3ffcf9efa03104936b6823bd7435cff4b4a610 GIT binary patch literal 225 zcmeAS@N?(olHy`uVBq!ia0vp^;y^6Q!3HGPJ}O-eq&N#aB8wRq_zr_GLB zeq{8kw;XW)eZ?&6#hu4Hmp!{Cm!HhG?puX)X7n1Ls;kD~hxUIs<=V=BrCNP;;4|i) Xl&z_ce_Xf+bRL7JtDnm{r-UW|QKMd} literal 0 HcmV?d00001 diff --git a/src/assets/images/friends/messenger_notification_icon.png b/src/assets/images/friends/messenger_notification_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..73f7e5eabaef31fc17d4dab783300e28b09d93bf GIT binary patch literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~%1|*NXY)uAI*`6+rAr-fh6BZc%@MrGHV$@~W zrI$AR7}vN>aw>aI)zvhq34a(g6oSLL(`1YY^C zzUz?FHkPvtm$dYfbQT_AY6>-;rtri6>#zCO#0&zYc%DQUSuiq8J*zZTT`2Mf&{_sh LS3j3^P6~)y}OaO!GOor@%?@i zo`wxM$y^uqnyp;tF;jkG$%~*8=WQ)rnVcf$g5mCD! zsd6Uw%ohjS6T6)kl%Du>{ChyV-88M9))^ZOH!TtQ$g-yI?|<(#stT>=ufZ^fSX*kAhak>iK zg$)%#&eZYFyzi+{dcuW=LvnMz^S0U}Ej&f-K0q=EOulM8v)CFazekR(#peWrqOg&G z5rkkKT6{sEdyfF!ZwPWfkU-%Z3m6#!<<9`+flM#~;}1>$t`OODHS_XPmfov@tDh^>v^wuqH3KrktPi4*GZ& VyPaJ09GK`CJYD@<);T3K0RS|c?-c+5 literal 0 HcmV?d00001 diff --git a/src/assets/images/gift/incognito.png b/src/assets/images/gift/incognito.png new file mode 100644 index 0000000000000000000000000000000000000000..304c30c253c0f8849f38538918c2f703476f1b57 GIT binary patch literal 1057 zcmV++1m63JP)3ZiR{oJKZ4@ zqqSxg*aQF^OE8Mx<*EC1Rd_h5oc`Pto1NFeheZ1Jzzad8xjx-^0ih?`pw6BDb>n{)!7*5ZAiEwdno??{OSYekJYk?6qKb1 z{i|MH2rylnthB+d zwZ*7o5~vQX;xV364HRYtf?x;?$^lI08P1W!G63Wi+K&&P7XgTbi$Zy|+F-@53Wk!C z0G8?X4gkT80kj|Q*Jr_durzL;f})YY=ot1%#puWAnzn_(lp0Wq6&p%)4JCuX00gxf z&{Yls#rkiMC5k} z>=hEd%Pl?oQpdX6l*v8`ssk~g#}0EAKQ&$>j{i*jbSF6}?(or~HiY>kRPs1%JsCt4Q4CRCONKyjaxsOY`s zZ){Hau`B{BvCL>FX#lj~0_)Aymdttd@A9atOra$Jf$6K#f!-e~ahpIi8I|DT-q%lLUL|Y&s^~e*rQE b?4Z{_#gM&@7i`=H00000NkvXXu0mjfW9Rn- literal 0 HcmV?d00001 diff --git a/src/assets/images/groups/creator_images.png b/src/assets/images/groups/creator_images.png new file mode 100644 index 0000000000000000000000000000000000000000..b39b0d7b83eddbaac0371d61c975412730c6c32b GIT binary patch literal 7966 zcmZXZcT`hL_xB-$ki-B1b0uOFLQO6Tp$e#wgkFtC5d|bv>0NBpgak#UOD_o>q=|@t zhzW``g&-gZiijv3ks?LPi}yayZ#`?hf6SRVd-j}JYrbpG?D?EKBugVfs5q2|hey!F zSpV!^-MUw@z@WWnPlIpIUIpS8I2-ifPaPIo>e74=J(SR#^S)^J9=``JvZ*xoeNT+Ma19 zz+QDceOyXq5qTQdmm|;E)9&v6$UN>VE&`Kb&}tvBtFsFX0!6J}SVI<Y5Wc1r!C8HuE27VRz#lH7o5ilxj!3o{{Ew;EN7J*Jk7m|H9NcT5xdjC5G(HNX0`uQ<~nnc2z!iw?j@;s=iqrSDwuwfomV_ z_2nax0>VJh+c7Fj5#b%H9SkuzSU)_~0mQZcQ!xFEt#dCx#0JaT54%j zL!9(@<1p3+IP;Wq<(8nLe2Ibt46%{~t9acqrcf&qG5081^~R^E?h~E@DgghbTU7@` zXF0Nu=P3xK-Qu{>TNs<|*FE>UL$^QQUvEqf-r738lYjE{@ApExe@oQyYXKA_7lydPv-FgE`IauPC)b{QOGKr?1Tor+@nS zDBHD5DQvrt=KA|i-1oMrV8lwD8vcYK?x=7UFv?#=bB9WpBUR+4BC{65~|2yRqy_$tr0X zv~=8*C=6)liqZCmGN^!X;LAv&z}u8xp#g(nXb@jIk%gg;Z=*@pXG;xia$ER;Z=x^V z13HSZ;wN3c{roM6sIABnOYnJA8=6e~83y66@@Bh_uyBjJAJh;U7yJn5$TVO~Wdw%B zL`09CYizQLsQxt`>Gkzo*bS$8-wGaDpZt7Z{MuK4^M~O0+f4zzDVFrDjv%b7{{7sz zzT&av0OB?1Xk6RD+kyO&O}h47xS%__yN&OB!TzAe-N(0fWxKlVGr>X|yPhOT9$US! ztCpD){`TIiLd%+zDIhc$s#>$OEzo%^^zX}C1pntljuvP;>Rqo32}hQxn(=nh&gjR^ zPSI(FWMFRU{zkJbnzrLWUhn4Ar`NJbAz;e`IW)W~eA%niTC+Cj&aX3+5hi<9M{-KJ zQ~RarOq=9b-9z-n{oJkZzKik_AyOHqJH^%nPSERK2qAO^{VJL=3qH5>5O@z?y?oM= zH7`M>GX@!IZ>REaJ^ zIPkp#YPC2gm7kgl6U2)&E%}_qaIw_xyMb#o4Z!%=W=I6aAfqKQ)@n;)iNuB(flSr*u3?>uDN`=v%Z5=HU^H;|!RAJ^=Nug>dlT#ic{?hKYWXuq?*; zjF3qG#m23e!b)a*pR(=JfT*I_&->b_(1nmn`%3X+r*3uMu1abSxL%6T_&(LxVBsI$ z^tqG%WNzSEP0UHMW#$a_Q0z+q%xp>{|Oe(#2flm(ml_{uaf=AIB8VYq@m z>n(1yn;vpU)txI=X}Wpv{Z(rwHzLhh{6-a%dHf79kDnj#vdt-*%9qer%q{&3F;-@V z`2AC}=seKa4l}XAsR$MdnbfC^Z$^<`0SYV@lJG;e#6W1e2K~U$^C%)89>hy!@kjic zM$RHwWQEtLJU@tHjY~po#w%>?;Kj8pNko2zW36kRw`f#t$I_+V=ZvKX}`XEC+da#`) zVNlqm$orO&detFz+EEqu%EzuSner}!yKefa5ZFgyX3#UkkwKguhFE09o&V9ii$1@U zW?l?Pc^!Q7a@lm>!h04am8&!0t@j8FC^D}}iPvlFGku_jTyNZX5wqQLN_<=UP?aNd zZ*qr+2dj&?KKsWI*|}ny;oV0LB~SOIeRW#>QzId*yj;YIa1Q zjCrBX7E3tI(T2!TZ|#qaC$&!_6>oS#qVMX5+#$+O(sy!Iy8>M{`|0F%%ewM?A&)5= zbwkl^`(wU*24LS~t7FHuMWLYl(@rlzP@7OLV{`pb5D-8h_{H0pKQ7`H-Rl~^IIGug z5@pd65wkI>$e`6th@L#!MS{bAgn#x zhX2i?ipY3El@@LJZV!O3Nf9<2ishQ1wlw=YeI8+1)poB(I&WBCEy`NqV8OAE^`5!` zpo}joxWoBvr<{)58ry;%GEY^z5IISivDN`+IP7Q(4jT45O-0kBx@`g(pNN<3(Jv0JWmTyIgepu zEscoE0gT8SDCy&{2Rywq`@C1}4qvI}mlD3K!h@Osg1e2D3Qa_zhhUGP21*%s$k9?Q zSU4!(AwwTeZ-H;_BX-(W&`l!Z->T2)EPvywnJ5(5@n5An8nPC(TGLcfcLNz3Hw9Qd zvWGk{3@W+Af?MK2ghBrS9UCY_p`qR9*W53n+KpClKvGg^14pD4|F+9ZxNei~Y z!PIp*HoP1wTPN{Xr24Ep3nZ0^N0Pu1Cm9~&Nl6ZPG|v*UtaMWTUt8JFR{NqWIMdv^ zZ)BC&xBP!`lYpj~lI;)tYU&L!ep62ru7Ig6AR34+d{4XEfkvRU2pArotmZHYU-+b7 zYdNdl_l%CuVEw=^-Y8TOU_aR>3cV>5m1l-Lhw)mao>s)b^Wt-Z-_7vn+L9#oSv?Yo zQgmV@od4vDVRs?7#wSAhWZpK(#S~V2RiHTT9zlex`ijaLKwZK!-oqYI+|;9gZL!q# zjv_C;?VU)Tu4B4Mk6l+5Zt1lORTDw1oPNI4sZclrx^+7zeCNh8s)oE7{$kU=w2^Zp zZ#Q%y;)c-1){Eagp8vR5<=z}TX>=Huk#=5u33)7b^g+*=i_`D-BVRMe@Wl~@Y*}>( zNLLPW*YBb(iG6k~F#|}GNQyG3FPFejuzn^MR8E}9xKvZ=YI`4ySrmO^R`HbgUr-x( zG!-59=r{K(1s=BWd9=**3b#wTMa6qJc zGsyQ8heD#X-7|F1e$5G9d{KFi!!)7M04H44pnZ=5)aD0z;{Mk%)R=dNJ^M3NY;ANP zi$vk(2S)Y7L_r9EWE{(@TjZ>&@xRK}Xc-y5jevg;4|%+3Bk29Icf|MbHNlsrM%A3i z-`+Sk(mK0ZD>{mAZG^&QE=D>83PGY3J`n}dYiG0jMG#`S&z=H6jf|APJ64R}!_e5X z7z9}4z_+v`9T{jD11KGUZplOSNmsb~5ge?Lmo<}_QwoSI9c84-1L(YSTmQe}9W8V6 zU|8d7GhQB87lnFTo&z`Y=iPK%&iT+obu%MrI%P7rzo|{K6gxQw)R7X#3 z92ZWTWINbi&^z72#>-$_d17AtUNKTRgsKb!HUjSeQUIy_GRUnonWHT(+jw5HNHY&n zSeUt}*q0hhKP|qNMS9PiCFurk zI;+s3S3%S-o@-lA&g|_L%=HCYt`JpEJNjV`1&672GR(&vRv@gmZtlgPn&y8DfdhZB zy4kps4L^MEiz2q89eFumI^Q>Oj|YF$xW|bgf+v*Y-)a#(KvC@jV|sKpTd{W%WJ4vU zw@E++0HP{o6I8x68;3m^BK}W?1QHG`f^!b_v6a2AC&Dj4q*9okZl#Ilan5hyiBkM} zyl{szvC|1ws`X5^!})U<7$oLHloU8tDf}1tTV`yf1Fhwol(M>>8q)Nw!@dv*Jw-(s zzP0J(U`%?$gW&DEd0x}4%OKw>(CrBwMCTp9_T&CJ)gfB#rtWtwKjh#5x9_ZMb9a^Q z0S55ZJ^UFlAx6vj_-C6Uf2B%_AQ1Hha-6X?nZNd8W4~=TbM@f*sytFd3!&`CBpN>S zQa9p9-(lUh9Xc{F8Q=Y-TpkI7&ZYHY%-c{`4N6qq zLK)3RbIoUvF?h{|%-WtWs6T5+QavXbptdeIrTQ_IGBi%Q&eIUbDH)jri=PkRd@+5x z_y~wl{|#GnCly2tw~(U3?9X`R>g~I%&UU$3&<|6@oXthMi<8XzC##8H0!3_Q3hVV| zRArv*{6=`MXxZ{Ee5}&^W4J10kQo{q&z5*L7lB?qm{_!AX1M3jBzZmSpI7K!x6o&t z=br1TcueIcs67LqT6Q3++DSj^;=#+&oQ}2A0DmW7dk3SP6?iE$egX7UsR0JJYU7H4PyFxa5;_cT$M%nH;%hE;arO#X-H z7Oi_7zJ9k=Nz9~uLlKk*l>Tz;5E==jQh6a)?;+|TF~gQ3pqC*Rl}>Jc@B02r^!C3* zf$L3#dLL{`Q+YIW%o_F@7?~?uY)rCtU>+X=@$swgZ4gW|9K1G;g)AibU}`g5*Ppg8 zFtzyYb0q6C-FW0MuDmha(^c>*TnZjztdYvjka~^vBERXy`t3#ua?0;>?i3t3LHAX| z>grs^n;~)Fwd_rkBV*S_-CzBFa!CpV9WhSY+Rs6%Wqou$^J#Ti?#UG5ss{)}FIm)_xTcH(%a7fNv5QpN!)C*sr{_!Per_J`5%0)gExi_NtvxW7CDk#TrP1*;u zsjoW{;&qcgeu-%*DB4Ty?s$TLQYXT_VX>mXWPWJV0w47VEiU(zMm)*-P#|*s5&pV( z$f$$Ipl`HiM8T!}Nz!5v^s8w2&Q+IzOdx9n5H15UVm{XcNuJsljTCP0M&QH^VE}Cko%+FC=_Tqr>6s)dmPAEWsW~F8Z zA-hNaw%jH7fQ^Dh=3pSzffgy7&EYq^9Vlb5AA0G4b`V6->q#vn9Op7Us@E-b38X8K zEGSfQg;3g;?i6^5bqWOfCGHpj&$qCW{BL#xG!kavq%DDTS#Kgz29E4UE}~Xc{JimQ z>aFSDyl2F{WglFsUB%ks*&!tcV(@>Gnd{GL!DZK`$wK^mgvdDeqy|N1JVg?C{h}vK z3jV-VLU{hb^w7Zs9W9ho@9{O0osOUm(5ntzTZQxTLwZr6c|gM+<`|k3MWeeX!F+`L z%pcg^Cp2Y$*OV?st{2@nI$m<_&C!c2gOP?WczMj9!!`fUeqEre*g~$hgg{Xy1dQKR z6ogY^zC=?qDl+iRLXIv3C#auJ7nsMa6A9e}b7;ju zwTPimxJsqcJFb^fmg682p*E0Nd`lH|$^U-t95^an-#UU2-eiB^qo#Qm`OWkO1p1-a z4_I~eT<1Vqpy0F383^b<$&K>7MhEt0{~w4@NOYLhqntV;RCSq={$%U`m<|G3$`wo& zClv2p;d%+8GYFQMjh$Y_JFO7+#B?jbpS)&ou-fLUP-s-1Eeg1t;e{_zJY%d)H_xnQ zYmi{G5F3jx*1x}aFE)7GPIIuH25V?um(Wg#A`0~sm3}*{#|jk>u<(DGp!3BMomriN zPoHcmAKZV*;*2(Zw73NJbF<){8vot!Hk*BykDX7PPAESJ2Mfl4Qaa>Xi4I+vPlz^( z60NO7TW3#8G>3~KdJa2y6MMpcbwp~cm5Qnvg*RmU17#E+{{v-+`h#!1b57Wgrv7*w z;Bf5LYIeC4P_O7LE8FYBOe=HUaQ0nO30AYUqzGA&&So1(*0RXc2$;a-R3V4i3-LZ9 zk52t?6Yi7W48P{}`8zCQb#*nM>(a%G%8vi#GG}ZJ*>LDqcoc&N+=am z%ifp0#Z5))*CBN84GiefTdcFgF;58Uoh-P{SHk&4=tgwmV%TZaXy*LTqkMpX_mB0V z?zvdI6I~+}FM_yUx<7lfzat4h2Y#TQ6d3`dJ{sZRk}&=q_VXxdnZ<7|hhQ?B_ZQER zSKNqWZY!lTh#L0$#t)hquPDaw+UapyYce;fW;R?cuL}rTxI)5ErsMt>&4Tx7fe4X4 zB-0N@Sj3Mjm_>mFy{wm4Kk)Ny!&G(=0ReX|3wFdd-{UdL#(nDb;At3;+jXB+_}jB~ z9})=|ZNuXb$)kQpn*To-%VhFgu*(~YYg4X}!Y^KH(i`&=e7s2+`wTuE8k2)3Y@8@Q zo!l1PkElCXRY%ufG82?Rz<|$y^ZE1lpN6^CC2RY`$<>+6tsTB^DX8&r;h5Rh6c*QZ z54c_|$gh>C7mn03rA4w0kwdamMv6(~70e#1Sl{>75Bc7T>_&d=^0RyuJt0E`@XkgH zhfnbpb;pz9G0YEGjUf>>_8zUNUUvvvyA6n;TWcD_s67XH7)Q64JmZ+mZsp?B@30dp ztImJOC8%J{W;Sio#E2Xm`^! zIFXi-`vETE^y$R!(11@GvF4Bd!#kWHY08-T<9jIPgcp8hBH$l9I)~^yjsa;IQ5{}q zQ;XaKSrvr6(1&CD;8SjGYs$JXww?XiEF?03$mH z6h)xn0S^0+jPaR)hi4iCzL^>{DNQs-KHPC+8FMskELmd1G8vgNhrB-8j|~LWsX++w zB)`rmcQOVMEp%Ar53N)?8WKLdv@Ls7{eY${Ttg5shA8HR9y#E`quCCDXT?pJCCMN( zkd7=i+fdUsb>r9YscZ3#Z;d$&@`SNgC# zA%Tgr*(o#iDq$g^mMl4wB*%C68r556Oi2Z?!3&7CIu{zp*2RKmJIT~C&Q`<9Oy}8q zS7?|9Qbs5iyYVrA2Jh6E*6b&+|6l^|D`^@?EJYLnf^7!y*glhFF-On{J5GGE+A+)l zBydy(5pdRqigqXKMN$?Wk3_vZ-lfWkuf?*ssC?_aYyBH53!IF%ASOgr=gCN6Qbw%{ o%UE8?y^ENTCF%ZfdY4z&GDzU%uYi+#KfHNN3@r64i4^Mp17q-w0{{R3 literal 0 HcmV?d00001 diff --git a/src/assets/images/groups/creator_tabs.png b/src/assets/images/groups/creator_tabs.png new file mode 100644 index 0000000000000000000000000000000000000000..e95b9f0cb0eae57a2178cd5b3efa87c58f61e30c GIT binary patch literal 1215 zcmeAS@N?(olHy`uVBq!ia0vp^tw6k#gAGU)mDzzLI14-?iy0XB4ude`@%$Aj3=AxV zo-U3d6}R5rHO#x>Aj0-wuTxaj5y4AAi=MD?a27I1NkuIY;pm*aq|rlUPQ$d>6WZ!d zu9=aN(|^3otmW&z{H67iGw$a6IVo5D>}T%0eO3FXmgU9G_PA+N`LXKvr%%gGgm{nf z9sPVhe#yiPg9QxCTv&)M4$uGI8c)pBm#6#57tK*vz`(?XPVn)${<>r|ufY3Heq3~> zx(e6wjs8uY>DHR(z$}u`5a`OtCJ_o_ES7H&+_5p{wX;E!*|gcW79KUOyW5%V>w6&Xwsl!#Hf#a4Q7l%UHLI$OZ-5LrF z8H*U0I{3I08eRZHq~l;#9*A{8+2_B(Q3tL@n=Xg{8*Bqsuw7Qm3P|8#leoI7*xy|Sx_iVhVE-_L1w5gtVm4Bx-yRuc6 z5Su{ZulBR6ejAtzt**bZMwwM$0B)Xqf7cU5e@~NJ6G*}geCqS zUU0#lhYL*k++48fpu5}0w@I5tH8=nK@{H~H*VYqyCl9|Xm{|AoZ*0ou+VX8Xt@|4k zfuZIh$|~{t?3K_faT*6A)Z5;cS{E1gEMiEuRrut$zv{MM=i_@o3HFXXm)Eb~rP(j^ z^1YBd_c0YN13q~dw+ZFHZl{-PTzYS*+9%c%2~^16^(pPtadH2vjDoH)$JfdeH=gR1Ihv~5zmqC)4gg;PmT;oR_5mSkuiEGL(d=!;9?-qE7 z;n5E69`S`I3>qxnF+D!&&ZW?||3JmX8B84#tj9L5w`=GKPOy1x2TUDYJ?if>m;~1! z*kSpd(d7)&mvEdUJ2kWs{b}3S=9>u2HkMN2xwK^#NULc_P anf+4U-PY;(L8`zKiNVv=&t;ucLK6Vt7V4P* literal 0 HcmV?d00001 diff --git a/src/assets/images/groups/icons/group_decorate_icon.png b/src/assets/images/groups/icons/group_decorate_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a395fbb7b095a1379f03fa047855f35df7293e4 GIT binary patch literal 311 zcmeAS@N?(olHy`uVBq!ia0vp^{2t3o15f)dLW3X1a6 zGILTDN-7Id6&wQs3>g?WWb8KQBKfpx}=*-IH0?u^_tQ?uhdD}(m@Vi`p&+~s~n}gjr(`b_1=KrRvAYM`S#bp zbP0l+XkK D3FK$` literal 0 HcmV?d00001 diff --git a/src/assets/images/groups/icons/group_favorite.png b/src/assets/images/groups/icons/group_favorite.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd734d12d6c5bbd6335c579f32e7dab3fac4ea4 GIT binary patch literal 198 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa&H|6fVg?3oArNM~bhqvgP_V+& z#WAGf*3?TEIU5XkST3x2(4MT}alAlyd8v} zlUY3Vpwo=Rg1pzpaUXcTvlgFF-I|}UzGp7iCxf8>GiBa4pHxbdT*~DpQO+@Y{i?td tpHz>u2-ypC-k)$JKCt=!0j1DaTa()76ZkFK$!8;-MT+O!CFrj z$B>F!y@3aL4=8Xvxp;8K!uw^)<%QGpF7ak8SkPfI^@-0Ond6-do^y};Z3xm?QoVR7 zo8aZI`eusO%Xxh&TsFFw8*jMqpkj8@w-x#;1RVRfFFYjB{NAraOtyWGUR(Lx?F;gZ yRBwBoj`aB^>EX>4U6ba`-PAVE-2F#rGvnd3@N%}XuHOjal;%1_J8 zN##-i17i~|6H60IqeKG(0}BHPFf=eQHUyGJK(;wlDA51~n3$WT0in5BvY9D}&jkQa zx)o>}E!d0z008+(L_t(Ijm46&4#FT1hQFHbK8gdbET(n$HH?pAd<@;&#-*zrd=uSG za6no>XlP^NpIk_Q`}w##_=n0us-K!EbyumTi;4bX2GbxzXCj4iDk03Jbm$&=GG#ue zgRz{tX|o)ls*myPrx?~{7rnbJ4*()FFIM|jpKS7U)ay%|og{VBt^qRt8%f5N5zw0f zoqtu7g;eT{ZbU53nC1v8fbX%NibvCjy&oB&c%i&H0aOI*D}sbQ z-G`$u{!T`Nm^%Mj%mha?aB^>EX>4U6ba`-PAVE-2F#rGvnd3@N%}XuHOjal;%1_J8 zN##-i17i~|6H60IqeKG(0}BHPFf=eQHUyGJK(;wlDA51~n3$WT0in5BvY9D}&jkQa zx)o>}E!d0z009e0L_t(Ijm49}ZG$ivMjsV3aA}oO7D!-%4pOo}n~oBa1qy>?0-P+6 zOQPgJmT(X7JH`ZLP^Eqn7h8V3?>E4IWc~S}{}iUR*1ph}k^Fdq(<5Rh@v$6h2vaR> zyQ(^STZU6^u_|`Zo7Ay@24`%2D%RT=8qn{CehelcrOPa>m;q8Z;` zsWdYQc@i5UbAo!lx9iEBZ)_VHpx7M``xeNVQ)z=Y+Z>>@En9}i!@dusnl*EAPAOyA p0`}dU6Ey(tBxd~2Kjn;negTP?;AQIgxq|=z002ovPDHLkV1hKJt!n@P literal 0 HcmV?d00001 diff --git a/src/assets/images/groups/icons/group_icon_big_owner.png b/src/assets/images/groups/icons/group_icon_big_owner.png new file mode 100644 index 0000000000000000000000000000000000000000..52b8361e5922b4346349dfb6e55dfd61bb100a50 GIT binary patch literal 460 zcmV;-0WaB^>EX>4U6ba`-PAVE-2F#rGvnd3@N%}XuHOjal;%1_J8 zN##-i17i~|6H60IqeKG(0}BHPFf=eQHUyGJK(;wlDA51~n3$WT0in5BvY9D}&jkQa zx)o>}E!d0z009_DL_t(Ijonf^4uU`seVV<6H7D4bhOqV?z#G_c4XGp^zym<92`A9P z+QKV9Pcjxb3;P32tej-SGH>47H?s@;O{g@cpNbLP>1$CRfyJmqP}F-+$c0MdUMvAo z3C86lZ%ncY8aFX%W23_?Amp+UL3be|wtZ=k8sQw~sh3MR`s&@_d2uVcU0zn+cTuij2~DO(5S5_)HuC1}%Wzu;kXKQ^X(dp1 z$g2q1=F#WtPR~u(&9=;r8qt4~;b9y3PaUEdw%Or{d=+U&+2QG}`^cqU?C3o=UcL(v zLDkLO_{~n7A0>}F-rZ&e*J>AnCSx&y%|GfPw8IxY`Y)3B<^Ko(0000j1DaTa()76ZkFK$!8;-MT+O!3s|o z$B>F!y%R2S9#G(MF?OH!R*ZGKM#-W&tJbGo%fo$FvIp|$pOoC}ka@prEl0w+@JKBN zmcNHsE?Vk1I3>53MwxRdI3LwK=U?l}diT{wkvZ;FHxEQREH4e;pXI#UFFL{e&cm=5 sk;k(RvTXnQWuo1a*)@M$rtD{oJR literal 0 HcmV?d00001 diff --git a/src/assets/images/groups/icons/group_icon_small_owner.png b/src/assets/images/groups/icons/group_icon_small_owner.png new file mode 100644 index 0000000000000000000000000000000000000000..7230ecc590749527ebcf0e647d7bac6b6b287536 GIT binary patch literal 215 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4uAB#T}@sR2@)1s;*b3=G^tAk28_ZrvZCV5g^x zV@SoVBX z7?*wA#m_v0E!*KyayOF;!-E4mlx{IT%gm8DESAz+VIaf6u*T`D{kiw?(m+Qsc)I$z JtaD0e0sz9@Ok@B6 literal 0 HcmV?d00001 diff --git a/src/assets/images/groups/icons/group_notfavorite.png b/src/assets/images/groups/icons/group_notfavorite.png new file mode 100644 index 0000000000000000000000000000000000000000..835d8eb7e4aaa03b8a858581fa5cef4dfca55243 GIT binary patch literal 175 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa&H|6fVg?3oArNM~bhqvgP%zQc z#WAGfR&v4uwhLFU&gMLHr0cNi`+IwT%ePDveJFH%0t@2|r5P4JJkG|Om?Rv`I3jtL zyOkV0k;F2$dyQlyPd39r6FG|l23NyP5(g)+YPuY&FnxH*XNuaQM}2ofObQqnTD*Vd Un$!dz0b0i3>FVdQ&MBb@0OD>qrT_o{ literal 0 HcmV?d00001 diff --git a/src/assets/images/groups/icons/grouptype_icon_0.png b/src/assets/images/groups/icons/grouptype_icon_0.png new file mode 100644 index 0000000000000000000000000000000000000000..9dc4191d5930a80ac818a4e5391f44c25b5b1020 GIT binary patch literal 542 zcmV+(0^$9MP)aB^>EX>4U6ba`-PAVE-2F#rGvnd3@N%}XuHOjal;%1_J8 zN##-i17i~|6H60IqeKG(0}BHPFf=eQHUyGJK(;wlDA51~n3$WT0in5BvY9D}&jkQa zx)o>}E!d0z00C-AL_t(IjfIjyO9D|8g})g~W>o}Y1?3_vNg7&+?IkcG692RU7H zO{W<5%QGz0?!;RDkH_nFEkEba4RUnJ{TXC!Pu8^1dUusFh9ey%3$HRyIi+ zI*+`IyxVQP)KgS|-Nkc3%8q`JI~nT0b@w`YFUwu3UNUXyw5<*RZL2dByC!l63c$(! zic_6=8*xlq$-qD7NjaT1_T%~FmTL#?B##fTlbVtn1;qF|I=qYp9V27CkyKddkcFn zc41~_=4R$FFflMNW0~aUu+1_=<01p=j2W|+GA`U1xoM_m$9Yx;|6QUP(z-W#fL5*a zba4!^=sg-_$#+D7M<(E&%iEGa9equG@;gi3`fohjv}0257FRc?2OA}h-8gXDfWg5f z)vu$YQmH^7@Z-J1me)N(SsOmN9AT2sd;ch!J)HBsErZU|uL6RXSwck5?c8?#D|2eW zhqZr=?j5}5T)VcuwCvECglCPL_ug72Y;k^b?7Q7TmCi1z$96<`YwFJFm{FJ3=6@&A toZYxQWVd3A+p{_EOW6O|o_hb7SKP)aB^>EX>4U6ba`-PAVE-2F#rGvnd3@N%}XuHOjal;%1_J8 zN##-i17i~|6H60IqeKG(0}BHPFf=eQHUyGJK(;wlDA51~n3$WT0in5BvY9D}&jkQa zx)o>}E!d0z006;BL_t(Ijg^zJ3BxcDMZYwJLEs?*u3csV^~nTTzyo-JOyKmIAf;+U zh6o5UM4Tc>Sf@ML{@s1Uvz8B?p5b90jNcz#|5Bo$j>38JL1 z83w@GREMr(A*wdn%yU+A5M4|a0vr$kG61ZtPUWOI^9Y0mh!E%MfUP@+apVD}?_~&i zSlX}R>R#*(!TX|_3qRZ)z<;&VBJF|DFhEv0La;hwlJ`Y81FRu6iJxf?g!|4vQhj2V TPA~^R00000NkvXXu0mjfrTmLT literal 0 HcmV?d00001 diff --git a/src/assets/images/groups/no-group-1.png b/src/assets/images/groups/no-group-1.png new file mode 100644 index 0000000000000000000000000000000000000000..caf0182de3d431cbed221d3ed663a458ca9f4d33 GIT binary patch literal 5007 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/groups/no-group-2.png b/src/assets/images/groups/no-group-2.png new file mode 100644 index 0000000000000000000000000000000000000000..797c946800de6e8f40e72da9a1beac732e6e0445 GIT binary patch literal 4401 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/groups/no-group-spritesheet.png b/src/assets/images/groups/no-group-spritesheet.png new file mode 100644 index 0000000000000000000000000000000000000000..3eed4b97d09288588d38528eec838cd259d5d529 GIT binary patch literal 29386 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 Z;?L|^6!j} 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 bnUa10rA77?wG|{O00000NkvXXu0mjf-iqfB literal 0 HcmV?d00001 diff --git a/src/assets/images/guide-tool/guide_tool_info_icon.png b/src/assets/images/guide-tool/guide_tool_info_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..32c4a3531d85f39e7dcc6eaa55cff6eda3aa1568 GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^;y^6H!3HD+Ytl-ARK2H*V@SoVw^JRt4k+-je4erA z?En7@mbQu=bSuw!vy%JXBOj#|Y0XoDwq@MRsk)HK*{F0uhN+H`mzuPg-fE_e+;I-~V k{Ys^S7d$>qxp+^uaJkH_!>z3>K&LQxy85}Sb4q9e0GcOBdH?_b literal 0 HcmV?d00001 diff --git a/src/assets/images/hc-center/benefits.png b/src/assets/images/hc-center/benefits.png new file mode 100644 index 0000000000000000000000000000000000000000..508ecc823cc7ce4e3a715392a6cd4c014787efd6 GIT binary patch literal 14475 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/hc-center/clock.png b/src/assets/images/hc-center/clock.png new file mode 100644 index 0000000000000000000000000000000000000000..4c37e957de4bc5aad6c1e967934451a71ab17c86 GIT binary patch literal 267 zcmV+m0rdWfP)&58pud$4f02g_0f4z|VP6tBFiWg_H3)xMsSo{f3L^vqIv*4P%fuKpm6jWfslY%BO1#Xf8`kVikppL%5-B&px5F0NoKsY7Iz<(%~YSu=BShmchlqYE!)0d!)ISx1pw&ui;|UO zjgfhRo{)+KjER?_n5d?!maUwlj-ZkQ2Db(Sum!fbx`(~E!o#_{!NkPBwaCoMoX5<* zt*Xz_$;QIU)V$f)(!kck+O-Ge#lYnU=j7?_?&{9G@AL4+^zZA>_wMW41N-gr$up-d z54(Ek2-*XvFwjAP{~8_y2$7*geHJTbEGTE}tCJgB>9Y~n>`aP6+G3S9B0g0AFlXT%yq&}6FgDSD-MyLI7vivEPY1C;$ zO(9U}^Z*?-Uw0ZfTOjR2wr)w9)$ziu)v1u;y3E>9(yJw!qlEfMXFdrFX01 zMU8zvW?aZ?0Vlcf?rhj~I<#Eqcg=ss1#B8mjphgn9;g(Ozc?#;4qT(FLNJgThJJ zSAYCDcHnpd4p`t&Pqeo~Q-4uK6K&JcchiL*J~QAY-c5KSg(Gsf7cnG0gkyWd@UxhX z!uV*%c@QEf5{q#iFwt^@>UE(QT zRb{rq#tdXOIpd~RiUi^(E#~wh49Okfl{r7kb*eh4N~)rTfVQ~Dk{EuMUs(Z<5 zvjN}w1qTH$JSnvWM*Npp2+s?jy#IBJ=f`}Zs}L6*>%!oeWR-U7Y7fgQ+PpIhqvCPdic=H^w4LoZ(E0;=^d8u=+eG-;e5TWl(!B3Kq>9Cmwj) zMiD+Z;CJb?IOBgCex~D;4k3Q`*`3mf`Q(&pD!J#MlWsZa7{6$Y>cC{(?2E6%KFaE@ z&)$0NTKE00)w0Bnd+xQ!4!n!MS916fV;~QP@+%*>{2!$}|A!sWM^8=l)MKAK^x04E z(3s#i!JPQjV_Lo<<74ly66e!!KKkva&%G8b)NlX&_=8|j{`~jvK_~zWpa2I*zyccZ NfCx;W0tpxa06QD@YUBU_ literal 0 HcmV?d00001 diff --git a/src/assets/images/hc-center/payday.png b/src/assets/images/hc-center/payday.png new file mode 100644 index 0000000000000000000000000000000000000000..c9bd80ba0406cd713215b03afd0ae1c19b70a051 GIT binary patch literal 721 zcmeAS@N?(olHy`uVBq!ia0vp^_kegB2OE&wm}9n$fq`j;r;B4q#hkZy4EruSNHhd` zn*;;~6m#jFlKiHyCFF{XqNzHEmrGdR$F?J7Uvh7J_xAUib#cd!DHGjK8SosI+tJ;< z`Tl=~Z7Mb#9Q;g&nj1bSC=3+ygZduLhX-wCch5Rwvae?Hhy9i%Uu(a=hH!4}DG1qf z^SS@~`S$*gPsdOhH#(l}}7|%@+~5o}R_Of4zNv@i_ncFll+yR(9d# zAN(%L?zh)9F0NIunNi%8oLh3JS$dDey+d1D*uy2-zsub3+IsAw>@{JzGZO!`dgIrv z*?Zc0t*Xt8&nEKs#g<*<KtTiE-(D+C1OSX$T_A38b^%WQmo z==Kl!X%E&4ld6*xTgX(~xcU(DyGO5nGU;rqc-4DYIojsyv4<0N=T$xaa6-&~Z(&7B b`1yRLqq?egzhAcklPZI!tDnm{r-UW|2B#3q literal 0 HcmV?d00001 diff --git a/src/assets/images/help/help_index.png b/src/assets/images/help/help_index.png new file mode 100644 index 0000000000000000000000000000000000000000..2844f41e11f70f4f07c486f4221b242c15aac4bb GIT binary patch literal 2569 zcmV+k3ikDhP)waCoe|^~H>Kcaijw@mPIPh@?`TBTiT>uA$K7oGm*3#wz z=*KCbBLQ^yM+5%`?CtICvZ+V4zQaFQZHLfrK6o(dwGRGB%{&17)7uY5z23ne419t+ z*8b6+K7Bf?>Eqv{b?^rQpWu$bheyxuEH(f5^$z|d;L{kOgFi6%u_;eYJ?!gY7tq0P z5aeJBxrzfj*uTTqhaT457IX-TaKEmaQ9TBpv+mb#1U%)Q5dxUm6sUPj*LDed2fsC+ z97Ws4zoix-y$AQ}5^yK7dOD{pbQ9{cLi8hx>X5d{1H5a_1}pJfZ;D zAj<->Mid3$bA=cQT|3q|vH)h#sqii9=NpXzZz;Um;5{O62w=zXoy|Q2Zz-@y_*$3E zB7i-^=bQUL_!fVDE8w*PI0yW~KHdwy6n^Qoa{|~ad~r6d7=8ixpI`nq+4%WCKl0i5 zS$M7p0eEHxcLSdlffhdU5IqQFZxs|6knUMqms z;70~6;V+<5rUh@gSuOBd;Bx|K1%55~cNa|kp!Y^SD%S$94W1K#wmeG9B9+I;pjDIq zzsl~dC3p;lOS9ko5eKHTY^vUt@lv^9ggf<)%r#3*b6?L$1_va z0=fmhb}|X)2POfO%pSJL++6_hz=yy~rYW}q-UFQyfERl0>05Hj)6%!WmLf7YEeCyY zE|ko{w=AmA+^0EN^d^-5Ew5|3u!$G>Z4sCt(vML>@P9+4?bl|2Fe8Zv^1 zCxz~u1m2Q16_uWh3u`ERAVO5jTtJ^xA9aa^R^cV z2R_%-k8H!hPc{C-Pf1@gViAJZ-D&mkqgvoK`Gc_RArH2;fA)18c&fRNScI^~Qs_0; z*LfC)%$j)t{8ZZ|@Da^@=^}(Pg7;($Ynpk;^#1*qWx(c*A}rw31)={~l8b&u@ZMB$ z98XEFQQ(m~$AfqG^$zej9BdWA9>GiZZU(RD+a>73{fq8e55J7|MyLb=&w|7~fcM;c zuA<vxtiXL8UjHC+q;0^wT(Fsu1Q)6dOmQ&YZC>*AsTN3E};GG}s6>E_B;3te05FVh8) z05tGn)1SU14DsR{!~>pI3nHV;3m)ai&B`T}A61qf=vMeM0p#GrY?WLg!PzEQ5N87q z%Rrip!M^)g)*kRy`05cqm9E4MV7Qc_mP?>VhyaoO=;@6y1N?4TOw*rrwRjT`G0bYVGC5o0pw}Kx^ zz)M{K9{jZc&pbb_33xnslbZBhfzQFm0w`(jy`U%$BfZuFJQwlQR21)sUXzJa3|<2t zdjU98v<7&bJw&-so^zUNJBqq-HGCNO$HJAsw}N+F#Ik6?Tk|!ba;*zj3!|aqYe?lm zJ^XRn0i*za6YyU6*0_HTI1YkPt|h*{luT*?&&AhsWs!E@g!}ML+rT)%ODS04;N9?* zv41fM5k?P%SwwEkLFF50p-Z7wO*y1(*5Q`xc%NNYZD%rZs(`ol`#AI~#r`Rqg*#EG zlj{Pg_rT9X0cZ?*EAZUvvCpGtQ|%sqroZ9N+OFEZ26*eJKNtHa{|9o=B*5A+0d-FD znwi+T20C=NM#yl>)$#AK=9hp6&@Zci_X?miEbFz>>sx@Q{vGeIonGUbddcl0YWuq2 zJ$}E%BG<>)xc(xQjd+STh7U*evtShSY~T{;ru6F;4q`6+3Inl?I`MjUU|5-Rak7vjjc?m`38@ zclE<3a?tVri7`Ob)303V*JlKu3LXYs((jai4Q;CJiY?l58#_gUXVb5{2p+&E&{WtJ z4wNTX3z`%? z`r{(GpJqexa}hoxh=LJ!hh11;McxCs9l8`o<}4PFugVYU72e$)|w3}4aY zckuf>7D1R^<2!s+Ki|RcyQ`4l=b`t}_?}%vD*jIS@N0)u`%x)A&_k-{^$dDQwY!Er f%GO1&Vc7ox%|VtsUkD)j00000NkvXXu0mjfTyEtL literal 0 HcmV?d00001 diff --git a/src/assets/images/icons/arrows.png b/src/assets/images/icons/arrows.png new file mode 100644 index 0000000000000000000000000000000000000000..47a833ce7ac60dea4318cfe4659d7876fec1db7d GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^fanMpx^{g z7srr_TgeqaKPfVEFdwX!kd}Qsnfdf#5z8-Y{`dPD+F7cY3b=OOewUN+=Futz1o_Bvo~F!n_MR`C%BSmPI_b=T zsfSiRWLUIGO|M&{l;Pr6ZaeQ6VaG3T-pkIgUipN5^?}w6K$kFhy85}Sb4q9e0EszC As{jB1 literal 0 HcmV?d00001 diff --git a/src/assets/images/icons/camera-small.png b/src/assets/images/icons/camera-small.png new file mode 100644 index 0000000000000000000000000000000000000000..1c0563eeb7250785f78d68eed59df0a606a43884 GIT binary patch literal 296 zcmeAS@N?(olHy`uVBq!ia0vp^fanMpx`Y} z7srr_TggApf0*Cc*!a@E?o3ZG-y-=F6<=NmdP=O|>^%ELe!b!eGbQN-nK_*19Lp77 zNL@U-VHb;Rvx>M*Y?Bh(qr*pRn4K?tVc=YUAYcc}CdRONj{kLSnGc$nH4C&k@;-bj z&cdzJqP6J@$DjM=x!3;RTWS2ze*;i#dn0VMaPS0RS zX1J-~(KF$J&4{_+Xc+UuoWirlCoaA^TiDwDlYVQe#Ra=yn3rDO;IKro? qDYjcGKy4921FxcS->P)aB^>EX>4U6ba`-PAVE-2F#rGvnd3@N%}XuHOjal;%1_J8 zN##-i17i~|6H60IqeKG(0}BHPFf=eQHUyGJK(;wlDA51~n3$WT0in5BvY9D}&jkQa zx)o>}E!d0z00D|gL_t(Ijh&OtYQjJe$A4RVfj}_$0TOQ>^x(x85X8q22?ZZQLLVX^ zQu+!#RfIy%z4Xu?;;E&Owj>1d2-`!lYd&mK`-8BP$?Sh-XEuT&l6u5%4(WNDT7}i> zvLd8jq~~pjq7e7-6TUv6)k#-HtAjgf(`;H5p@uS|ClrxHqk@r7j?>cd$_sbHCg zBptB?bW+>2jPmHm|YT%kYl4I*Bye1B5GVE_OC M07*qoM6N<$g4q4?u>b%7 literal 0 HcmV?d00001 diff --git a/src/assets/images/icons/close.png b/src/assets/images/icons/close.png new file mode 100644 index 0000000000000000000000000000000000000000..a723b3eac2c27b2fee149515c421619c3de11fe2 GIT binary patch literal 1163 zcmV;61a$j}P)q-fI4Nv=jka4qIH!l_dHX@P_hjw6G%f$+P2ZmLe*F4> zp7YTM$jUH`m#CdKswW>apip$6jata4zCzU6n8{*fD1W5Zl#E1>j6A^j$ftKA!h(UI% z6|Gx5sD#F^-#|*&MgPd9%V>C^QS@!O0T=uO87P|-9ow6+`Hk063Ax6`@atdGNNGBy z&@ehiMpuGyC|NC=J$SaxiS+$NR6@_y)`~sHX8&7}*EX}khNj`+KS5Me8&n2cx9x!C z>1UuSF|V#(X->&tHSK#)H+TxpBVMIV>|O7Od5)vqP*F)>w)T$v!nW4e;5^Y=$~4sM z-493C;Y@$`9woSvNQI*2c+YAXol9hgtCAcOEDxq8>R*Skj}B2&i8xsZt^FGj`MM~(hKv5 zp9&y-Z3bll6L8B*wCEtTt5>9R?3|NBP0rP1Lsb~|Jk(GC#=-(hmD24ZL2$Y$+)m25 zfqbGDD&zPFIW-FvDS`1O{L9U9j>9UAC#RPCKY~7W1*%d46Pr<36Q7SjyL@rYacN5Q z<)MJGQ#skN4zY8|ufO6qOmXWlJ0Bk!MEdqT)+-4tMbF7-B3X3erhRXjLa{9ME;x>P z5k2z-;^AiZm{N|} z8XC~leVp36(z%3*Gn?Alv6bFRd7i42z&r;JlUp)Wj9!th_Vr+KVgfSUZfqMKRfa1W ztjTNcR4f=o(ASS8f}8rV2Y1e&lg2#t@+$;)8f68`h0k+He)w|k{5$-Ka4h#dNI7-q z{BMxqUR83gD46TTts;D@7v!9pqVPSKoRDCH^#8#;tnHKzWqP|{H*TH%N!t9+?qgV- zoG_gu;7-cUaoFd7`59#a%Q^7>ci)QdiJM`J?|akqDlAmS(GPGp3l|GcWAadE(Rn$Q zeCMB`T+*Tu+UQ)-`%aB^>EX>4U6ba`-PAVE-2F#rGvnd3@N%}XuHOjal;%1_J8 zN##-i17i~|6H60IqeKG(0}BHPFf=eQHUyGJK(;wlDA51~n3$WT0in5BvY9D}&jkQa zx)o>}E!d0z009h1L_t(IjkT3M4#F@Dg0Ewj&cYq}vh%RsfBvc8h3lhCX z*G@e~7-%BLb($1`r~Eiy96u*fNHas)D&`z@0ASjtcAZVRkFHwj!Z|$(Qfw zq8cMY+tePg$TM$Fik?CZ2c#O=NCE8l-vFg`ljB!awHT_k_E_tbMXzV$+S^?j^|y2b zOhvK4)A8W?=gV~hrs4rm3PkY`kgFd81D{H8LWM-)<0*bp@jJWSAB)DVc{%0E+g#Cc zt+kl@u>ycGr_?4T#YC6`hD)l*GjCDX51xs{r{T&%7QtyiJyC=n6LOsIWU_u qTz#;r;T-Air~C%9zNvskf7Bbt`jlP>L(ee)0000&H|6fVg?3oArNM~bhqvgP;j=V zi(^Q|t)r6;@-`^&xZa#3dgtK98wZ+&3m%=j+r9Ey#H9;juE{x{k`~ylwBKRr9==&z zAS+(jiDN>L)x235TrFy5tllF33tFx28CgXnYCO0lvp%}YM6R{r$sE=zzXPj8E$e2A zuhUn4KWT&ET$lIUTh!NG%6r{zrx=#}5uIDXfYrZ(Vnp9otvurQ11q dS0npYM)wnXq8m%KWP#3P@O1TaS?83{1OQ*^RXzX! literal 0 HcmV?d00001 diff --git a/src/assets/images/icons/house-small.png b/src/assets/images/icons/house-small.png new file mode 100644 index 0000000000000000000000000000000000000000..e106a4560351a8123ecbe7b17caaca61f5b76562 GIT binary patch literal 361 zcmV-v0ha!WP)pG8%ab#RCwB*l1~o7KorJ%50urP_03t*v%M6j@s zXhi2_X+HZj)0tS<40t3`I`Xwi8yqf-H27P&1ajGlb+57&h;CN(~)7YwlwK z7`EK*U@#u(gg689pf74E>>3xqRtapfSwM8YSi6T^4|)ppygC$rhGq3I$83-+XGO<6 z=;|ekjrf@8j_)ZVCggc}Bk(i*DvY(#saI1SZM?A1lDDwH<@65j> zQbLYwowon>POc|T#*;XIo^6^CFB<3;?=jU^!0~0No%4$RiJbRd+Fc1>8Z!04q0NG^ ztD3kyU(K4F6=L=L&8k9=m~$Vq886A)WQbpT{an;()(3GxH{QLPzyC4YZinwb_J*)s Q0lJI9)78&qol`;+0BV3)7XSbN literal 0 HcmV?d00001 diff --git a/src/assets/images/icons/like-room.png b/src/assets/images/icons/like-room.png new file mode 100644 index 0000000000000000000000000000000000000000..1c13cd8512e1a04df173511aefc4ea102ebd39e4 GIT binary patch literal 481 zcmV<70UrK|P)aB^>EX>4U6ba`-PAVE-2F#rGvnd3@N%}XuHOjal;%1_J8 zN##-i17i~|6H60IqeKG(0}BHPFf=eQHUyGJK(;wlDA51~n3$WT0in5BvY9D}&jkQa zx)o>}E!d0z00AvYL_t(IjkT5CO~Wu0g+EtWfDuYusU4szK#(Y}Wd%Ay2VjKm&{ue9 zVFjM8A~k^#xN!d> zJRU0E-?ltI-aw@FB*U`Y1EBL^bZ?qr^%>T?^#XcQdce^ebnS+elle;4Hw^7=y|1yN%b3A$Ve9M2vP@1O7 z7=tkes0UlkmYcxQGaOHg-jrR3W`1!FM{m~d3a|ZZ?xYpO#pFVvW$ZPMP(Ey~FOM?7@|Nm#Weu@{UmVvXtBeIx*f$uN~ zGak=hkpdL-_jGX#(FpGCxlq$-qD7NjaT1_T%~FmTL#?B##fTlbVtn1;qF|I=qYp9V27CkyKddkcFn zc41~_=4R$FFflMNW0~aUu+1_=<01p=j2W|+GA`U1xoM_m$9Yx;|6QUP(z-W#fL8T+ zx;Tbd^lqK7QLsUQ$8~>RQg))}+|WCR6kkX-pS$HcM^#KtYlh37%Xb3zmg_SV@HxwO zFZ31qx#FalwUUqBqSx#kn~uJ_eMh8XPPm%ngkL8G#=G>*h QK$kIiy85}Sb4q9e03xJp%K!iX literal 0 HcmV?d00001 diff --git a/src/assets/images/icons/sign-exclamation.png b/src/assets/images/icons/sign-exclamation.png new file mode 100644 index 0000000000000000000000000000000000000000..7db61fb02baa72a4398f188e736e56ac28e57bbb GIT binary patch literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^>_9BY!3HAlZVLrcjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBufiR<}hF1enu+h`SF+?LcImMyje}ELrfk!?GY5|YV{$I`0 z{y*IKN}L$;XGa!~Pxc-UtPUHs#fGOD+wA^4@!;M*(KQUqgta9!JddT_4Hu9Td6V;t zcag+HtHgU1MgQbOSfqpk8U&px7z-1*xvoF?`sTv|W#KJ#0rJ?C>)zkkAeGCfrT=5Lx^J!3# z-Bjb?V_{*C%N-$TXW6o38H>xaU)&tLEDElClYNn{1`ISV`@iy0XB4uLSEsD@VqQ1GUwi(`m||J2DFc@G%~xaxBRUbuLW!^Gqx zpAXYXj~1Upe1@#N$`0=CjT3tQKiqt-Q~BWJHP*NP?~}c)sd2P*#^MV}o> zmsOtFPqesy?ehe_$k_S+Ez^%qKd#;B6t*YwOhm^D$|XIE_Qb5c4czPMM>{dr6| z$4Yq<)35p*)0sA2f4fhaD|ox7qeyo61rzsGw~Jr6{>V+4Yn{Dv)weyW{Q1+bYx;Hy zG;Y?sYH@VgHBIYny_L!!z0VXvbx*%2&}f*x!>iia4o}kL$9yv Tl%{!lvI6;>1s;*b3=DjSL74G){)!Z!V2r1WV~9rZ+)0642NZamZ_f?zx5}R}a}uxG zCKA0+HGE&&O;-H^t)x;__b8w}44$rjF6*2UngH9-Ki2>N literal 0 HcmV?d00001 diff --git a/src/assets/images/icons/sign-smile.png b/src/assets/images/icons/sign-smile.png new file mode 100644 index 0000000000000000000000000000000000000000..1a1721cd0ff64fb22f340ec9755104051ff93b98 GIT binary patch literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)N!3HEB$FfNSDaPU;cPEB*=VV?2IV|apzK#qG z8~eHcB(eheoCO|{#Xt>*L74G){)!Z!;8sr;#}JL+)CsYC2OUIO*XMOEsGaQbrs+qe z=trGLb0gTp9BgKH=3Zg53M)9Xb8aS&`p&=0if{c^XP9(icW(c$ITE|y9%GJJKg}iU z-16kON1;H5QBP$B!h8FMg>;Qe*K$hx;?5USHt&np72fx_#Nt ziE(K)N2)?DzJBrZ?VJ-&Yc7d23Z-2M`t^MK7Vq`Tw|Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0v1U`K~y+Tg_O@r z8$lGu->S41LD4`jZIq;F&RVKcZ<<_0+LKZ|RS$xaix-<)JVkFxP!QCdL;?z>l1ma1 z)F@I*>5oIy95l%>`4<}Z`!+AmW+(cA;mw=L`^@g_WQ=< zK_i(C7Gi_(gsM#9bKK`IYzwmRI(g1Xy$rDtvhSN;cWvLG**uI(@B0VCUayDBGIQT|tvq6aZ@_qEc`^k4qn-cB3`+i2meinUUVXxhrLrziwx zSNpXyhp~URX?{BFz50YKgc)b(b8vzuvl;uvfa3PH_G)J3s!Y8kY_|=h_#3%=9pvhC z(fmS)i~pSF=pu2+$LCx8kbkR_0qyk#sT2&24X{jowMH`VNn{1`ISV`@iy0XB4uLSEsD@VqQ1Gayi(`m||J=!rLWdN1T$>+Xn!JOxT3Oe9 z(^EImm@1*~Gx`rW_@-YmcNTu|nR99A72)D30c$Pvyw9<;HD*lzy#B?U$HwVQuRbi^ ztg+Abmqw#e*5kuvwP*6J=J(A%_g`LsIU`!+xx`=7zopr04MrxEdT%j literal 0 HcmV?d00001 diff --git a/src/assets/images/icons/small-room.png b/src/assets/images/icons/small-room.png new file mode 100644 index 0000000000000000000000000000000000000000..c8bbbccc0b9d5fb2912bfdbfdfc0eafb8e5482d1 GIT binary patch literal 413 zcmV;O0b>4%P)aB^>EX>4U6ba`-PAVE-2F#rGvnd3@N%}XuHOjal;%1_J8 zN##-i17i~|6H60IqeKG(0}BHPFf=eQHUyGJK(;wlDA51~n3$WT0in5BvY9D}&jkQa zx)o>}E!d0z008MpL_t(Ijg^zJ4godHgW=rl|B-u##d`X==W>fbp9+6a{?}&YvgF2qsfzAOUaJVc106XSNH{t*Q literal 0 HcmV?d00001 diff --git a/src/assets/images/icons/user.png b/src/assets/images/icons/user.png new file mode 100644 index 0000000000000000000000000000000000000000..c4155a2c232767fef9552f29f6cf1e20922db787 GIT binary patch literal 881 zcmV-%1CIQOP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!TiTL0%yHZsWLHH5nLnA?TH~0gBBGD2Ol@@Ea zto(eN3RI^FO0A zpHnKHjq~}unvH-=I@j^5S{E0@^|asEyrB)A=b@W<+MMt6h`?%Hxtv_zSl7P>;?LZMNOb|Caqc342zCg1{YW+;vj-aMXu{Y@tv8m5cvV@tevP6GAB|P zt-Qho66u9PK@zTxTkI}AUaW-cYLh!~v^9Ux;%YcoO` zEs_<p6k)*cALIjv2Ewx6@vqpaP;^YY~9|$?ja>e z?+Q^TI$tPiPs-2X%{CItmMntBdAu@}keHFnLz*^&K3?qUgso zlqkQ@QApwA&%1L-mE|>i=00000NkvXX Hu0mjfjA@BU literal 0 HcmV?d00001 diff --git a/src/assets/images/icons/zoom-less.png b/src/assets/images/icons/zoom-less.png new file mode 100644 index 0000000000000000000000000000000000000000..36423da8e9e577b191089f8199b12d2aca77646e GIT binary patch literal 252 zcmeAS@N?(olHy`uVBq!ia0vp@KrF_=1|+3_iKzf7&H|6fVg?3oArNM~bhqvgP;i~6 zi(^Q|t>lCQEdMtq?0Hb1G2#9H$H)5*8+P>jD@R=1Q~5a{hw-E1qZ=lRX5FggX`Gnj zevLz{<#xjwMo(oAxtl9Ff*rV=stpZ8lsDyS%=-6#PkSS0meMV?Nq<%Jlzz;ga-n%u zy2Hf|{do_h5*U^VJ`0g(yV~fxTiOmx#(4cCFjYNER1cwE}63gmVEmEdeWQ! x?WfaB^>EX>4U6ba`-PAVE-2F#rGvnd3@N%}XuHOjal;%1_J8 zN##-i17i~|6H60IqeKG(0}BHPFf=eQHUyGJK(;wlDA51~n3$WT0in5BvY9D}&jkQa zx)o>}E!d0z00AdSL_t(Ijm?t3N&`U<#=i@`z`?Dt5R0$4UASezwnRo{ItOR!z1dd#A>~X)+y>}E!o^z+7r%FVD(@m zow|bVmIDB;^(k(3A1_yD*{WISQ2Ybv?fnC)~Z!5>(R-dA0R R=d1t#002ovPDHLkV1m)8!`T1; literal 0 HcmV?d00001 diff --git a/src/assets/images/infostand/bot_background.png b/src/assets/images/infostand/bot_background.png new file mode 100644 index 0000000000000000000000000000000000000000..cd460bbbf42c0d5bc4663e9d472cccb8bdcc74a8 GIT binary patch literal 811 zcmV+`1JwM9P)*MPiXaH_(6^RbIGMVZ>+ROHg{fEEbE!f|d9C{qz5)bDXC!ve&Qv zd2}L(ah}G=UO&EpzrI(F^E5{GyJ19NSWRp|&z7iycVk^Q48tgbaig2JdOQlojn$4= z_>nN4Cj80$8OCN9>(wL*WR(Gp09or=m4L{=PyiwYqZ^1Ej7}huFuH)q!sq}Z4Wkms zO(55rfwO^}MynDqPVICKQd&gjHZp1wshgIj4Au7HrlLNrWC;cGbs+wlz{!MuTg6Zi zUCk=D_6q5xs0I`@v8d;x#`~;V#BH9D{hO6AsOm9EO-ewp4QRwOtaF*DiL3$96-1?1 zIkKx4nS!G`jHv@ejUY=kXpQttcgouH7zN`dV{Hi&s?_8ZNP9Cs+ki$=_}8~dRMox) zNIQ(~Z33C?3Y8m?xR0)_0)(4}ukuYWTH_2gHG!-ym3>xJfhp~#H~4qhD&G?9tUK3= z_K=2A0VBI>R$<6XV03H;$iuj;Y~2KOjEa|GSWUn%SjRF)8PF(@m9BLYkd~vIT}^S> zWnkWOI8PG^XxiW>%gxKnpt~5o4J5@We0EL0@ zGLklp(VxLe%}Z35@z~1fk#42c))FIh)jcGGrLKxtnHt?Rp^Sl%ITvzz(__>G)$569 zs;`b)V83vA^396d7K_CKkbl)340;)h^B({J002ovPDHLkV1f*GbVmRH literal 0 HcmV?d00001 diff --git a/src/assets/images/infostand/countown-timer.png b/src/assets/images/infostand/countown-timer.png new file mode 100644 index 0000000000000000000000000000000000000000..ebfe62911d9265e43efe5527abe3d7fa44633f0c GIT binary patch literal 219 zcmeAS@N?(olHy`uVBq!ia0vp^DnKm5!3-qrzQ4@^QjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`g#vs+T!FMD5Lj6T1qCT7DFL}WP4hvj*-C=^g8zd6!{+@<_5(#Y3p^r= z85sBugD~Uq{1qucK?hG4#}Etu2z1%?K#2uTS}h6O@RSq-aJtn1+PNn+?Q_`3Em zcSF|<&Za=uE1^f61bE6_*RX^#P8B%!Y|R=5)svi!+4Ej$0?lIZboFyt=akR{02b3g Ai2wiq literal 0 HcmV?d00001 diff --git a/src/assets/images/infostand/disk-creator.png b/src/assets/images/infostand/disk-creator.png new file mode 100644 index 0000000000000000000000000000000000000000..c4e95c9dd0e0e6ccbce503f80ff5b65d8c975dff GIT binary patch literal 189 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0y_7Ec$)kcwN$2@*OF?4{KFg3IST z`hR@$or4lb)Z9M*e;0A|f9yuC1gRyQZo-#%LRK(@{rI0^D`4(mBD_@a)tUdxr4G!9 zc-}OZ?Hb1x-jvsmBjpmp1)lnQ9H{tHZzJ8*c`0S@Gh>NnQzqUGjh{YdvS~MHwj`$5 n>7A93y1;udTbt3xl7Zot{2H_QZ`1b!-NWGN>gTe~DWM4fAVx-@ literal 0 HcmV?d00001 diff --git a/src/assets/images/infostand/disk-icon.png b/src/assets/images/infostand/disk-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9ee4ed83d0bc6b952e9ebfad35387592209da1fe GIT binary patch literal 240 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 Hu0mjfRv(4b literal 0 HcmV?d00001 diff --git a/src/assets/images/inventory/trading/unlocked-icon.png b/src/assets/images/inventory/trading/unlocked-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d6362c4524949693bed0d263b96fb4a47e816f1b GIT binary patch literal 332 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 PfIn9?^wld=@e%(IBrHKV literal 0 HcmV?d00001 diff --git a/src/assets/images/loading/connecting_duck_01.png b/src/assets/images/loading/connecting_duck_01.png new file mode 100644 index 0000000000000000000000000000000000000000..e3772c8b4e3910d9c52f7865d8058c3e5c86d41c GIT binary patch literal 3985 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 RxzL7}n4_)B;YvaP^}pt-O8Wo+ literal 0 HcmV?d00001 diff --git a/src/assets/images/loading/connecting_duck_03.png b/src/assets/images/loading/connecting_duck_03.png new file mode 100644 index 0000000000000000000000000000000000000000..15a2bb9f4b69d21f1dfb005e2c0bf785f9d16617 GIT binary patch literal 5842 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 Bc3=Pi literal 0 HcmV?d00001 diff --git a/src/assets/images/loading/connecting_duck_06.png b/src/assets/images/loading/connecting_duck_06.png new file mode 100644 index 0000000000000000000000000000000000000000..0e51a51d7bceb555184e53fdc51325904a2e5edc GIT binary patch literal 5290 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/loading/progress_habbos.gif b/src/assets/images/loading/progress_habbos.gif new file mode 100644 index 0000000000000000000000000000000000000000..2224994d32c823d84cadebc244e18259e884561a GIT binary patch literal 11805 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/modtool/key.gif b/src/assets/images/modtool/key.gif new file mode 100644 index 0000000000000000000000000000000000000000..578ee6507d3d681dd51216bf946ddf15ef1ed575 GIT binary patch literal 214 zcmV;{04e`RNk%w1VHW@w0CxZYA^8LW000F5EC2ui02crk0D=Gj0A@1)mSX_SnE>Un z75@PL|I9Q0|7HlA01F5lNN^y4g9Q^RECAqu!-D`8Dr{)LfWwCp2~xBuU_iwK5i>Tt z2=d^^kP{D7TnUn)MU)9gl6(kYK+AEak-;lKB?wNQfskAtH?&w;=E zIdvK{5}y7zHt~U&i9{549f!8llJGA{e1+aOw{Rb0*|w%_GN)egtFjw;k4tmR81B6= zkd!@WE!iXZDvS5mqnY0pUi4-=&Sa~7xX$R$F=^Sf+0tjOt-T=G-xa01O>5h}-TCEp eKi6+5V{-q(nRP&T@d}_j7(8A5T-G@yGywpMymChX literal 0 HcmV?d00001 diff --git a/src/assets/images/modtool/reports.png b/src/assets/images/modtool/reports.png new file mode 100644 index 0000000000000000000000000000000000000000..4731fed34a3c9aec2e0a6f073737e36138b8ccac GIT binary patch literal 3551 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 Z0RS~29jz9?DDnUR002ovPDHLkV1h+zz(N23 literal 0 HcmV?d00001 diff --git a/src/assets/images/modtool/room.gif b/src/assets/images/modtool/room.gif new file mode 100644 index 0000000000000000000000000000000000000000..94e77dd9e877f93611d8dee8e36d188f8b7a0cfd GIT binary patch literal 169 zcmZ?wbhEHb6krfw*v!E2|NsA+H*fxD`2XRw(#1_mHFvEOcB-_;>X;SsF<=9VKUp|A z7?>DzK$0Lc7+AsrPH3K6SP^BEGfny+@7<-cEDQQlG*=ijG33^sut+*%S}A?tL0{Ts z&PLX_*I^lEyeDra&YbMZ*x0Z%_-NwhNNyk1-%W?7Iy02<&31UT?Rj!xy+N@L3xhQP D`wBV) literal 0 HcmV?d00001 diff --git a/src/assets/images/modtool/room.png b/src/assets/images/modtool/room.png new file mode 100644 index 0000000000000000000000000000000000000000..2ce5efa4e580b5eadd99de481fa5eadc922f9a80 GIT binary patch literal 3102 zcmV+(4B_*MP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0003=NklQF=!v~v-G97H5UBtv@^B9d2MiEcxU!Kj%W+Vkq%tFLC) z_|7zkK!!qdNF*A~F_0)U$3*&~IR(-c%_)(#XwCs?isqbt=A}*8Zl;|Z==h13e@=S@|L#}3a?@JW}eS|#3#z31Qjj-1z#zn+1M&taPcU%&XHeyk zu@GQ5*vz5I5W&%K@K6hfv{8aX!=l5@{M<$k4GSI}>0)DL(g;vk;?&2XwoIi#;fcp2 zJ@cX|o|hk-VC9YxQea?Eb!V`SwQE=)%n;b-*2tx*bxEUTdBFTM7K1BGFRut#9wz$J zZ+|sT)r>16vZA_jyZK~AOr{P<mvbUEDgLj+h?OE{t>F$Zb=Y98Dh9A2jY<>Rbg5Bls zPt0a=|KVp>bMQR_Uqicw(B5C~p51P)zpt>b;?0}6n_Xwy@BY@pV4={^z`$d%pq7C} h!GVQQMWT^`_uKOWEleyH3z@%N{3;M6z{G0H0wA>v%vKg& zXMPp5N_y;J36Q>fNmPul#h_52&}g>B%v|;${a+~ucy43Gx6&2>W13hFi)ZBcMzd0!0Nqkz>VLB0nOgNf zS@BG*`k(B0rfIoNOgvMw{YP;;lNJBSwG<`sOji8U!nbru)uUh(3>g3bS^C6Ts=iT~ P00000NkvXXu0mjf#8I%7 literal 0 HcmV?d00001 diff --git a/src/assets/images/mysterybox/key_overlay.png b/src/assets/images/mysterybox/key_overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..8f8c2a5bed7d0d06872c2e34613899589a16abcf GIT binary patch literal 260 zcmV+f0sH=mP)HY@RK!QR(ACl#y%OXjCs>k(Wv|#qW8y#SP z+3V(TG=L7amF}8DGC0`4)&}$}lY41^4pxEouRDfk08InzvF;9bV2GH)L5z5uy>tg7 z;wZ-yab`(+#5jlbe7MFD@m37i%h=*{2YvRUAmA8H#BKi8^A=|o1ZNPz_!c#~2T=9Q z5pe}P*38sms))ZVYMK|!co*|Lui_SA0y~RXKQ@viNs=TH$6>u0ZYXe|37>7?3_7sPMkQgW57>A> z76qQ8Tvis%_x|tseMzjPe{GjXP}d6EY>A0~J3bz<*dSuQ%q)*}gE`}(tBc#d9SbZv z_V4WTiMNhi`mDM^>*SBhy|O%;U&XdrT=UZm{k+HKa$5TH%?1;lKNz)6v|PdQE^xc* zo};fh*Ol=kpB1;|ZB#mZ;QJ%LwJ)j~GF{d!^Qr3ERD1ZuD>IFN=Y~A(flR+V?_HU` zFmU;r=865w`rlvXV@s;l3I5)#^!WGIXEuC+bEntt*WSME+2x;q*{(~MFkCTxe@tG& zSLJ@0xi4#}>drI9_kH%PzNRnV*l*WxZ*I%I(lV~+CBN9`x)(l;Dz2M&I-@8jlH*X$ z-L)z4O?)=Kaj6+hvWs@g24t>lTPR`j>F{OsQ0>Qjv;Onxx0h=idf_ShV8!}uDe13& yJFBI-TKC!XF5-=Qaq4b^e#vu*eJ>=>e`enxD!^x~!@~v)YX(nOKbLh*2~7ZPWa%0J literal 0 HcmV?d00001 diff --git a/src/assets/images/mysterybox/mystery_box_key.png b/src/assets/images/mysterybox/mystery_box_key.png new file mode 100644 index 0000000000000000000000000000000000000000..79b43deee659e915eed87ae86ce3c6acb712c490 GIT binary patch literal 487 zcmeAS@N?(olHy`uVBq!ia0vp^>LAR)3?z5gE@TH%jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCf`V@Ck7R(*OVbJA3wQVPWC2Wy^kk|M~Ly%LNM-96We%_wL>6)~(yRbt_Pf zb^G!6K#I2{$S?Rm5@4`>q0|PH;4JWnEM{QfI}E~%$MaXD04+E0ba4!^@bA4GIq9$h z4@*GDWW~PP|DXNJxi_v-yDWdNLN$@=(q2`?uN&2WOq%GMr}vcIY2}smvxTiyM5lLe z&hmHSUc&q0mFLf|hXWnAKJ!(UTE8)iC&6#&l=j0HD@~tOu8?{7s>-zOL*3#z2j8Wo zeW+b7v~#nRtEhAN8-o(nrpi*)mKkT?m>iUSB(#I~QEWkJ;OnD_Z898D>wg=zrQV3+ z3e#%&VPmz)pXK1OuZOE&epvNML|S$h)3*KF=IC#Hw&A+P&QnpZqt;rQoL%$$OjP{z z&98Fu?p;|EWnH|SwXjWkm9UwaReFSuoZ`HD$6i##{koHLCL_Jz7pqTxij?Ck<)+Ct ze-8yDb?M%|W5V_Qzx|Az?|u4m;#zI3PR6gkc(wSWx$*b4nuh|{y{HMecIUf3`#$x# Um)HD@1_mgDr>mdKI;Vst0L75q*8l(j literal 0 HcmV?d00001 diff --git a/src/assets/images/mysterytrophy/frank_mystery_trophy.png b/src/assets/images/mysterytrophy/frank_mystery_trophy.png new file mode 100644 index 0000000000000000000000000000000000000000..67bfeba771e24e4245a29e41e3a2e0b6eecc5c3e GIT binary patch literal 1653 zcmV-*28#KKP)Px#1ZP1_K>z@;j|==^1poj5fly3TMF0Q*_Q{j~=d`eTS+ILqy_tR2x1Q;%Z-Z4j z>8fwMg;0@aIaoI%D;^o3b5q@)b39)FQ-c5`kJ|GwhN>NgbjDTusTslBNCMqgdUtn5TN_Tf^Y;<-2003l9 z{e}Pl04sD-PE-H?|NsC0|NsC0|NsC0|NsC0|NsC0|NsC0|NsC0|NsC0|NsC0|NsC0 z03*q#&;S4c32;bRa{vGi!vFvd!vV){sAK>D1x86kK~z{rm6nTQ+BgtJC6Hoa#u zU5sHllX38Im0zYYAuVGy1M%VFR&`2g34%fdSaO?|(}=;tRrQ(x-n$fm6E`WR=`217 zXQ!(~Z6I?lFjHnAih~m8y~J081sLU_d0e)h zu#|VoAS|kt+W7|}9v*huVnElt}Z-gB0ataItX9zr; zSp`N*L5R-96nx_BAFkiwFUP=)c#jhJewLY~0LD2Ev8N!{Tvj?m>wALeQ(CUlGA5NJ{<$6O#5W5hvz8 zKYVfb_k}<>o5bQys|pL0qbY+@;;ISm?kvH5{rv3<2!-$6x={x*h!UbKDq5+yv$D?G z*RS8d2_e|tu`e(Wv??k{4B|tkN_T^&RYTe z_~D+P9;E}INIdgeX@FqQU93K&KKcceicBuH3GWPzy=r)5dOJDczXJ68T4jiCM`*tk+Mn%h|ML! zuYX^D9{?W&7`AJ2Sf#9PESd$9Eu{c~H$V#x;K(MILt{CSW13V;4fRTnXWs;LH&tFk+9-96Fy~j|d84Oi?06n#wDcTnLJ|bQ}>h5SrWi zAp;@aMvxG>)^^Ws#Waglp%yX1tSb64Fo_uZ%_i*K`+Eyyx7%!#QL(qT?R>)C z?Zi+Xn?IXBudkcU+Z#wD{Jd%(gl;EBw>3T5_FY%o-L#T&Fan6eXm#$|_pPyRs6yj8 zFckY%;q5%+3d9;jy7{qH>Kx%AuTl2hRBB%*C ziW(g+=IsR_Q!2glSyL{<>j_f`Qd$WEg0!w7O46vW5#*14hG4J)QA+J~0$wV-D5+8) z(3Ay3ysoBlkZGNw5DCJ(P*++ck^sIc1WJA|C`115B%mRnA9T+gyRM}E3j@zWFagMa zXKGxJF9c&q24!tJD2@*Cj)!u>JmjQWF=5Q_O*W00000NkvXXu0mjfcBS(| literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/icons/info.png b/src/assets/images/navigator/icons/info.png new file mode 100644 index 0000000000000000000000000000000000000000..b32d14d9e83a5da12570c44f26073045b0c19135 GIT binary patch literal 256 zcmV+b0ssDqP)bOdq`Z>$L@^!AudELF>kLDn4 zKqe-3uNm0uY~{Q_ENtJvfDb#j0H}=#a>z`{=mAYuCVk^A?hZT}jx#J>DX{nmN*tJa zhuuEQEA+@K3kJf#!d-}3k3203eA&WTWEmmAK#nvZFPbCfNqL?Mw05<1AIJd!+fZs? z)DS41x8MKlSQ<7aSsncdWC2UI0is{dgP5+7s8s$Ir}sA#glM-wukU^U00002>834t)1fP(R!E{-7;w~{&jo^R-Rzz{3rJ2ByXG>22L*CHzmhp_fH3@a9G zZxR(?3FK1wpWCL?5v21wh)HEBMjl6>z{e1un7IM7nXFK%KWsU?Vx9e h)q=!=NsSc@4CmFRq~;%T&;we<;OXk;vd$@?2>>tiJCy(c literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/icons/room_invisible.png b/src/assets/images/navigator/icons/room_invisible.png new file mode 100644 index 0000000000000000000000000000000000000000..976fe8b41e044364f798fea9c9a06051ef084964 GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp@KrFz)1|-ie{%Q%NN<3X0Ln>}1Cmdk;zcFFYga0#) z)BhFVdQ&MBb@08jNof&c&j literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/icons/room_locked.png b/src/assets/images/navigator/icons/room_locked.png new file mode 100644 index 0000000000000000000000000000000000000000..f46843c890b3b9be1b7ee732a68437564ef44e3c GIT binary patch literal 221 zcmeAS@N?(olHy`uVBq!ia0vp@KrFz)1|-ie{%Q%NI14-?iy0XB4ude`@%$AjK*4@b z7srr_TfLLIc@HRXxXj&Fyo0THM_aZ9yQnC0c+Qm=j`{6gK6C!O>kgbR#ger)XZjD0 zwgzie2H7PH^ZE+E^S$lZ^f7e)j8|`_6sLU+m548RukwB0m9}~J4xNg7JE8l2!g8?; zv6XkWOgmb`vGv-Vy-i-4%mUS{nG3!C2ux}b70gY{bFde-`p*~lVC|BBtTj7s==yjm Rt_3=c!PC{xWt~$(699PlRXhLy literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/icons/room_password.png b/src/assets/images/navigator/icons/room_password.png new file mode 100644 index 0000000000000000000000000000000000000000..9fa392f8e0d8f8b26de41e8c21221679afd32a31 GIT binary patch literal 188 zcmeAS@N?(olHy`uVBq!ia0vp@KrFz)1|-ie{%Q%Nnmt_{Ln>}9oz%^HK!L;M=1I$# z!3%Z~VBCF*YfFRr`&i%Ov)lGC zvw!omOc&CZDrW5Te>Bxeo@GkVqNr?X+kK8QMI*=L=#&RJ{kwLhGMho*WolpK@*0MHodYg^o+&u!|F6W>;q zyUOynK;f@%^Be%E82?QoAn!dJ08pBFLLd(xdiVwUJ@@eQ=P`glc>JIFxqCi!1AyRV zq$M0`xzD18UpoUEAtK)y`B^ZN^H_kBBAL>J#CR#_4H3LWOU&jycXf0~IEM=n#3?C} z$;{^B)QOafWZS$SQ(}q{iKCYrVa49_?fBh^o2CWzy=ok?agwZ?oH|Qi&Rijqx&q97 z_gla>IxK zyLY_IM1^Sp52G7G8}LvCswW>M>HsilfXUscUl~}G0Hh79-SvUGW}q8GPt^dBQv=fG zh*(j8Bn)sI=I0Lv-sA#Ix~JBlANNu8dt$dro;(2fr&9~nKJ~WXy+y4WJ~xG!0Dm`f-?ly&2{lhV)b3rIpo8#RW=0YJMj zN~}(c^Q?zWyqol1&z+SXsw+puH(-JOUNAkF(lzqlGe^-ryqtsVTF8OYA zkGKqfTFG9kFSKg@bibX*3wlJ_qbNwUhRGS7c$X1G&WSMo6mr(w*bCkseG-Nw0iF>A zlR2MJnu5NJMIw~PN}uag?b#p7@sD*|(^Q86+32gdc0HMQQ4Qv5U#XiQE#*oaZ*)IW zO}Pfjn6vBEw~@p0Ap^ zYLKJuQ8jT}!nw<2eGmyllukE=B1sUK$BA1@A7%_QUDPkr%;$iyY{PPT{j8gPr2uQz@07C*qR=DNu9|rZGpfe0Jx--5O{QKe+ z`SSW4{23$5uHFq~%J0N^?hnUqBe(mu<+eGuc~9*qkOiIeWQu zxqenKF|^n#=Co1DOv6l}OinQ=Xhmgdknw3>N40rm2d15v9;C zR6nuw>ZMp)&NWwCl3Ha&kupTiP|mv10{6~cgKR4&;NpFtDaD>hSvD>ragFIjbW@bG zRNlBn#_h3pWma|;tV+Miyh`t|LlJdUhAi#SX|uVEqSYEQ6n-qM2T#S^LGD>DS+15* zma&+qshp!EE4@oNi`ukT#J*1xQsR{9l)dpLtz{ZiJg57YE?u(c;NvZUEuTT3!D1m& zAzmRPlLM3EJPVVSQqfXXV^!m!iSdb#6Z5$W`D!u?vU~Y^`3w2$&5pLgw&Lh=^cOdS zhHcwYn`3mlZ>+7dO$p4(*1l1<23aFG-&WIBcDJm>G{dy2U>@FA_X9nGu4rbj+gT*V zqR>9*a3{DkE4+CC)pwlHkrCb+{@1k{8BP2WZ55X-SH$zr&zMvmsjTH;-XCj(51eJ~ zOTl52^plHnTWH2H(SA>)W&W%loE6^kJO+nIN-&BZlgw6^%;y|j9q!mR3_K68-w2&K zN9H%?_a2pFYmXI=J&)s8K8(wM5G}Bgdp^}S4EbihGp#}OAye9Wy*k1SVipB&+T)jU zkxH|!S*U9+397_9ov^C^RDbqiOMMHZv?jY|^JHA9yLq^I%&gC^;7RN{CD(PGw0jjoB_>8auM82X(T@qK=Z%xTvtsV{Q}!JJ+v{aPgbSO{%Rh)*Pu} zd;gFkOz&B28B2vEgSbkTrZK|w!MlI7=*7xek_As0g9KA3LwdP-;e1lliqnWi$YU{X z(^GvhlhE|P)7lk61P0_v#CKZW2v@vqH>NRO%~vU4$$QD;Bdey;Arq6H_BH@q$!n5< zh9AIp-CvGXyTvx#4EQEBOL1qCjd(Y{t-fO;L5T*JeuvtMEQpacz}>9fyCxDwm9we} z?oS+zH;!W8D!o7XKsaLPW!OkZm&6%aOK`z{VlGO?lbQDyK52!cC=ZQ*}K`azBEhApJ~<~eLgz> zse_fuo)E#d#h@WA#a*B;?QUfwmC)SF{%4E)c&h66RvJtO=N~qkw$h!2guQBxrI2t0JA-*Q#r$sNce4XQ(QG;5f z&fh=OqaFIQ^9FemBtfG{WA)ej!A*XD?U3z#JjFVmn?_~%WpY^c2HEVNFS+1cQeNP^ z(>&dL%e+fFXS-IjarNo>gSF5rJ?x)!@8v-@A%-~Ui0ATt^lS$FbTS#4i42dB{I%kU z{fG|RbHS{8(fOUU(g$wL=n#_jRNGZCk59h@o;F~27_Glp=hdn_@i_=Q0NrHFnhmwR zZ_7iqJoaum4((_PbI0#<7ARp>&pJYWun^|w3Y67WJ8+?X_yPR+I?HOrGvRB@Sld#N z@NrGMS^JYS%_FTP$ZGtYhTYA|;>pTuukN5i-1Lebo-{UQ@+R#(l@4+zH8*uEjwbG~ z;DNfb@}|bK>;0=T$fWC}Pyy@B$D1(}D~nXb#oXZnW;$oODEl-!qs!-2$@M|^A$u)$ zuy>HVb0kQV%UxZuS2Y?sS0C3=V`<3PZ0IOg6lL1`-AO;>9Lv?O<2n51E z-LhZf7)rv-Z~NQH|KtA({EsMr`T=O(QY8(vFtxlj|E>KSyFPUR0MW34wiYyad3+K3 zEJm7|ENZ&!`$vHbA2m(p>;5Mx~3*NSakfd;jig)-anI;_T+rh`B#Kqhg{M5 zWS@}92Z|$@ZhEI1wY%|Cjod$cms5Vr&$S8B9up%R6vA}F$R{9&gwXN-UQ z?p?<|5N|A-#S1RArgDIg$-|v$sx@CnGg>{S3Q7>Vm|}yo44lWyofVKpU-;M1RCwCFnOma6AP5Bysrz5^P4lQm0RhSFZ=23Q zBA`LZ`Vf)-BBa4TwD|>)1^?{$%`Jc|_=)!lAPauueFDgWSG-36S@0Hb7eE&LiMI+M z3;x;H{pJ=x8XSOM0C{i(1ds{$3m_Nn6~H3=)5CoNSc8wT`vkBA{|3J=xOfJz1V4hO z1pzFi{SN*ZHDUq?!wcQPU$ag`0MUD14m|3J6%s(~o}c&{d{h7-_=%@BcvWDN1Q3FM z@h7OinT){D3L620;I-V04|6AQw!%#iz!H2aaMc6`e+pm`t_skR_v0DacmWY_hRXui zO&7!8zw+XR7Q+ktf)vB|;UzdPQ0U%yAOo+#RRJ-42Yv=u1muU`inqXJ0V#YV-U=55 zgz!yx4_pzT5Z((v1V%hU_z>J2{sbR{6IaB!)~Dk0;P>O#j~)&Fc^&1#3%?8e;V-xG z=eh7s;KDaGw~k`vp);YpkYbdP?mQfb9>+lKPo| z`B-sWTmcr=H<_1+`9OV0y*5H2`7P!U&ujq9eTPeZ z-|}wom+<7<|0PG?O|jwqRutdT6XC{}DbIpJX>b$1d+KewjvV~=|7cwChFwPj{^}T* zwbLkE#f$T{qo0TI`n+x0b;R-dcEz~s2;;4z>a?E+@#GLFB^&SGuH@qXO8|)*wp{#w z2+WJ_s{+%O^R9rYAK0000 literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_2.png b/src/assets/images/navigator/models/model_2.png new file mode 100644 index 0000000000000000000000000000000000000000..921d6f1da2ad412a1663bda19e36a5e69258860d GIT binary patch literal 920 zcmV;J184k+P)33LB5+kg$VD79I|*ThV> z-%zGf2?6aZ4`v4cLQB<+UqGgn>h^J8vP>)0%N1o>ss3C+rj_dUxvpNaOiNWF0`da4 zRSC(f+*<|m8n;tBd8Iq4jm-OX%2akWJ%1wkIOUIfE;f}VVw1^=e7MKgOl67KO#>{S z?yJE4CE_rLfL!H1i8xI_UU$AvHf=1--g!5bxQ!~flgw8OC~!C3?PUISk3#o2aRy65 z-|28~@G|%K&d~W@BDXy&@BTi7?-j=G@w0uq%eqfCnJ!=M@RupK&8F6s8tb{rdxhI) zQ{!IOj=0MIyJI%p*VVIE%vFxNb2f?1!dT^~D`gXC7V0X8-A!^)_TyD9akVK6R=LJi zWW*M@RdhkSc4-c09&Q#nw;yiWtVPWt<6isK=Uy_qRNZAB0jpayvZ`usb0_KV zH0Or3RqVEj7&+g$S%h2wbnz_C+PPWyU3R$QtYu~6nC|(%HC%btDzRv~-~Fn(c-Fev z{kk2cala}qoVAQoQMmrj+ff?#D|B%h`d9hc;xz78iH{! z0|O#6YmpjpGqV<{`yO%QvlgjQH#%#P8h6oMd`!)_@?CsP&AI1><$u)V`I2YW>cuDF zvS-#>bt~iAIboN)#PyuouYbE{8v6>Z>f$2xr>eS%BIPdc*i95EzY5(%k&39mRf`mx zs%In%ZlXv@zY<;Nc#(o_Aaq?tixm8ERi-&7LPbhbHJM@$YB;81nL-b0rU#I&gZ-|f zNO@KHgvtG&Cap$YrAQ6kKdc%qC{kl0DwjWgZ$z7WVv(A27+!=*aF8R;YUzvSR&b}_XD9HX^#ixa?n}2d`0)Lla z=aUX{OrKbT@f~E&y%p;BJqmr+FHrCIHxzk=+PpVn$hB(szDh3O|95i%U*To~zQ&CQ ue3csw_&PTh@Re>P;A>rfz$cHl00RIKHG1v!+AuBv0000)cCqTEd8e;g#fq2b9gAiYRjCG!&@TmMAi>5S-Z?*(?ou4$7SA}B64&q$~-+>Bx!QgzF??G)*Ds&kYOT4 zOI7WAhKQidf73*$%(qiSXw2`!Md`_1tF_9egop)@*# TY0?d~00000NkvXXu0mjfE4CK$ literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_4.png b/src/assets/images/navigator/models/model_4.png new file mode 100644 index 0000000000000000000000000000000000000000..e3714753ad9bc5c91b18b4ac563c9048da050b32 GIT binary patch literal 709 zcmV;$0y_PPP);0RR90`zJ>s z00005bW%=J|NsC00QF5LDw&a6AL>~yZEpG%W`armz{1V9M1L6AezraQx2)8HSjg3AK zZdGpEAl$m#vO%~b^4SLAj>$(GgqxNhfAo83gK(3w8GzUZ;ilx(MZyN*CghJB+hE{G z-no%2luvHo*2yimV+&=tr)f`kX2({_oGp|W&Q{6`Wou=|R?B%?FK6v*Wc1H!U-7mP zpNFmghaq=N9aU-9v|kd!Rc1-fmC3X#k_)axF1e9>9Fw&2Y|ZV`j- zWXa}cgw4p3&Q^w9pC#Rm5WCdv?p750V!;kgu`iZv@09vt+1?P{7Ylc#$i7&*A4c`X r;+;66FP85Cn%1RuAEoZH{0T4s?0s7GOWxKt00000NkvXXu0mjf9Zg#6 literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_5.png b/src/assets/images/navigator/models/model_5.png new file mode 100644 index 0000000000000000000000000000000000000000..4036e1d3177b4b23d4adc619e04b81f88ff8282d GIT binary patch literal 1730 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 P{_UrV(uGcb=B|GOej7t3 literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_7.png b/src/assets/images/navigator/models/model_7.png new file mode 100644 index 0000000000000000000000000000000000000000..031751ed4b1a0be57d6cfb40b0c65726c79221a7 GIT binary patch literal 2368 zcmbVOc~nzZ8V|CFkRT#rgs5o%jYyLBLK2eHh}j5KBQatD$0d*C0a20{k|1GG5M);o zI@G%3I;g0i(z+Ct21P_$1cpUKRNOj(P)A}1Da?zCGk zr4pQzrxON)!G(s1erp^ZtP=0`CUlu$6h|7DBQeG#NDpK9DkK^vhH9W#SPny~ z%PujHsmK?{isOk9*e0~;ZtNfWO%a7{eYdtNTOw~A>b z;(Hb2dLHS6Q;|{`QHbbaB9j_GQ34=HWU;6qgUMon{zN(e(rEztvM3;f%Vcrsfy9pw z3ALtI#c<`K;E%S@6OR;UH0roCT2fLHH7S6K=woRhhr_XQ(CHLZgJMY58X*%!Yw#Xt z5WxneUad2#5iQZm2t^}_Mji?E^g{?5omBdXSZny0C^Tg>6QrYoRDh<@SmS!HZ7|B= z|G4p4Z9{mn4yMUr1Cpp$qVM>UcCWU1?v$F@qLE5>d$z<0{&OzAF!(bCu=m+8Je{{{;OTa zMQ9INx1X9HJ$xD;Sc}e#9vzNbStJbxW3LSr@xx8``nq`=S^-y#;w5kB$Rp3=Wj?uO z*?Zy_YXys4Yfe6UVeQCw>gC*-?zFH?-&4F`Pgv4nob0$`PQaM8M_14VDU+L47L z?(PIo=MPsaTnQDjEkl3!xtZs}w0EjqbXi?X#Mx&nT-`JIGDin~ z!UXKAUA-M zl|buc`}aZf!oHi9>w(Se4JQ-9+gs5DEZnxdA=%Fzj~BSo)AkawCJWr8wLtfd+FAC~ zu&;i%RBtq&i#n9;fvpF&l;%Eh&*IBw%#pLBfwqA?(;2_`zn=Uxvdfg7?CCjAfc@cZ z>-}L@^CR#RsQW~hcaqyxfzeHXY=n#Zt z+|*PiRPOunUgPNEuOuh%dUJPO7~WT2 zd~Nj`0P3sm8i|YUbeBxXWpCDWybhBi?!L<3$%Lu~riEMpQrp{0?i@|O)o+p{=*+H`&S!4UW|oRy;UVdx&bQ&)+s3+@Wq*B~2RQ~Lt;5IU zyTp<8Y|E9fnOTir#9%h(L=QYR+?*kw@^dgF-+f<#?)t;4`)wQwdAlt>J!?mZ-IqD#xG{0eHSw-W1tO(lJ^<{nuP#CH)>&y+_+ddk+^YrhdjpUTM@OFcby>)erS ze4pE3e}K8f9P*E)GZ(b$vhThI+!6^dQW@QwGP))_paneA^y~J8_vS>ue3{oA|7~|m zb$`oFr0nX|zZ_C0aPT#OX2+Z=-1%X4fe=`dmzk=bLw0=Vie;>9w=c~(m6$p)Xp0TW nduIsf3TFsOwwoF>zKCq}IbIkPYYRi~`Ec z`AZZ)rx8%nIC7@kB!aa%S)m!;QJA7q7wXkK4J9rXh_Ui90t1XffYp#^wD7G0$_rjT zw)Th_6ySvlsuxgRI<-@-07Qrx1~~LcnwrUC0bDMf6~*ClS(^bilf`B*v6o9@Me#XY zK6?xB>Y-rP%$gj&QY?LC3)=}OS`;<$8H|F00(wCt9Wm!JSUeuj!@*|LFb$f;W<((? z&1eZ08x@PFKRqqaq5GrgERsgw)ym{DTh78aMq zl0(KOw;7@;H2KbotQXe}$ zWo~176YN;tc~x$2sQoZL(O>wj-u{;R&x+IvFp%Yxwy!Tr8`?eJG4Q)1)b^*7f4hBV z!g=+MJwCa2+hD+C#+2MQv0`dIeh#EBsDl^YFOC{HP*^}J`FL%#KDuzGzoW~m?e1sK zewuR~UD|1zf8KQ`E;h#Zz$d8(!Es+j214yT8N_m&^NqhU0QvbSg0gW=e??xOJJSDc zVmGvQ$tuUkCTmvk7uUhx=K>cdsM5Ll8Fgy(?1On|m19nY9@>s%W;7Iiwe{(Hq!V-E zaqGhLk@>B{8nX8qXPJz&A=Ly`Hs_KCsaLjL8VMU+m(_Ym{S9<|MPBWIJIwzq^?26( zWfea7v4HA?w;XFRQ#gMX^;A}CW$(70fQBxIcq4ur*mZD#xuJ06$63<1!JEfsX)LWV z@Nm9IcYjcKQ&`23QgYWQAMXdoZ1ghKx!ecG+fJ{Wk#L+SX|THF>$J%tq)1;v=KM&z zI`rsb3z(3+ic?c-d9=GUf#xu^<;{<<{}7ONM|(Mcpd(yzX30TsBDo$4H4n%C+%-32 zO_qc7#!uG}B7&7m4>iO@=MYkfqCjU3^*AIuTLbooE_6JLRpzS_$;*1vhh@dqCiI-s|xX+EbfrOyj@hohLMAKY23vLxZzSo|W-Yd*g44u9cK@8aD4fPrWdj z%51N+(H6+x-9YvjS6(kKIcd0Q3_tewbY+B0aq6M>JNSY2WqZ2!-jag%8e58|BMux3 zrVdFbo}2{1VcC>z{pp}x&aT4-^}~fKp5pAVZ(kQzAI1rv{AbTdy{hm_IU(`$n!EPB zD>`N_YU0u!-UyWk)(>;rM{Q?szIX4+MxBI1`A1rw%5^R2env}EdTgESFFQBKQ%rFR;K06gO z_!B*o%w^Knh+DJ46SK;Q`>B`1Gn5H>lgS$}5^M=B{vsU}Kq!PZ+DKb*;w6Y8MY1D}=H l)-!cKH+vJ8Pke&&!sX5uMikq}AA5f9Ns<)t7YSLV{|1ZhH~jzr literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_9.png b/src/assets/images/navigator/models/model_9.png new file mode 100644 index 0000000000000000000000000000000000000000..0f36c7ca95f5e8f830927516a32e2ea0dda8f585 GIT binary patch literal 1739 zcmbVNc~BE)6km=|3L#=ARD^U~f>p4R-3=rxD+wkWq6`KCq$66RALKZIf}^<6gz(^&v^1OpUkh;1$SzV@#b5TBgnd%)3Qp&ivS|<(mV{Ph`G05G+i9(9N8=~0Bf=!`nbR1o4W9)o^P9+5^G7D=V z6necDgpA8G<){+JFkCLzX!NL7qs-T2k!1c9#&ouNy%Nts6nM?+2HXE6YMk|DAE6le8Sf9f3k)EXOK{N{Ht81ra%pbx(|vU z3?3XG#tzPmGjuq5n3E6`{(FH=V{%`(F^Ii<4i$fT+j{j0C%HKJPP`T{ z#){aPO`>1^YCqrR%M!6&@{^mlKhkyU$O}HPh!r$lKBuz*h#{jS`}LNJ6=4zhz3!vK z4#ikYHu6TGGd|b6^QYc?+)kZ;I(@&e)+7QN?`p%m1fcSlhy{r?L-DAGZYBMTy#Ply zpg7v0_M4M~^JGJKdxBk1TPT+d?O8fk@TQ|MxsU+YKIeAW(li&qm992@7%PNr-l>AQgPSN z==PM@m;<=~aCpp9xZeoncL5pJw$kD8X~p{-o&ePLA&_v}D%{@>wV8l~+xFr9A?Va9 ziTTVcodfrZd(utk2I>^Y+!0sr&}BQcGmeK#f(OpNBiy$@aC=AW#~x3kr?KPBAHs!D Xdbwep;donj==V~PU!;3aYi|4-nQv%q literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_a.png b/src/assets/images/navigator/models/model_a.png new file mode 100644 index 0000000000000000000000000000000000000000..cc4a072b971ce241da3bf619ee8568962d889ad8 GIT binary patch literal 3279 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_c.png b/src/assets/images/navigator/models/model_c.png new file mode 100644 index 0000000000000000000000000000000000000000..2ce5efa4e580b5eadd99de481fa5eadc922f9a80 GIT binary patch literal 3102 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 bj_&~gM8om;uFF_m00000NkvXXu0mjfg8?4Z literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_e.png b/src/assets/images/navigator/models/model_e.png new file mode 100644 index 0000000000000000000000000000000000000000..039b9276047bdbedc52c91bf7e655c2f9c314f75 GIT binary patch literal 3267 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 B2E_mX literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_f.png b/src/assets/images/navigator/models/model_f.png new file mode 100644 index 0000000000000000000000000000000000000000..4b4fadb805a0abf03b278143dfadda55021bc102 GIT binary patch literal 3294 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_g.png b/src/assets/images/navigator/models/model_g.png new file mode 100644 index 0000000000000000000000000000000000000000..26d03724ca129ef9f820b139c13c5f6b98b0c8a0 GIT binary patch literal 3395 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 Z1^|?liOZTpSYrSH002ovPDHLkV1nu8uS5U< literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_k.png b/src/assets/images/navigator/models/model_k.png new file mode 100644 index 0000000000000000000000000000000000000000..96fcc8b15631b3f0fa7476bdf5670a810cbbe586 GIT binary patch literal 3785 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_l.png b/src/assets/images/navigator/models/model_l.png new file mode 100644 index 0000000000000000000000000000000000000000..f479323b7e9ad93fc49571ad4f25efaa510c326a GIT binary patch literal 3717 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_m.png b/src/assets/images/navigator/models/model_m.png new file mode 100644 index 0000000000000000000000000000000000000000..d1d8dd76e6d69a7d372a1ba32abbf964516af455 GIT binary patch literal 3559 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_n.png b/src/assets/images/navigator/models/model_n.png new file mode 100644 index 0000000000000000000000000000000000000000..6e023a1bb12ec994049526a6324b27a4817f629e GIT binary patch literal 3778 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 UH@4=RyZ`_I07*qoM6N<$g0Xn3xc~qF literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_p.png b/src/assets/images/navigator/models/model_p.png new file mode 100644 index 0000000000000000000000000000000000000000..356601e090f91d9db5592147d17c51bdc33fb091 GIT binary patch literal 3751 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_snowwar1.png b/src/assets/images/navigator/models/model_snowwar1.png new file mode 100644 index 0000000000000000000000000000000000000000..41bab59ce4ec0e52db8dd7cc3f37b3f363303228 GIT binary patch literal 19090 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

Ull{GX01V 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 V;)BDmH6Z{1002ovPDHLkV1k2Pk+A>( literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_snowwar2.png b/src/assets/images/navigator/models/model_snowwar2.png new file mode 100644 index 0000000000000000000000000000000000000000..41bab59ce4ec0e52db8dd7cc3f37b3f363303228 GIT binary patch literal 19090 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

Ull{GX01V zN2V-_l=w!HlO={$j6SeZN_4{j!yuUN-g@^pJnlq1%tX$ zgT+za%DGyaOveG<&KM(Nxx>m!De1CoHk~}hk&~y`y>CB$pC6y!kLxs8I5LspFuE6rr(rO zQV{aL9jZz^mREww`8YTIdJXQlD>&yvAH>vBcturV_nv(irkDTv_`ic?F*ZMUSRC?J z#wgl9Gsb97Bzb5hNdJXMR~YRoUX1e za&mGA_!5L8G4e`M7UR%?{oM5HH8h1T;D-2(cbN`{-d&RM6bu=pH zpG`?nBVjw3YU*ItMWiCyrVo86t04p;>o^8TSE9NqI5vB|pXNJH))1QTX|~++TO0|B zlqAQ`V%fq4*tU(MI0&V$l`Tvw#)R5?dGz{AsBb=pXe^1Uc?m}?1U8E5CC8`I-qDGs zrQ9Ifb|?;c35UZdia;m|otW3=+Rpa92dO@AkeL%FoK~(^$khI!80Bpl2U+#fS|0twO*pRPgP;5s=bXJHW3JTb z%F3R6rs32=k&-q%)F~z+7-UgZfP`&hxekh=kZ>igQ1IsDyb6RfDMzN>G;ZxULM|(#X!cZtI^z+Tn zT*t)PdwKdt@1?G3Igw}*O*e^H66wT_w($J-pipA=ThGs zK@ozOr6XjFt}ci1l>tsQMQQDTvH~AnF@?MwgOl|w)HMr=^B@|HwGRb^~hKKqY1LpX+(m4zIb>y>Zl6#2ZEhA}`fPOBc} z4a(JeTf{+LShEF^t>K??XDke>=Kv7g;i3FbXgh@z&Z?B1>D1)O5!HD7*(n;NHDqep6tCYof@3KXl z^~I}E(&IGT_~m;wI**>9b>Z0UxdtBhr=%_T~6BPBlg;E6FDu zwP8C*7u^Vu?@Q2}EJ1pM)E=+lCuzm_?&iPJ+!`SqiGrph?Uc*oN=d@9m@>8) z*L8R$kw9WWGC6D(wAX7=UR27TpIb>%x(HQe_QVOCKYb>;N_H3}k|dz31Pt|^ILPXk z*7ES(KW6dd4#rQJ%>EXWV@aL)rB4xwD_8|SOiz^DoE*khx;*jIiM+7(e(roYL16mD z6rXoCj%5uv>Iu6FR7|EQKfriXU^yeP1nn6T^y-nZg1p4wE+yut>;(Kv>#rUGHorll=PLB5YG&MhJo-NjQ+tfJk{p=?Yg z9>X9k6`Z0`l#eQ>GGw9%7ojMrQzk`08Xi=S2US%O{jG)&=_5}uqFgEINZh!?zU{kN z_w--s+`FBH6UOkR54|5rsB^C7(f+Eo)>NPN21r_?wWizB7P z@AnZ5_(7)DLsMHjHOEe{`_w58wY1UUO1k0+N^<-|Via8!n7Wd#9|uhlBgPn-B8b^T z%lBmSzE$EFv3P`QJ}`;fe)JKN@eVpV+L0)fgghJz=d=k<$Bq?qmBpGN{W_#l=H6qcZjzQnk$tl;MPasN01BE8)IzPvYkuI zOlHiQL(;M{zP}4R^uYZ@B2hm3+0SzI)mL-J9e1#K^JaGK+J#~0eE8o#LVL_2-_&0n zw@9439{vsIz~q` zwibiE-(cf0bgtK7>_{i3lRzj+Pda^ZQ30b$ict`#io(Vny8tLFEk+<|ZEhy+IDGix z3&_>Atl}tdWE|uzR}a!59UYja$+g#B%P)TM3*zzA z&YPQ?%iVX~iP!Y;k&k?2fPy`?ZYR#3n`ki4Cy<+i)3||)#+;z7%O+S_MPqA%Nb@mn z`sHf0`B(F;-ik3)LgY}^G3_H;i3C^{3Nn>$@A06k3XQFuG>2{CjVFmVH{!bPpbAo4 zR6tQ-0X|LVXng~nt!W&Uf@`l%gXo?_UWLFQ+`=0-M{3)Kg z?`9TMv~upEDU2#FBHY!EbW)S*xRRh>M@on!RB&Ucii+t~>5Nz?X_M*LE~UkJG`4hN zdQ@ImcZ`*XgUtTWH<)woIarDK;6c3+Cj*bo#MWL&!i~9O{9)9>?ZoKivH1R{P)b!k3?!5C(e7?Sl0&wkh z-(dGkxA3lizlzxt@;Gy}&Cyd)qRq$n@$Kt)WBcAynZ)8Y;i!Y6K+=|MuC8U}k$lE~;5wEpKku{^MLH5U>EQSJ zx$l2(;q#ZA$HFt`(vfzBhI;?+e)qe4?|a`P91a7}(a}LyR~G;iCQKj}iw$aR`zuNc zW4I`aLfnO84edPf;J0}0fx9W2_aEGQ$4>!xdDB*Y_sl9j`XAR2iAPY3ev$dXEQn0! zkqx^~2$HcFxGs*B#Ow7EkH?869Zu9W6YuO~Vrd~c!a*u3n}$#jkEZcT^)56`Arg%+ zead92D$29U^~&{VDB`tq_r4|SL59nfS~~wsHmzO9>W6P*#dL@BmP}>zs3JN$`&ty~ zx|A3DscViQfPkr?XkNlGn{c>`fZv1bN{aIhqDdVq8Dmp*El=*rVfK|@W6pW!VkHxU zA50B`F*a@8&hf3U@cZvvLucG3FN^no_~C~sFE3~2%$exAPBNJscJn#7Z$Dc%ZQ+dh zXEJfh`lR`P{D;4w>db}AKVuHI zYomD7K9Tw1a=o%rltcn-8^=n$o1yCnAe^x2OiB`M?KB_Uh2^+IDoANb5su@YcCOcJ z!6-sXDgRM5-~Dr?MvWTHsk-`9O?N{Rx~}y}Cah~|C0cumb&ub{((w__Su};wql#$j zY{yN8sVpntRAW1?<5E!;;^5I{f&mTJl>~Eg=!_)6jU!Pg%++aZO^};okg!}fZLj4o zdxK28;;R`gN^grH6Al6p#tLZIH6F8apT6} z@puMRl$@L#mRx%g3%_zT8*aOUmw)yYkw_}`;q&>p{r20r=9+6pH2&+i@8A!AUqi|C zxy+qEABM^G%2<>&jIszJNJOK^WCB%7tq)z(QB?)$*u1jm2%I=XUQrRnLnw%4*_4(P zv3u_U%F9Zrtvx;<*DGKsuRBIjul?5bH{^LWylSsw%puO}RXP$jaa)pbT(Xsy zQex^VrlxS$+D-g*?FQ;7&E$(NF+ie8l$+VfN&(0`VfvpsVFVRab4OvI+#3Z zBD2rEm}gIgSo2~vO~>~zsw6~dX&Ei;5nLyUrYUqq;*=J8K=INM0o*vQYm*xclC+^K z5~rf9fVM6NE16(oRfr4cny~M0-22-{P;$pGYQh9u$9^@A;iwpj!TQbHXj}6v^T&(1Q-1Ae4T)sENJ))I+C$j{HmFbo{W>1$<=9;JHwCN}^5kIW2B z<-)}m;y4bvo&x@pCr{?dC!b{2tXWylUtwVpmo7VtK%|{@e|wS>Nx`_vF)6nZH}kvn zbH94tc?j#L8U|7*xQ>HFpeYKfq7b(%$_sK)3M*-7X{Xp!(KVgct}b$O0%(dtZ9^l0 zfFIX&2?YE!HMd}z9>$Cwjg$&Uj-O=Pp#!uh6U?Y6?_;$G1pO3&S4)*Bge`}JwYOA`#SdykP(yzuH zc(TMOz1^@uuu4yQdpv;xI93AJbx{=+kEU_FC5*HzPM$cy+|oRB-Qbn#odYaLe^wU9 zbt&-sscERAxTJ&&XU||tLH?isyd&Wd^r#?*$0%<~JqWntBg>afBQHN6 z>>6r(!@#y>8y&7a+dHf=r&&+LU!gcw;(JVWCZ*L6vDb)jn-5{as+=&Fiz z-BhkuB1Y@seH0cCtsu7JFn9a}b{#)XQ&S^`Dp-E*IV>Ahk}(N#Bp5}#;f5P-=xuu2 z;x=(dVrojCT&cL7s@m)CO7%D0z2&Rm>SBc>D5jryP64y0j-@C+hj=oHs-}*NT`57+(Yz)|Nl`@wi>~-2jrwFBd+>Q$ zPwu6>G(=v0F}Acx#=3|k3>?QIngCTe6ohhd9UDVeFufXzo8kGAv2KR?0WU*5pki#b?m4`yqwYFfICNm~Z=E-NCW$oc3ctQpEG%qfaLZ6N& z0~F|Xy&P8(PC9)e>pcqcdSHy+@<-aEys&FGe|++9AQHDM001BWNklG5ZI_Z6Qa*XMoB80}9RH*(S3xjegiHMe~2Yv|Lb^Y_)O`Qt+m zVP{a3=H_NfN=is165N0P{mh-afJYzwf2`lMfp<^+6Q8{H_jtT2Rino+uCkJjgfpV~ z15nd3D^);q%qA~X$otP($S0OB=Ek4jLd?;q8X-pMJ}n>Yw}m25G!3DuIDmAVRQZ9T z;y5nFp&X)ym$+geT$hg4){F{L779_{p0X%GzYn45Boh`dtzC;J$46ChAs$s3^8GVm z6lE|U^|f%{wj^wR_P_(2Xlh}`j2UUbIefXOkP=7p5GW~OYE?NU0WY?cL|m7M;}SQ# zs6Icsks*<=SFV&5k27u2LjLpKXVEVC1;2Q#h5P>a5(kcUk&{=>?5X7#8c5+q5fayh z4mX!|M|6&D`Z^^*$cP)D_U;5ll?AU&s(FJ)~`S`<)pR<2ISKM+7 z;q<^X)zq-^4}ak5n{J{x7USz*|2oe+^9-hG_EExJyLK^c+H`Kd`9da6(b@7xFHawx zM8okb`T19Nvi-${Y}mZvw7G}-_XlZ>B)4el$79T9z-`+@i5hyj^i+6 z#te=(w(yj;d*RT^9?1kP=;0@RyYn_vPV4!pL;Ys=o!v&&i=993~cvW13zH3kvA$jPgQt zHH&9XN6%o*l8#GL)WWALJ*QflB2o%+x)>wV!uaLGf1;>#6dfHMloS^djm9!6NO>s4 z{)T3vnn}#?Q8BuLviux8TAI*V2&C(f8_dPYV1D8fY(USHUE3cFIw?h2eC4NTP$u)( zgU`}de~99uJc~{UAAXp9 zJ9qN_Idd?9A3Xak+ji_gIe3srEQYS@=(^6aW5<{(v{hOsp*L!6>%Ss3;#z zGL}FnB5RD&tu#Gz*-uVU6nq{J(O8O%4~E^$m?wmXG4uwd+|pa~6iuS24Ob21WEu$b4PvK@Og*r7038Fbs{3CIp*1N@`l#K{PK5q^X^M7;pnkrBprthFTB77m=kEUi6Sw=J- z$5aHCX)tHfXmnlYR7Zr(9T5y6u!lS~bLdJWIaYg;g2L1ZSO5Md{ons}<;B`_X&)`#Wnozv#Yr2YyUxcK`!E~X>VA~J-`DGJb-0c{O4yrNjMy4!C7Zf zU0sc$C?p++7hZUQ6)RQ%5Q#+a`FyEeJgp>GUwt*-{N^_~dAyjdhZ$mNu zrUu53oy6tm7(D)$2l(dH*{}Qa52zWBB$+mKDvQ#kz%Osw%J1&@^^mz<1LkU&q);g( zcA6l<2n0~1#B^Q4PO2uQB*$b-L6G&~2suuip|Y-W0zoz%Jj}n9l;YEe=jBp={DT{B zFm;uhwm6|d(y@CFZcSU9@*HD;k6j;wU;gROYS$l`B`WV#NxMA3siIWhEsgB@`DI)7jZcd3ia0e8;g@of`OBgq* zn5brOJRHXx4-<|hdR(l=re<<-0)!(Gst+9C!{;vUaUFXJ>vmzJ1CfvGbt z|McXeLd9pA?A*DNi4!NH>pFMcbr+K+O-l7rkH?ujc`|e6%wgNMZ9T`ki9~`(B$6sd zO8;I}RTY6ifHiB@QC{9fA_1$n!*iQJM9g((iL2 zCEkD^T}lEa(P+(pj$8d**51#ba3l%a9Y|h1H2K=#gbEp} z?t0gLTP~5ZD$XqlVxgpRa5NoLS8;R$TY^nL`W!JZ%{nVWgnoGB$mTWZ(I{u1yO^c3 zX5!Puu+N`kC`;hhC z{p4p)pUH6t4jnndkdpU*M8g*7Y>9$!_dqiv1(uq^m4bYcy)P7^;#LM)hb4 z$)tlhgYp(*NJ*$#Wi1R-#Rg5mS08D>udV+>cc z)K@zI*t=mJrjkl_j7B4vIlhd1Oue7KGvV~OBE4Ot!9IVj^7r0*FGl(~R7Eg$$_y$e zPs4O=g1udw4DtZ`_wQ%bs#QGr;8RSTSdDErW7{*i^2+a1Q?rk2ue}x^>*w!C*aKXt zgzb`aCDEia!1Wpc{-X&_bVW$GE`NUE1)^zwveJ?wVy?~9g^T+}^7>EdY0Ukg^*Tb3 z3`bCKK}t-+VD0Y1h?9qzH@1xTE?qpJ*L{2ZRi=|3>d|btsRrY;>b|Gj$DL6%hLtB; zxOB>BEZ4=6kmoZ=sM>4B7=u;!$v*#j^`@{RF_ppjm}X5F&j%Nr0no=s&*X~qcAbX9 zC?k>o=tn;~Pyq~(Qq4+!^5n@#$Kga>J-Pm+Jq1*U5AW|$j%=SlYB_{VZGbB^z;()i zQKGiwPn)-L@aSP&JJrjvthkg~tFGrA!cISqq;&O_?2awUEav_+Fp9^qX^%v)T$i(^ zjOMQ=Pf%7?(Fb2=!dSiHAPzE1jL}WZt;HsB^BaxcO?l5bF7)2@y zimnrN9DJ&R(p<-!@$btTV`)M_UX~c6H%4iT+Zh|Td~*3xp5L~E*$d9ZR5apm7|;;N zAu8X(j*F=bBz|*T7c2{(-_M#&+t^)mm{FxAjIXSq!0TmJSut<7%d#BIUKZhWu~xPi z<3N2QEwMO{sRLnY6nqAG%xn3Xx( z`1K@h==P;UUX5I@#&>^zAEAnIOj~^%$!T)%0}cFrhab<3cT+OHifBg%!BAnJUXG1T zseX=`nw=_&LZG+|0)A#ppEB&qAGDXsNP)T#D6Yc(jhksWR>My|_pt#nj{aG^nTn>= z8*aGahM^MLhDyZi@8cS(Am7wUI+FT`#U%^Qpt-S;KmYMh%wM#OxNQw@{)fOQL#HbD z{oc}oJap5frKtsJC3`3@QyHV8$4B=2efWJobVa45qk{=mV;NOk!s-p1h^h*T;UVNR zDe~zUS-yWF{r;LFs0*ij-O_;ZmX&|;h1KkDi*ew1Bk!JF$I-wCI3cGq*}Dgy-(>L& z54l}iSoO>+n5C2O=jD?Z@MF0y(P)fFGzP$^lH!!#4|GK$L?TfX)6cl+Qy5!WjvPP& zE{AmIhr<|yEd$q;s<>XAI)rjl}f0fLB+TGY8co?4&@dDL$rQzR7hRe11RM z_8wr(mTg(_F})qHOyuAvES)op|3=(aN%~$T2d^CE^2=*Oys-9O9=u1;e%>dTe%8DJ zGcaf`$07Zt!RK5^F(Ot=RjM?eJi!Z3|Ahy?e;o}Gi>`iDOv4>UyzX(3-ttE~5`?_k z02ALFwb;LJ4}V$v3N>wEzV?l;5sgPtJZfLpD}yQRdz72qf-He!%&&B3!yW)d}iAYaV3_f56PDV(z~0MwXZCqbp|P^J)YFe!}5!PjAEgpofjs zr+8=`lq|TMU}+h`wTJBGsQdh=x}LrErY(pV4u}hZn{e=Xef<1?e#V$wKObAZgr#$5 zfe?M-I6gxq$500c^qHtf-^?bSoe77qBPjPNeCkKHFy@T2m^*JiSTTg5BC_emBC~O~ z-W)!r6|<(3=P}+^7-e5WD>q*?gIj($kR1GIIG?R)a`4uOz?%lJ(U^nZr{ZQH2j5s- z%bhC|6rO(-ANu$Qv2CYMFGt6=5rzj(x|ic{bC&h|ck`3!Cfhb|;>eZ_+;jc4LoWV= z+3HAkzwcm>wU8hEb4L*)-Z|X=wokCXFA@74y#M$@R+i{oCSp zpEyLOh5bg!AL+2T^Q%+2WW@qX$_mLb5*#`Wa&Ui|96Ud0@aQv#Samd)*_U0*tYu3G zr%CM;!{{LhA8d|B;;{Dav-0wWjfg`#xa90bESfVbEBXG^qQlYtsNeeT_io76KkH5Y zmUL2mJ#1H!Z)T5Cgh*vgh7&fED$AHLV-`8F4%R>Y7uKv<%fe-66AQ-?%adBk|V>qr$z^gH~vW!O8pi3$=c19=kY7hOuKt_j&)g?c&pwfhIkRIe4w|eq9g?oXIcm z`70f#4v>=*q+(P#k*EdI!gf-*O&(pvZ)zkQ9Yb?4JsN&fBc5~-!ojj7$)tnd@24Q& z!v%AF)NOp6KR@^ccHu-O%$PpJ$z}S$Aj{LK?mjEI{s4F1dK+!~cXH9R@m#WE1*OHs z8PoI!1LKN-xRa`P_vu6P_P&YKqu@x%iO$5JE<#nFm+$=imx!tJ`tmU;S@h_TeS@=a z1|JhZb4y!KuGdE{zKGo$Hn93nx3OY&glUsX(1gp*{q;PxQRBDwzmPH2ssSP!(npLRSGO zB%fJVd^N>+oQTuYvTh`KcD*=|U)i#q$j(jN{EaX7u?WNEiVfvT4M#otrjl0+7&6(d z8_d@n+{d3^TF>G32w(l`b;M*M#m6)PjPk0EWL6Z{adB-6nQo$^Dhi2Y64T>J_1@c6 zLkJ5y(nV=$$)F0-(%Ozt6ymL|Y<}z(uDz_7spE@C*e>~gg>}}INa-@gvyN@zla%D> zcsd$6fBCs@8b)y?+`E1=2ag@;$(1T7E~WNV9jdBQT3XD(9Xr|i^xZv*(wiI{A#hKV z9DL*UTK=*(hly8wjV13o4=Wze8e<>?rtom{%{MbT=;Ko>ma%a5jEn)r9!0SnO8iFF z7-P75-ZJeE24WP2Qd9838*gUv{6#D|O^h-esCUyWjf!I^m6GCI!d+cxx{sO`Fg2Gg4F%*F7LyBN z)a={Oy8XEWT3Ekc7^AxmWOUHrSW7!=4jti@&D&VfFITFusU_9oEtH}%(hY;!gNNAl z%-t**7v`KrQ>YwOL|11!b}~%G2*|+;a&?;0Jqrlbo!tr>(UEuh+~N>F%u{0HOSR+AK-+wrV14A7V~f6Xz|N)?-l|E0Q`1 zln~0*iCa3&?MVV28^@L826JeSB#~B(QDp@*w8U_%D8YaSMGc(fxT@&`WjocK%WqdrcT6 zQvfoX-g|iSdGV14Xj*e$k7BfT#L3Iep)+D5WC8?4qYge(#c?I^ghRex@@(BS7L<1~ zI?zT#YXX5wZf+iiHbz{`ba&3)4_##<^5GNgRWMo}_bo53*3=vi}_ zJ8wR*cr?YHF+z+oWUg1Q-g_R8ht z-N&M(gzv88E>IHo6^7k-j@`Rx=M$*BnDdhHQ-Ta0&@q9DeAH2R8MX+PX4xU@P zo_%3!cs`~qT(JH)MfZ5wzG^kq5B-GY<2pHi@fhL>hfqO~rp9)(^k8=PON_-G9M=KG z#P3Pbt#(FIct%QzP<`a&y4>8eTyfn^AoMCZ92fP|!~_7Q^H5B7j&Tfo7T*9#7A> z=pHZCt5@}5(Jw14L^=_|o$bUO4}OmgiU&hU5)9_h*%j&O)$Uboo;X^?`^Fw260=#~ zK8cX5XIfE|T{TfwR(rYaw~r3H%O@O(C+%LpR*W%h`-AqlCEqNJ@p@yF0#lxXQUtF< zJYlB-Y=HnJAupB{qp>xCrlu4l5C|f)04Gm1v1;93)*i_rIDG|^X3wCrvm@(VuO9lH zXo5Wr4IDmE%dUO<@%#MPw%gN*YcK_wKWhfUb?A~R2kV>h27^?V6=E7{PcK1LH$YJ^ z(&T-aF2n0(`-`jCde2XoUEIL=OQ%s@R!G2;;8cAJO>GIvihO{?b|k8jIxY??1;oaD zn~tsDqrAu@c;-)ubhff@|2jJBw)2}`KfqsC?iiB)`1QsZ!^!_cTd<9^=6z_4VpEmp z;Zr}lh0$l6HF)p6A#%NRuZQg~tfKmnpYh&lUCf_73f(aA85WIgNmND96-f{XJLX%UC5rm@ju^7Q%9>;5&cyaAsp5Lu<&IiBD{3VN7vuVq)xnBL<&|~c# z#H7kEAABfdFUO%2L|2s|bEQ<>08Q&-F^0>P(mft_tXj>>cm9|~rHwrpCC8hfy}g}; z<3T!6q*Q2gf^0ltlIs(^dwf0Zi2x^BEdGCc=NcSWdEW8gdoFu+&q}M+-L^2Xv2hYp zTe6L947M?ukc2kLK-!^oW->sMHvRx5Nx>ncGo2=ZOkzwfWG3}A7wAkwr=1B*CmqVr zK(GXMEDVwzhy%gK*jTc4v38}soy+_7!_mrXd8J)RD}0gni#{Cx_Z(U8bDsDAKL6+c z9Im;GTQ^!>6+}*rn?LBfjAG2fKN?eJ@Sdw4Ir~Hwj0{ zXNGKa(Ea`6C_7Gu3pr@@t#36U8A`v zPEXGOW~hql(ZG0d&mlVAj&l92+pt>}Fu%U8EUj0={P}!xO&h*c2w?A_w|MjL+hxsi zEDVrS{R0g3^})iWtY5wup&J#~O64=k{!xw+WRylyBd5^~i-?xs|3+>l)_4tnbweu; z|L}em8Yf8AZQ<|!=5Nu&@LRh6V+UK;WLUFu0a7Xw zBRMqFW$4M*@styoNTl7_FMp~sb^lKtG9s_fBj9qc;ja9{IS`4r?FV1CKn$C?<>WE zrY0I|>yV~~#6T0G47tBRQ;O4A^k@-8EXIb#RpdMmSNeflqf|(t5w5O2bHAd&AxcZl z$7!XqgCksb<3~7j@DPKz?0WhcXJxNe000TmNkl~}s;6YQm{;$iAqSFm*1hgcjBv*wn+!p?I%%4l;z%z?Dm_5X4`YXIl$&8FWl&Ss z!ts-*`Az#P?CEV{<6Vz(=jKhM`ulNn4hls}eH2sISv0>k(0YwznCL`0W`*e=U9buj z1coYYW?%?7GU9?|vvJ*(W3wC`-$N4uUx8sq(KHR6iIfFJv{IsctyE{%?@1l(&oV4-s$Zn z9p#@A$%$Ed>>($XoiV4 z(VKJ%s@mylrLvyHoq%KH4eM$n2A2N*a*R^frtOQBU#L^@?wt+%>8ytaC4Hsd9T?)} z{rg$5{ z%s3qH>qDAhkdm4b4k7tbbutv@SjwgP152r`im)US!c^cY5F*GPQa8}eBID``|6j+u z%#?9Vulr|frJniLeI;>WLnF+&-oFzW))m|OScZ0vcW!=R$HTLOzo_aUmB0feQdypS z;bj&N{hXGD5DnG{zkYijiL_$f!VI-~5+Ni)x3Tmb@pzo!tVK_6KhJjr8Dq_z4+I$_ zlbw>*OY>Y9)p~)zbzSVR#o%z7M8+l6KR_~_W{D-pc;09R=|NXMG+lvhnZ%o#iCQ|1 zHC5zXZ)^~1aI~y3qM1~Oj0vAZOrlkrv>#)JTB*WAl#J^WF?GU*nAJMQoT!7$MjfTv z(%JRozY-n%EyuhCJb3R8{^xfGIeFqBH&;JRTGrruIigjyoapJ}`HnY6Gsf3x-BLu2 zTDI0pjBZiiJB{YQVAf?0# zS-75yX#~L_A~s@l=A!~40@89^R#oL^i(>s4)6q(m^8TI8I>wx-gG>jXe_Ku__IvCX zza-cFZRFyc`Qo}Xo`C)VhgeNe$LQGiCpzD)Va*+1XVphU9DFk16I-p-^b16 z5K55~B3S$=EYn1x@H`)(X=p;j^IQ}H(=TR%s`7ijgT^I!miKMB z+n`FwfSGE&P7EY?aOaQtiyJp`+b6d0-cWYNTCbwU%A-Hi zQjJ^Sqi=fO5BgFHj#@>2WJ5U?NX5jMg&Ad%S&qN>(GU3C$8O}74Qqi#$Y@CSsH@|Qx0tru$@q!M+MoF_Rk;*{0%U8was zJT$@s58RKdK_;C>n8oK&kIxpA8yJaomH$W_i- zyVJ-JQ^_!@*w#%5lqa!-#cQ3Nyu5E8%c3T!Opc#SvFE#xW8~`?7b(Xmh5viuOSw!F z;*WEs4pQlUl!1&#+|sED8|>*iz~j$#a5RzP-tG4@lo}>#8$p$NCU5+iWDFtja~?Hz z6%ReUgPX31bM2aIS$FL!49^=I@>y2T`{?bmo@ChZv2^W%uVc&*|3RUYV%YI_ds1z> zR5?Z^@Bk^t=Ty3M&p5z?J0IiqM2@d)zmG&}B&e>3^jveYzzh8|K204tPS(6uiRa-pT_akIUM66;0UvczY};u z6qG@_lv1siBFA{Y@x@w#AK3XAVap=n_%vU$iq&h^;`uIap0CW{6ifLELo+zAZ$Gbe zcG6lK;lxmipM2xXyfc)gZi2&yi55!ESHUci3!fv*3f}QmceSOr4)AQpUV1Vfi(6N-V)ZIad~{1ABMW3I69mTC_+k`cWwHNv`#AafYux^k z8`#jclEmmpy{YawTn08c#E z!Mn*c+rM@nj^pDx4!$Od=)s6(TNkALVCZBl93rQ5EM4QFhj*~KCC*20SkH#GmD9E^ zoueG1DBf4>vQ2GUq15CY<%Lpe(_G{jvyGo|?K#3R z#{XMSs$GUAwoQ0&UWQ57ZNNXB&U&qvImY?Xij_SeX9o9oe09lljB(exfqw>`nzXW1 z-iT!Y?odj7K?t$wGRJry@spT(SNi^!IL9cgkpB#LqMYxWN`T})%Ycsq8-QzoD=6qE z$$y3&A72K(tdou2o#=BJfBx{n{K;TlOjm1lA>aiJhD~wY)U?-uJ;1ZTn^XC&{|AKe V;)BDmH6Z{1002ovPDHLkV1k2Pk+A>( literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_t.png b/src/assets/images/navigator/models/model_t.png new file mode 100644 index 0000000000000000000000000000000000000000..920255d74e150324784449c3c63b545c53beb543 GIT binary patch literal 4329 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_u.png b/src/assets/images/navigator/models/model_u.png new file mode 100644 index 0000000000000000000000000000000000000000..96da1012a9bdd8b56e569ce6e950caf0787d606b GIT binary patch literal 4126 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 wtH0c{y~4D+)~Os{O1f?5uUwcfi09^Gj#Q*>R literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_v.png b/src/assets/images/navigator/models/model_v.png new file mode 100644 index 0000000000000000000000000000000000000000..6d85c22c736481ce8b4f1388a43393267e3b2016 GIT binary patch literal 4416 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_w.png b/src/assets/images/navigator/models/model_w.png new file mode 100644 index 0000000000000000000000000000000000000000..7bc8024fdb59f4e39be4ee682f8421d66a7f8a14 GIT binary patch literal 4331 zcmeHJ`8O2)_kPJTb`r@}wvg=mzKu0o7>%W@F(O$@vW{JPlk7C2vTvbAW69VRM#wt$ zv4l`0Bg}mDcYHthoO_@9+;i>^_c{06bCYjc7%|av(*ppQOpNud&k%X$7PORSQ3f0T z59s`j?SlYdxbV*@K>o{10MJ|BgFw=Qn{u`TYa^Jn!B20O0Wq$_4?m z*|?~AIR9JEG$}gM)X$oOmfu=0C7L~5L|lN5^+u9F@id23$9V$-DxUtLB+As(=w}>O z5{ysjrywf=uTx{+Bt89dycG7vXQK6RZHV+?Qfs|>AJsSv!O}8j8_QcMMl)9ET|U1U z+t=N@v?8U0q!;!Fth9}H1%gh6C;%~1L*u$27V-&DJRW191z7U}tPF1=nPpQi?>0qr z90fKwUB#3(njPpoN!2R_I))U{s9X^nP(%e>2Hf2U;HD&S5sg|K1JNjAUKj;%%oO0D zC`t$X7d(>mftNa{9=`R|0NBX@c2Co8RWKz9WK7_m#^7BOz~WdL8UQULkg-aNzXqtn zfJ?uS&|?sn2iOhw;2OWK*RrmQpDC5spn(xqH9$JjN(MmSaH-2;I1|q6>`JcZT=R9L zIz#igVO}l^k7{^?T!zrbrnVls!+iA#xZf*q9>Ts364ggERks~At z=|=8ob(&~j((9``R9;RDMGp^CJR4YaK7gW)8Th{cKWz+(pJS${S65c1ZglH8x_*I2 zkPw~DSorDvqezXD!-K_7%fd*xJ4hoc;$r9EZ?h8K;TLpqZnJA?Mkh@S>6QiY!_KIB^4k7PqpBfci1<{@+F9HB?(ifJ3eQDJ0qXj^}2r2$fm*;oK zB?&AIZwK{k2g8YrQkF_CFt-KO4{F$XX zZ9g(=`m_9Xr7i3bYemvg>GsoJa21$IqHud^CzMLdnHNtK=rE>YOA+S(oWyP=Xqv8Q zENlG~#&2uPx2G9+PBrniVRO2AKah(#NoX~a%|td>X?|n;0L7FmbH^FJX83wHST;ZH zVo|}@!1vsm&y>v$L2oF6ZYJRyaRHr%Xo~XVobxm{D%}UZnZ_ zQTHV`IoU)zV+o-NwzLb1d=J`33`nRxAwo${;2i8Ix5)}ms#@s>~ zpJ(p+G(c1{CHSxR$FHDPx>n>@cvb}V9O+SRIstEqHp^nxU%YQ{t+K8vtkQVo%IVs^ z$+w;>FNU30OmonyDMnk@StXWVPW8Mo zABnf*p7OP%XjWDft3u^($io}0_cJ}UA%xrj;>%zQx^=O#oc+kB^X$9Vmap-YDwttp zJ#PC{X6IzvRT)=VRT*uyDb;S5p-S5fTI`RJ%({IyL~n~4A<}TvsCAoZo4GRjvWw=L zY6rDal|Cgr#Vz_F?GMU!atl8>MHia+mCc8?t^ehXkUoW-`u;6DFk-Z$X=D_p!8776 zY4j)t_-XJ-_u+q8LGN_wKucV+TyvJ<(lH|%Z^jM(Gh}&oulF`VnBd#%+xtd@Mnpiw z)O^!?C*Rr}Q+lmb-AvuAZ)kAn_0U9~Vu7aYq}+PJdckDDLX(TbV+RRzIr^iANyCam zsr?SR^+CLYs(p!_t;3y0!x~i0)rppxma_9@7>f*xs=^6G*SlZn&*+LKu6L_bH27My zFFL{%;l_z*>Z$G8$!N=nXpZ>rR1?CKIL%ze=fD>k^gfVX?UvenKJMj?c0|wb>7DCuTe%T@5O8Pd$>;&9 zps}EHyBuG)qqK8xCvo=Gpu($bh4%76U%UFDi&m>6+6=F6=yQY$m%0t&(POU#$fv*T!1P$eRxp{l%L1k=Hf75_vmtUcC`~oyeh#3$hb?Wx*oc#l5~)7!ewiZHqyx@j>rF{>Rl0$%+;p4SK*uIJ*? zarcazEXhJ8^QSJWc=^R=SNAT2Tur49?d0o32uhDAjU-J$Z;M~H z*fSP4fAZo-daGinaF2Y6#409EwBmWI8I##ufm-3k{73x0a++#wvav7Hp9knw3Ycf0 z5u1oL&qo8*9`OyNo(CyS(wDO!pM4siS5wqFavjbHH3m0#|@B5$*GvMs$pmn4|NCHd^u+N!-(8#Dq!@$>`^>gx%&XsR_nK1GZB zj$2l3!*gmQ+eDt2`yib+`eOJCvCs*!S!zfibsnh`DraX zo->CdYD#SO@hVTE-l&6KDK(4eZ~!`Ys22ce#8F4ly*kvKDbU2AB^nqgth)r_%V)m zY&|^H`uquROJQq1?m6ypY;xRqs9l>}xk1c6_PtYM;Ty2sM>$G)3K3#PPh!4}^Nni4 ztkWj0Zy7Q7UHL>p?S@D)DKXjp_p*0c$X`EnW#f=;;qWq(+RUS8Vbx2Ju`7@G5PZ_! zdii_#h6OhH$Bu4}&6a~&BNLnRPfm>RS6E&uXxN9|*oS?-H?t8lmVwwCeum0IMMO#+ z&AQ-Uqr=wSaSProe!I=A!AqkCnIvzL?6?i*fmx{2N5 zs>Ano_Fn$<`LVpL)9+iy+Z`dZA$+@U7FaA-!ug}b$X-YF2R2{Jjx)G2GRRbvx%(&X z@{;(of0N(c*xDR`CszT8js@U|e1;nUJd^=o(**#vTmU%zUO4v{0ze6Fn!yatddOt* zPpsUx1h$eetF!#9@<02Z0{>4GsQm?W&PJsPwzjZ2TmG~4Z)|_t4!}90iM}rE@l1FA zKz0p3BX!g45IM^K>q7{dWhI@e9xr>7I*v*ck^GoUE2G7T_(`tOP2f{OvH*%V%r85m z2w0Po0Eh)4P0e5M3h4Y}>hpDx>>ykjKJ4_f;7EOsQrjUa%xtgmv`OtJN7Od`G;fR# zUEqAPHzARK&(+X@kl9xB@HBCtZcT?ks*?`1SU}nkqPmgt#Or98A6TyNb0(U$HTgZ) z!lP**O~`v>xRa1Q@0Yeko=+=t=FA2|%PB_;103c%K^-~A&MLyY(K zSC5e*lzI+}CdVX+QJ3~LsY=N7IqOjg<5PlNryMZ=I8yOjqI^fFYl%@RKh`Z7C!fX1 zJb3=9y#N|XI-sR@r&&GH=F>Q7bnl6tKY`M^X*4uXIjcC>W{^($ghz(_;F8R%&dISS zH-?r0IWNlB^8UAKZ(;B7)sv9F^ZU?aiSW+4wer9Axr3J8wUO1xN94Ud_2Ne>n}a$S zvfZBF2l(*P1DDpQBjTwR-I5%Uk~F{5x0psmly8Q^feJCqrGQuyG`?kEgQr#^5d!Zn zI=X=*yP3m<4DvoSyF;My*31>(EuZK6p-$4d67Ej8?LTmQNrNjj`Qx&&wCMa%OQS;2 zchtM?b#L1vqbZSy!GfSS5tm&D+!G7J3AxStLY*P8W5ol?&!+tiBNFmD0#3L0vt%7{ zh&weu{b;7^`d%l7*L(A=Z^bwt)i&!N2;JG=jJjeIkm^O!a^k);7OGS_G9B!woUOC7 zJG!QYN(&Ee?S%3E@R>MzA%CKq>bahId%=T^*JH{WeW5vO#O0T#kW%8^;9JC+F_jv| zs*`3U=Dp80(@8cm%4t-3kCKKI`ta#V(LZ(_2|Z&bEmX|h#Px9rWY-xBB@3eU$8vF@ kp_ff*Qg=yzOEt(4P*obIqR|+{_3r?f7+C1n>bb=HA7dEVX8-^I literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_x.png b/src/assets/images/navigator/models/model_x.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0403737fe8bad143927c5f61a9d66a48ba47aa GIT binary patch literal 4103 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 Z1>46=DF)r|nEyKt3lm%8awCtY{|8PCT*CkW literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_y.png b/src/assets/images/navigator/models/model_y.png new file mode 100644 index 0000000000000000000000000000000000000000..430344cb2c901ab2fabf38a56687f6dfe13f7002 GIT binary patch literal 4245 zcmeHJ`8O2)_kIn7v5#H0w=E?5z8l8AHVj5m_EEBx-ip%L*GRHQmSo>Uwi>c!8A~C? zzJ^eiD9e1k|BmnHo^$VW&pqe<@I2?a=iJz97KZdRTr>axdSfF!s|yUfNGllmMXg>w zUAO>hKO_750MN4hmk^NkiW2}D%iBogwQHWf0lxP=ef{{2kw|{Od%hmG@45rBi$&lSRY@sNp6t0{K&Hs17E5eJFQdUJQ9+A})aq zB|#TQ^Nngnpdc~4Fea}5WHGqVYohIFZRosVQfs|pKc{|}3J;@8Gm=Ls;^<0s`Ir{M zd%JoTSELZ3G{SxW4y$()xPK-D0mmU48dnAJR38B3;TKvMz?)~|Wq6`VjGMZdHz2qO z2tFWL#T15P1&E+T-M0W?0Kw&Ch**O>C~z8Zby)`2B!QFY)3q-EmvfvM3;_-=1=t{Y z$$+25Jw^|Bs)LGQ+c*5zmW!7ml3#;mfI>03TsqE~e_{0dt99LPDoXMQC5K^6w zGB3%6DljhW4+4;v%yz+c|Lh@wp^89IN^E4fCAs;Lk|gNjvV6WeT<(VeU@;(M^jt!^ zo(rc=iSs@$xV!`Pyh)q);3(dsjuFuS@;0XJ_Wl0TMnC^~^VHPp%E~vBE?o!be!C~< z*v?ycyR*B0LNrc~4i`Qw35Uu#h8jYT7di(I%!+u1Urmxc#RJCK?kP< zo|317Vy1)k)JZ8qSGcQF7p_a=hU2;CbfqFpS1f9%hF9E4`YVE00gv*GO7UZm>a=;! zpy|i>*BO@EA=Va34b|y`u{aA%$3R@-FhYqij?Bnofes@mbG$JB=NML$plPzAk*rl5 zn%~BVcTe*knQHV6gO+6VJ|Gu%8r5bf`!ci+rTLYv0oh!l%oSl!Ks(|VAe)uMo|ipx z?;V$Bf>QqaNVzBDbdnabR|fx`lS;npu8dUAC*4{V3F;Krfm*=|y7C^b8rur;*4RVW z;o3kbbErO^S_vAc%kRvmV}vooSbQ@o;pY)nV49{@rY4U-b&3k6l$gE~X<_=)#pxm^ z8;voN5Sn04Vv&Xi#~{0ug_&cKa-z-8$&72?pvRcU490w{gf=8fvlWcEgi=0FyLr`7 zslJrpzuFhMlC$z@MSg{QMPSc?CdUQg|K`|wNzAJMj>)A}_^QGxrF({)j!j{f)oe*V znn^L~x^88Dqg6F3x`ajHL6+)JIJ>cFQDs5u7cNf@&!gMb-ve5hW>fhr(nsvZ1~v@8 zr++6u2&AKrVhit$!HQkV=A`1pUJB%_&M

N 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 Xk!GtIeuWX3RvA29{an^LB{Ts5AIV;W literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-shakesp.png b/src/assets/images/room-widgets/stickie-widget/stickie-shakesp.png new file mode 100644 index 0000000000000000000000000000000000000000..d5011c74b29bd0641a0c9897c207903d63a3491c GIT binary patch literal 5036 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-spritesheet.png b/src/assets/images/room-widgets/stickie-widget/stickie-spritesheet.png new file mode 100644 index 0000000000000000000000000000000000000000..02495714fc62ad63e0da7c100dc969321abdc86e GIT binary patch literal 5828 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 J);T3K0RV@$%B27R literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/thumbnail-widget/thumbnail-camera-spritesheet.png b/src/assets/images/room-widgets/thumbnail-widget/thumbnail-camera-spritesheet.png new file mode 100644 index 0000000000000000000000000000000000000000..63a9397f20e4cedf864e6111ce0bd850044caaba GIT binary patch literal 883 zcmeAS@N?(olHy`uVBq!ia0vp^EkJyLgAGU??LB=LNO2Z;L>4nJa0`Jj~)z5CGjwu3~&!|3L`3*X{5dqp$~ewyAF_-w~YBi2$S9>0Q1Y`rw`FhJ+3mYt`#1Emm*(1S@9pcB?R#5R{pLp@-=hDgOXk1s zF74j^tIa^KiD6+aN3wyNDJTIkwikl7b4s3}RGN0Ly|*?NH;J z5VMm>kiq5OBhd~v$0sw_IfU>iiSJ@!be^lx5W(im#?+=2=)yj6Aw!Z@*K+RbY5_9@ zE(@5nD;ag{G`!Qr+~FWG&!^*?noA~+WQT=>Pe+$SiblhZ>n$tzS~(e;IT=|6zT5mv zaWXMbJhaF_F~LAl?9P%Lr!AjndkG(EnNhp>&$3G`E55%xRM}B9`>Bn~;pd7)B0OqG zYBc7oEP1u-Z1XmU0}L(VuNfXbt#PP0s4sAji-99wim7G0D98gG5Z`Rr;lRMiB7h*6 zoVc4$i0xeW{}ww!6*MG4T3~ALuqEX$T%DQkbRxLz9seR1vkxV<--_3+4!$1s?8??x zxnTnBeDw=ny}fmhiSbB@K){yHL?Mo?l8`8Y*o|Z=G_oRO6tD$0)U9CkAlEWE{bylh z<&l^8^;?sH@rb|?PA-E-4i@#RB<__y73SJ{|KxsYKE88#M<@TX-Vm`yKp@VZw{7BY jW7X{{T$ef9oa-6y>7~B8=s#DM0SG)@{an^LB{Ts5nJ6~q literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/trophy-widget/trophy-spritesheet.png b/src/assets/images/room-widgets/trophy-widget/trophy-spritesheet.png new file mode 100644 index 0000000000000000000000000000000000000000..f9184cb57814534a20d5d669d292764fd173ff1d GIT binary patch literal 6595 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/wordquiz-widget/thumbs-down-small.png b/src/assets/images/room-widgets/wordquiz-widget/thumbs-down-small.png new file mode 100644 index 0000000000000000000000000000000000000000..78e51cfe5666ccc5fe46dcd7976f615d19eab56b GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4f5uPrNAr-gYPIcsBP~c$s?*H$9 zZdkj6i$Tur7G^ow5`Hd;Ee-EoTf{t%3P-w%zGj`@S-x*>Vfq>2*}a+NP7@R&Lzig! sFNyP+%$9WF=#@-=yCb|#^S2#h&~MqhAozyaL!ccDp00i_>zopr00L$)8~^|S literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/wordquiz-widget/thumbs-down.png b/src/assets/images/room-widgets/wordquiz-widget/thumbs-down.png new file mode 100644 index 0000000000000000000000000000000000000000..fd320c51d72f0debe17f9537d132ec4de35bf815 GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^@<6P_!3HFkdog(dsaj7L$B>F!Z?9bBYf#{5O}y9n zz20n9x0&0EGbb0S{y*j`US~PiMJ+NW|82kxr`Fv+B;%QntL{+KYvB5RZqC;?8;co3 zcluQ5JW=caG8r!IE7jJKC2Yu=J)QJ hqVly?g7yYi8Nao2ZhvIZ@C@h>22WQ%mvv4FO#s~;NumG% literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/wordquiz-widget/thumbs-up-small.png b/src/assets/images/room-widgets/wordquiz-widget/thumbs-up-small.png new file mode 100644 index 0000000000000000000000000000000000000000..b93111f0cc078bd21ac90ce83f59ce4663474d53 GIT binary patch literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fex5FlAr-fh6BLC0v@@|~G0&3r zDLwo$$zUt5x!UOu|5KP|N%~wU@Zvp|-f74ad^KU#&Wab3Hd4o&EqXL{^;hH>T$#?2 iEs<F!Z>KwQF(~pde^&qh zf7`pzl&z;$ a8+P{toZC}5t{w$CfWgz%&t;ucLK6V707rlT literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/youtube-widget/next.png b/src/assets/images/room-widgets/youtube-widget/next.png new file mode 100644 index 0000000000000000000000000000000000000000..a02e164ba6f2f67e15c80ea935f447722be8e217 GIT binary patch literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^qChOb!3HFM-ZD`IQrVs^jv*Ddk`odVe)P9VUbvCc z_`iEzMWf-Sg&u$YhnR}^{;?0`RXtv?h*$M!!6aVQV+EUdkEUB3ZA&u#Q~x-Q*}%aqjdAIwIv2^hiBLBl4!m^o1g#8dvmwF_@^$Zh9E?xd~`3 NgQu&X%Q~loCIF11JCFbX literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/youtube-widget/prev.png b/src/assets/images/room-widgets/youtube-widget/prev.png new file mode 100644 index 0000000000000000000000000000000000000000..d48b658ebae5457b7933bdda12413135564ea62d GIT binary patch literal 249 zcmeAS@N?(olHy`uVBq!ia0vp^qChOb!3HFM-ZD`IQjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`ISV`@iy0XB4ude`@%$AjK*2sw7sn6_|FxG+^EN1mxCCxcsxC+mJ{I=o zu$;~zy9thM0!}BY58VC0IZxu~tbZ{wi3M^|k5+It?7sIm{^*8h^VobNIH!3A7#@pS zA(d>pMLOBis>g7_WrJz&Zd9(92$8+rtSC_VM%xNb!1@J*w6hZk(Ggg$;Z>hF{Fa=?cG?vR{;`k54Tx~uagj% z+*oj4WrnbL&#_zmH@sf>-zjvopY&B_o{aXZQ*IwkXGMhb-}2kKZri%o&;B{t?n(K( z!`=Cfu2Evbd$C&~`3vS*A3d8EELgVt?u)2$tI6AKW%z=p-#$5c3Dff$7rs{i7*) z(^u-xwDSwd*H^miK?%cKQn)ADNQh3MrzspnW_Qr*C_kQ1f|GoQl z<)V^fwHhD4+&}PQ_ui@7zU*FlJ#+VuW&3wub~|Q&`0qv8XV=fyU;lmPr)Xhem;A>w h8ZY$9be&_~OCMAT6X`lCRS8UI44$rjF6*2UngEKP)aP};9wU8$gdB;^Uld4*mQ#>+Vq zgulpuN+};5<2aHb^a+7A^l9q4j)vo*G{M`pQEJZfyv}7=Qrotw*}Ck(B1nl4f|g~` zw`4JYyE->bL$R@~l7j!t6??(4v><4--A#@Z;QyfuGef+gRONKh9FdduRfGFUZ@Fv3YWL*BGlmvJr1?K TLp@I}00000NkvXXu0mjfrPG%b literal 0 HcmV?d00001 diff --git a/src/assets/images/toolbar/arrow.png b/src/assets/images/toolbar/arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..bf04ea0ecf9d491eb96f2433e37502b673bf7856 GIT binary patch literal 14533 zcmeI3O>g5w7{>=8(2~_kNE}#kvbQb&=}X{B(K5ZxEae*B8iUc@@iaA+G}q~O)nsl zUepy^)hg0*t*Gf*rK-Ipl~k>a)iTz~idHhznxW}ZcF2tai_F038QZN}SvmHrE+0ja zZ(uwcjf$gk(F+c-R;$&pTEeB0!W2q);zrh3al;#FlJh(*650XvBkH+QoY(4l!>BIH zaiRRk?rY%Zh1@V>$0*{l=&OQDx=qFusY)txOK-?%~K5eegKk_0kJn|Nl z%r7r2%*LeM&TG%@?_e;ubQrDOVF)s$bDd%5#3y*0gx)Z)$=V%uD>vrdeMEbc!I{bw zUs>39GNub`@s-q;OoeoWOm?qEGay#v1s%`pH_}g==))ZA&wB=4oH7Zr3Qhrlqb5aFKQsqpJw&&2^$($T|+0ZL62T;!DU`xlb zBI`e8fm)Eqv5lS=46LX@2i76MzI$lmNh46^6Uu0MeJ^0$B8{?%=QRUYGRce$H;gRT zCL65=Yba9c7|TQ}b$d%?#qMeqrPQscilugYO4+eVx2jhvRozY?w%gEtA$7~Mhw(zm zQaewW8rUzS{ve=ile79j6Zif}OUy}gq0~AJ+0;A%Q!&U+B|hJMyg{ zB=KUa%jc^t-@o$hDfT3b*0Ah&BWu|4S|E;zr&=f7%AJ{w{@(#i^8@?h8lxlPUbdjp zREcVFlF;i#BP$@Qhip!8mU)_R+A*8i#POcrr_*DQwoVVt`DDE~lxDn@8EhJ`E?EhF z&BPblCY4kDUOb%rVWM9ysqDa7{GD0hF>2DTe@L9A(t}CZ2Mf_{=`>>KG2@|(t!eh;mJ6rt?U7T zJ)7YC;beM<^A+%vgUJm&<-{e z3)0}m0v8t%6bM|91~(SCxQL)Y;DR)`vB1Se1O);Yq`{2^E-oS{5V#->ZY*$d5kY~# z1!-_&fs2a>3Ir}lgBuH6TtrYHa6uZ}Sm5F!f&zgH(%{Ad7Z(u}2wac`Hx{_Kh@e2= zf;70Xz{N!b1p*hO!HoqjE+QxpxF8K~EO2oVL4m*pX>enKi;D;f1TIK}8w*@qL{K1b zK^ojx;Nl{J0)Y$C;KrBYDokGYBQAT#Z^T~a`{~z*E9^x;$zI=XBXoKbq0c@?=-)GT z{sW<*iqM}22pL}^^s;yVu(JRF literal 0 HcmV?d00001 diff --git a/src/assets/images/toolbar/friend-search.png b/src/assets/images/toolbar/friend-search.png new file mode 100644 index 0000000000000000000000000000000000000000..7156c4fd2ef8a18ba27ed00b62cd7aaa474a74cc GIT binary patch literal 2213 zcmcImc~I0=9A4LBRK#i(wMaF_qgu@-+3XqJ3bG2Qs}v}%N|kK#7E*RM>n6Ahb*G+F zozV`8rJ!hS6&ZC<8MSpp>s3#%*4v7$SQVsr;a#b=FR*N#jyn9)%)I2iyzhO#?|0-k zSrk7ZCbVOCM}3Gd+$*PdOF`4&a0^5eg%Nrrha*B#7z4Yy1dq2fcCA(H_7^f+tB3 z9LS}b&&OOzNF3*yWs6pB*uJVzUV(EzlR4b$okET%DXBxVGx2D2IL zjD{h!M!?t`b;0vyfz6ZyD4%aCTuxviM=(aTR?9FHY0zVofiq$h(AY5p!4eqYILHSy zECck-Y!f{^{B{_}%dE004l?RAq+P?>IZRJd9HwD`0W$zQz!iiLPwzp$I^De z<78l^@J=QT;BHqMiu|i4T5t#+i@?E{Q*u2+cIZVcFT&(y1-_6(Fe5N?@JM6X(F`kB zy9s6GGy@I|yyjmKy_V^0qV=W&Ncx84H^D@~E_oRbh)RQzZb5nBuahq_Gv6$}WvTyP z#Ro#mrZcWI0NW>y%I@HDqiISE{@$O~};b+D{kv8-BRUkoHfPsuPNw zIZGxUpHyDXZi??$sXe^;){-atrxY%1(=B7=s^bx}t53#G7j_@jFN?JI!S>~knU*@> zd?#z^wDld1{=V*zyB>?qt`1#Zdi7Gwz(?m66&*F4D2&YPa!{KbQ+L1YOy#rIYyF+C z-F;yC?$^II+{LSMucF(c*5_ZI5_e{XvRhTe-^Xs|ZAz;?nnRThKUlPskDg7{-LNW$ z<`hs5?mZq_G%j`Imhy<_fR4$j_$?)ox-xgFzyBV}H+`S!=G0nM1^7PE-zTrokUVE^ zkLp$S6YtNibm$^>UGBUd;edE0&t_Oi$AF@fNb{#;lsD zLfGZbz0a?$y?A_0L3qh4=emWCryOCLFKn)7DZMHOjH=eP;>lAXOL7aItTql> zc%|6i$3(Avai|1rOAW8=_)Be%@XiNaI)}B&b?D-y0XnVsR&Q=g!Tgo6`uWFB$J7|+ z+`mNO&!){<({snXb=n)Mfm@!;W5aSjTxz?Q5cy}xu)bu>Th@0=Lsyj#zbidHy|W>6 zrcKzs{lVA5H%IEb>@K;}?ns+1ZHiZ8_2^6hk%awm5 N;zmy}ZyuF8_aErWD;59% literal 0 HcmV?d00001 diff --git a/src/assets/images/toolbar/icons/buildersclub.png b/src/assets/images/toolbar/icons/buildersclub.png new file mode 100644 index 0000000000000000000000000000000000000000..bbf6d681267e9fe2ed4a125dbd558233cbb6ea22 GIT binary patch literal 576 zcmV-G0>Ax%@?n(5=LnY#13`g3nb&svJ90Mhib-p|3+@iB2bdBI#Yh7eB zhCr49CrX8?M93A;>6aO(NdSaCUhP;*6FXuCJbP;}PX>LHqBV-u%Rn{&(yt|S@O8*G zK&iO)UIr|QB3MCdPeV4al>ymGIS=~vcoFws?{|g?RN?8fPA^AOdf1=P$#f$GOGuGs z0SS`F^1YzJ2o^xJiezvtp`Qo~YzefAm~d;>C2&cgBGS2Vx%M@%Cm;(`iX;NodIOHm z+)2Z%^BR70yhZeRUKB_qEI8k+L7tcvVEF0YdXfoOK*N#xR)NdIE`%k(F~6Rb1;Sgc z|Ez*givYm%JPX15fHRa^ge9r065-&GAw~OLk0HWdTGoh z2px^@Jt?45EunD?1()}sza>!o`QV!^u$(c%gKH5_!2Ux1xD8=tfUh$Ym5xI5z@dLX z-d{dg$rF*8@&p_(4gO8@L>Mm9zf5pdL}p@4a9c)J04|EG66`(3&D#%5=lPeguvU5i O0000cL_!rjfMv5GDI^;=3j{=qIO<@b zD2K{nhk{TM0g-y3cq^AsWDsH*uBuEGp&W`@!O{(a?Q|TcJG1-V`{(=q|ND5@-o*8j3~JnIH(#87w-BMI{haZK4{7bX2w0VZnnC z(ZZNggDX)rVDJb

en;g;4r71(il3dFNQIoi7woGP(}Z(3vy_U8OSQHHX&X!N@-{ z-iy|TBx(?PFrr1{Fqr6v++l%C)b7K93_wB+Ux>nSDnyNog`O0`LX#KA3mKBMK|>KU;SP%5kq9yzMEd+@NWJQH8J!U zG2P^^f}RkUhA9^MhUj`)!+g~b8B2dQdTF$?@gtLAyB9qPBiy)t*-!c_C3??WV^|5 z-!RIuBCGTj<{Yc#V*?R8Y?hn#)Xk7?k{rkrmf7CL#?odZFwLT4z;3pL7Pv&3w(GW@ zYTUi;;(o^XM51%x)&B1~6`u_3DYrxJq562T{51DbQ?t|VyRx;h@b&Rmpnaxq@^;%) z&@>>mWbmp-%o{pU}99tZc?!M*#+{99Hg zQqcYP(d+HG3Z%NWE8M4B>@!(Os<@n@xUKZ>uutK&^wl45*=w<+iFE?JHYKzU5LZCs zUL`lm?K4jGG$gm$8QEq$gcA#<@|q|O^{2@F!G}rJHQsTfZyrBrrc|eGXzCEfX4Bfc z4=^X2PL`fmEOM#oS@E<>oIVkns<#%@WS|iYb$I9eJ-pD$ch2u{3>sxm;_~nm?Aq;hPjTT@?T72&Gv$a0< z_lG>bJ_p>9Gjh9={ZT>n)Nr=ct?_7{ahBffU}@!8C+_U#;&MV-CF>m9@>z;tOG{jP z?UV2UO~!*C#s!HzoW{bDnc9xp{((Nr%o)IDI#+qVWoPx^VB`hWRE=d${%&4;$;*H| zlY@s4#8y) V;qoj=JZAW>i2VYERjVUY{|16+xRn3^ literal 0 HcmV?d00001 diff --git a/src/assets/images/toolbar/icons/catalog.png b/src/assets/images/toolbar/icons/catalog.png new file mode 100644 index 0000000000000000000000000000000000000000..f68092171113671bf64cb13c059b92f59809f644 GIT binary patch literal 1202 zcmV;j1Wo&iP)2}$f`ONoy-^^?s=V*T*<5>JL%*XrZIsP{o1ALM& z%QsGA48y1Vp?Zgc8Z!C>vpmxU#%fcn`cSO+mWoh!1du-jyRHL40`CPw(0t^8pXd$B zfEmHG4ELIUerDP&La7!qT&WrgC~ z1rI!#N(L$jh;wT@LN%%0**7~K$AHiGM|Cyz149EBG1Bh5ZpTpQuTv*aT2Q14f%p>+ z2c?m|cZ4F(P(=|GsnrEaD--pO8Pl!@meK2uh9NIC3Ia5k6wyhDR(ZEKH3%cCH1VL^ zF?offAO?#95)d>d*wh51o>g#U{$7>{S>B=U*sBEKp@GdbX%(o$L?G%lm1z+`JNY_=9f3sU+jdTpx2%SbKk5v&@rmsI1iv5O36ZXaCc zjl@B?Wcu(mwmCUQ`pyDXyltsDVB%OhVzFy`bHIv zVQ6??&cf8)9AsXuKxB;GpwhIj84#j!Jf5`|RnCtX7~7?d3f|aSfX(fv0_t8KvM&cy zN_eX#2B(=N?_{m5rC7D$=$e41jWj8kt*_7cx`I$iRBC2^p>Iz`)DpAFA{foGvYIoL zD>Aqzw;}pY_X%VjgFZLpCk3^o^v%9r9VmUqnJLibyp1!YPAhA9s2XtO8>N~Fu@wPI z-p#_y<0mk?QXV*c43#pxHT$}Zu?AC^N*Y9knF@kh|B&N!eW}Uka1t~bQd?D?uz)}K z5t;TuQsd#PGTcJ7$P?Fa5Qf76%~U8*Qu4j@5T#vJoIZ8fMM;5|;0dq5d;g3ggrSF$%|3ohk~ z10tsONwE*@w;VKjZP#leYnk zT}`#?5PFS`Z=&rUg2krR!n=TDzz(r(2p6s-V^b=wpB5R6;yC?8*>Ph30PFMYiDeL! Q7ytkO07*qoM6N<$g2VwgQ2+n{ literal 0 HcmV?d00001 diff --git a/src/assets/images/toolbar/icons/friend_all.png b/src/assets/images/toolbar/icons/friend_all.png new file mode 100644 index 0000000000000000000000000000000000000000..b2ca0d7d52b32aa4b6923f119618a8876862790a GIT binary patch literal 648 zcmV;30(bq1P)>-IhnZOAUGHo7Y7z0Vq%<~92^J(Mkn3W#rOhZVt55t zU!!^oUpe>mw3izYZ}O!rJ?;1FX?riZ+}}TjY37eP+FCTvC+oKS4+wKrDOz(XX9?ia zzPUW-lm);uoJ@wt3(^7*nh;v&Vn-vnF|OzA(_H} zuavJGfbO@l58(!q0fMCfp`{&TkO|aPh&}&6dK1EoL!qy??F)Sp0&U8ci_tQ0bFh|> zoRI3brGYd6^kEDK!4)*0Z7c0So5oJTgWw=A&>&#iNu`P(51!0!ddEMWFO8GE201z;-d&*3C+m3m;Q#0Vq-yRZ~u$7D2wSx#{KtfR4 zGY7yB!dp3+hX6Q)ROfU)dKw3BlX2}q(iinqT@(<}fa z`W+Ke3Binu_ai7Qz7N1jtkXhHBRG=A*r08r8i1r__D{tAij4=5M`80BBB9 iZ)i?s^lLan+2;pJ`lO2}U};GJ0000>NW%u_|*qM`#)a%v>4T-9 z9*7qC^8zEWXh7D>Gyz$hv=}bFX2_Cx1u|C6LQ9<& zCC0K)@G4`B3GBsim91k#TC1VvWQJ>R{cycKhBi!}`uPk#8qPBtWQxfxNgB^1m;k4E zCZk*KI59_toY}cB+_Pdu2ykLOpG%+ui8(V5Z@(;E1pB$yQ*`xsjUb7=xu^Qwg4_Cf z^-kbj1}{M$s_w!(?RMDbuH?kA3j97Z51KVoKy9;PICQxk3MZ$=5%khc!JZ3yl)V_Q ztT(O8vBsB~t-9{A3aCUjpD;H&8?r}f@rTM_78s1D$^fpHbvQT~n+#lJW{F^k0b7aS zFh}+%d+O29)0$!;wC15~y+v?4Y*>%Rb6W<@)YLkl;haAzQ_sUR!&X7#DCVZmg|SV- zWo{FY7HeThv>@NeyepYs)kt0Gb1wm`e|Br$ELe@LLUy{jhQq^dHf&hnZpGNzBvxzA z+Ap)$Ox#T*^lq|$dbPsWo@vp?>;t{19VD%hJ4?Rt^$8i}QuJjhKz>`6O)r(v1GbLX zhK0*R>GVyk6?;=lJOB9>dKmWq0k#Ps2u?jK@!Q|sqkMY^@1F**tn~|YTxGIbpA&-s O0000W6W0H-@A^~*- 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 VDRlbp)(tAFJYD@<);T3K0RULZ(Cq*K literal 0 HcmV?d00001 diff --git a/src/assets/images/toolbar/icons/house.png b/src/assets/images/toolbar/icons/house.png new file mode 100644 index 0000000000000000000000000000000000000000..f2c8746ab2d045bcae2b99e07df7e534ca299ae7 GIT binary patch literal 399 zcmV;A0dW3_P)PFkQj_bQ7!pmCio@=cniW4^UL4yCcAsB0B)d5#(ah900N$6bi^8u&6r`>A}5bOYu#UKZO_#qC$a11gOgo)+DFe#3JrvY+(j?9OJ0x2N@4|Z~V zj4K342??tB9GQBjLjD?IcylO`WGAiS4ApfID)VP_FRH!1kyw~0GE28 z13;lbj3ZDC1ZjX_VjO@|J=6i%9D&yX=sqG8$Am%vnE!i)U49N6gZNZ+0CH(YO-!r< t=;jE5O*2|Mf~W>6EgeC0Go8j53;@05@P!IqvTgtX002ovPDHLkV1g{Rnh5{^ literal 0 HcmV?d00001 diff --git a/src/assets/images/toolbar/icons/inventory.png b/src/assets/images/toolbar/icons/inventory.png new file mode 100644 index 0000000000000000000000000000000000000000..d848586ae31f18b3a33f6badb4de59a0b0e73c09 GIT binary patch literal 1973 zcmV;m2TJ&fP)Mdq%#-IFu1lap5QG57Sn*ZzwJHlZi4hK*$CiODtOAb`upUFG)MuHt=o&9DWghSGeEbQBRF0^6`fWcx+& ziwYigl@=ZV>@Dlj5cGuyI%c+MyWIc`Ai(U-D|ni_2b9(0H>(p2jf9Yd53G^%EW*cD zarL;Tv{(V&xtYs)ul@zW-4@6U!Dh$IYubP90mBLa08ITcz4o;UQyNUk>PLxSAFw+# z9t?$v_`nR-7ll_(D>U9Z91sOYaoNIJj<=bmc!FTZICe5c28GJLC4%cIQFm=pVtqms zrUI^|sk1 zwH4H?KMVJLaE6Y#t^in7nytcZE&M3jyuW6)9;`F?`=h|gD%fY>h{6t7vR{V6u~sfR z23>z10Su!VJQ$SrRU?H<T+!{WPcpbFuE5p%R$YSKdbnY#w8hxH;X5Xv z&~Faz5|KVkhK9nrx&d}OTVz-$%|%GTpc?=kC%afsDnh>A0jIdF?b-zepn%6Q!dDou zRZ@4$4+1X%c;t@rbLvwb_7#R=LFa%eb@q!z2p-@710-N^eu{#DGX>AX;Yz11O_yZu z4)nSYD4;BW6`Hp+Cct+?U~X#-3kUaMw{VDJ0l}iJ1)YPu4`_e_GD!EEH7`)?T>HUV%jeAg{7O+`2VsLa6H*5p+WTBtGu z!y0S}Dl(i0?9N1)&zGS1rGXH5EEWMqF+sTl3wdDsu7)PU`+vMOrJ*}cVYhG`Rdo6- zvE+G{)nlms&?@Nx9)qe{ct|8vIL6<}K5(@x1%-IWaH_UIA_;T`fZnt5S>T9=ttFP@ z(vJZORhQ4UaVukm2^5_K(Cp#tctqhyza>b(0J>@m4aFtO?n{x81r!$7`3g5#!WCUM z6BeDPn6$Md+a$O&C~eIGvR1)126u6yztiT$j!VPB$1;#xJt&4;v5(0;o=)REPzc}z$QDkZE zI^P<3h?~TQdV9x$Ve*`J&cTl7=^?G0{1ol`if8Qg3S6~|1kcy-ESc{H^K@Nku*A~i zIkUJh!`bvu1O)--39g2j!>gh4SDgAKn`n={|96^eu`Kpo`M6C3vibw0WtU{KJ_ z2T(fh;`tIT(Xajm2shzjT~=}SBs|KA$gB4i{nC6sIObIFuJZ}@{o*kcJr?-V0>G_8 z7eA}$@#d?DC$(lYkrV$F6B7gc1SYl~pFMHVZ~gyoUp?PH3m)g34)!Ld00000NkvXX Hu0mjfpuwsH literal 0 HcmV?d00001 diff --git a/src/assets/images/toolbar/icons/joinroom.png b/src/assets/images/toolbar/icons/joinroom.png new file mode 100644 index 0000000000000000000000000000000000000000..894ee78ff75fafb47bff9aa359b4d2e170897c06 GIT binary patch literal 1084 zcmaJ=K}Zx)7#{Ibq8-8nt#BFU2@iCcg2McInF%YNoU?P^VS_b z6i6W@b_qlx1VM-{cI;4LL;~1+8)fj_rLkR@BjYy{<#;6 zc9fM=l@J6`7U>k?cs}DiCyMcVcDZLBPn9T~LJ2s4vXTjiHU;(rGNMU?APyvD^yV^X zCWzt-YBGgVVizw%%`G_?x2+kNO%Tm3wjs$w0FnJ*P}Tj^j~9y+sVaV|mlZvt5dvwo zbIb&Zv1n2r87gKuYcWhehW8`vpnir931sM&~dYyxT_}~pLc$*v$3vZ z`18x6@sHxCokuETvQJun@NBWp cwYOE#N8CME5?FcuVH+DDBH^g85bPWO10OhQfdBvi literal 0 HcmV?d00001 diff --git a/src/assets/images/toolbar/icons/me-menu/achievements.png b/src/assets/images/toolbar/icons/me-menu/achievements.png new file mode 100644 index 0000000000000000000000000000000000000000..575464d6179161e17ea2322fbc271bb17b9e07bd GIT binary patch literal 2306 zcmcIleQXnD9KPusV=}CQW2n*NoP-6gcm23)d!cM&oveh8x>ek8#$NBqL-@Swd;4yUOMEl;Nca zGG~zUdQVDfgADHSdXJr$TbLt*bIq#X|BqSPfeMdcewGGB>vQ%E9Ajs|0v^xWNwKE?Iyqnq8UW1U2e2glmSZVX`C~d zt*FJOH_&F%M9)Kwq`^jzR>EMyNdrUD3}r!67Y41#yq~FbJe;xxzwB7Os!9w&gu`Kd z*rXTbI>KPHK@KBfG~y6}E76e3M(~jG;5dT=D4Z-vsvw3?jgj?dEcWlhjZO7FiCE1swXh)SS{jWP$ynm|Z>aNHQr zrT0pWa$wX`o+Cg21R+d;!VGCsQoX2(O1=0Wex#Sv-bhTT3|Li^J)#&GPn3I{1vNsi z(K#-b6G9qqp{6Od1vpq0*fAq%G{B7EM%qJK7^9gn(KtylBsm6k!Ajs+m7N$^{ zD&v0@p9(Ek&xYy%?5+f+xkG4uXe=?rKXW#^c0pw4bvSMex!Ri2tjHYcIZKm z%?*E@&pRC@p7Nt7Uhb|KAaxtI&znM`r;oCWTH5Opvve3csVw4cUxQa zrq8E7G4+k}`@fp`|wM!$;dy)FZU*FTViQCqaJl|6_oUyh4&a2X@ zHxs`cELi)=sV%WDKioHiIZ7@qjmdX%-8Iz3k?FOw z*Ys_5PLEGsQgZ!=Z`ik{Nf(d)HnQvN#mptYd1KYfh8nJQ&wKp)#ihv36TfdOvcJ+4 zUChpZZsXl&udI&O?EG|6-l5AYoV|{8PZSrGJ z#TLHj?7bSBet+H{?!xu^8?Jn8INcv=*fD*eYVXX-DN7AgUo2W(qJ#%6QtX@I+GK=D zHrLJ=QCg4h|Dthk@WY?DxG|%JYi{{@!&a$g75>OjjdWnc@aBqS`|<+ISI-P|_y>0F z%6$6TA^oRl?T^ml@jA;Y9J?2KTmAw!-a94$ literal 0 HcmV?d00001 diff --git a/src/assets/images/toolbar/icons/me-menu/clothing.png b/src/assets/images/toolbar/icons/me-menu/clothing.png new file mode 100644 index 0000000000000000000000000000000000000000..bfacabd844f94288df500e1b6ab84ae5e7c5e7fa GIT binary patch literal 2255 zcmcIleQXnD9KP`}w(*e$6WCDUb|4wD>wUDn?M@0C>#)NWMk%r(hFf2mnbro_K%m`-Fxr* zywC6XeZ0S0Q844xp-H2X5Cj>TXSX@w`+0bePKbxk%PHVGg2bgtIXMNh9R;pjJO@G2 zP6}@K>3~+Mg5hrWkrNXqB&i|i^5s=dcXLZiYs(4rxch7Onwt6ZJCY3@Um^n{e?{tt z)K#ut+qsY_}X-?nBk3%I54#JA5_vinZuh1{%}$VDaV=QFLo@$tJ^LsP=?H`mOIYh2&T zHg9T7Gg-ITT934}WVKQ+O_?w!bzkGYahA^tTTKMpT!%KNHH@Fe~k z4Kr=a>rZ;!21amS=p2MZ!j7Ak5AIdNm?Z}6ML`4^{G5KrAscFw5oF*mlB-ZFbmVh_ z;y3c5;sM66KLF7PGHFUUzzYk3hI&A;BwMjN2bwWd60O)=i-U9oazKe>uTa74iWx4U zVxho_*p$iWq%a2w{6OQ;u-_*Kxv&-M=H=j6pC&N08=@_=V%fSuw9rw2<|ryaEk*_x zD03#7!5S&XOq!TWsWcDbuVNRNCW$KMz6iRyhKanKbB0Ky4^N|`Ev+;SjmV|$H> zLTqoKO!akpDhdSf0Y8KVp)e|DDo~HY)~286`>N7+|6^8P%(EUZ0}J0oGZ-ogoL5o(JggMS&ldwC zAQxlkziM(6pQ1Vx5ysr~_&Om~OS`%q6}i2vrSd+o8F)eDD>P#?xRF#L%1a3Di!6?SN2 zt`qk6xHs}_*)IFx%jaK_+p-Mt9o9r3S1)^cI+%6lRKdRdqlxLx@3vj-_XDzGRn9e( zZCgf-zVQ?94V{bF2izPP|Mvagzc@bJQ?+^Bv5B5Z;B3Xg3x^};Z~XayPPw-r0l&2C z`r4KU2mYRz-;};y361@5Zofk>dNL2a3mU5UJ%^@cZzngP7e`R`%~#{fgu;}Q?b{^J zrKY^$RfdXBKj~jMZQ08Dww<>g7{;5*x3?pEQ;h>o71obfn!F_xZ`qM>x8@ouCyrTI zGk8?xjl)Gr{Q8e~-yJvd;G%P%ud41k^WLzdRKIX7dNi@6u>oywpLgJ^Yx!-tuXWzR z?fD@pzq34$vUkaz9ixQGJM-HzPi@>s4Zb&ZEp_|HF^9#)k5(4l_;t{kbKmZ28ZvT6 z7ukjW)-mTk`qoxU&B1MZJ7EeWyUN_p8=@l^6r`zZv{t&-}D-K_6NqfxMu z%F<3kp^#o~MQqU&pt4n(BpBQYuOO!0o|$GSHE(nwR+<3GE2u+cx3{CXg$!5mF2Vh? z*cjRjuL6@T$Z6J`_SKe02~;~3xSgij+8l+Pv2MbI1ZfAA5~#L_*!Ij$0u%WvmxcYI z3o7(mI~J3!7Q^QAW~cuFxU0F_)@Iw=;&Hd}6#CuAK$G5URC0S$7*zHLIz6+rRRP8d z6(%xU6G#eqLfa@%e>w1Z{o_BoE`gfzKrHng)UBg1CKI1|+)Sntfikt#a=7`wtI|oE rK^!;0HU;+jlbR@748r~LY0&%vC+1Ek*ngK*00000NkvXXu0mjf&T2Fj literal 0 HcmV?d00001 diff --git a/src/assets/images/toolbar/icons/me-menu/forums.png b/src/assets/images/toolbar/icons/me-menu/forums.png new file mode 100644 index 0000000000000000000000000000000000000000..e22426e6d67447fb698f844fb454b2ac6fb29024 GIT binary patch literal 416 zcmV;R0bl-!P)-t%kH38_1VPzyPb!m7_Q!m2xC|=D$sxqR5V|kNOpx<`06Z(mXX~98RRF>;WE#y^ z>~|cWD?N<6R3pF;ssVVN0%*_YgDK0>T93zE2pS1sE4;d`0n#*SEq0^rCJ(NsQgB_2 zQL&8x5CAqO?~k!Wa!SR2axC1NLnr|tFp46w*8xmlHzpZ?@BMrSGy<@4ps-#5NYjAh z`*VQLKUxR9J0Ku10P*zB9Ka<9w&R_SO$CZ`;()5;C~1;zjk3$xQ{$dllbqa0`|O>}~q0q~ouo=i?ynRiKI z{Fk#efUujIf}$6WXyU8@sFw~b0icP47u^Z~Ulc<&c@22|IQ;_c=nI{BT3d<$0000< KMNUMnLSTY(XQ?Rw literal 0 HcmV?d00001 diff --git a/src/assets/images/toolbar/icons/me-menu/helper-tool.png b/src/assets/images/toolbar/icons/me-menu/helper-tool.png new file mode 100644 index 0000000000000000000000000000000000000000..e324611fca78d810deffe9c00082e8a0ebdcdf37 GIT binary patch literal 309 zcmV-50m}Y~P)9`S-s*y2< zV1BF#LHBhf=UfEvWAD>|2)p&-0PssfWQ*3!&yj-2g6=xcnY4nEp0|%)bjbw2bR3)p z5Pz-x3JXH;OC3W9E+`GS(z`?;hCGLma$M=0?f}SrUITb7z;ir+gOce3TQMMB2U1A6*hv<>piw}ZLeH!TRRat z7y*Qs4JR6%p)#XGBEq6PBy$}Q9sx2z88he{U`!AU4|xd^{N2`7Vu0))FS)zl{eGX{ z=ll6Se&2Rue(t>Fq%lbl1SRKW+Y7+^CH2;7z%vcswH<;ICWx7t`3oKS&MY(&f>PU9 zm+P!wSt0W;xLn6iPoI_~R}^g6u(`n15{L2Mt6_jZ757(+@-%`)5B+a5vQbaEXi5YBI_TLOBWf^(&HU&@FYz!k5sV zO>@@NS6#DZn3mJkZ)%R@uGxJ(+>w=$2mL5z{QiBbXNqRGRkv5W;k7MG5}Mv?wYEf> zQVq5Sd+YING^3T6oIdT<3HzJ&Ponk}wi+;NOC#Kpx@QtKpmF4-S$LQBZnAd8xiTSQ zg4U&1-?8e(?%M1w*15GcOP4q`n-Zt6hgMdPE_x>=WyVk($InkVQm}}`Jr~_BU7mfz zjyA!iGqJ9Yivyuh&~b%Yf4c_6Y>+>@C;&l&hpTr2^g(?J1P%B@bQUUwjs-L;dG!n@ zxp{ri>j!8EnwcK-Gi)iZz;3=+^x2T>2U`$WN}xZw52X?=lM zm_Wd=AmhgfJ+8Xc3v@W15A}NE=zx+{4y?rVK9xA&4EcGifDcGzGRtR`^FC!_uQ85| z@Aa3-o^DS$7UMm<7r+8Q7!fz+FOd`}P$E6UkN9%j8$X>X^Nb?NPD%3gCMv&|1tx*l zaH@l0MW4z$P1O|J!rK{zw;?1>5+GwJX>#IbnxtrgL~)A7@gArHR078+Oy9r~Ckmmy zKphS`#}`l-AIs<1Z3qyh7e$UX3ao)MSq!M#Od3%mK?x{B@i-V_UlAL5M7X;u zX@*q~gAHNTD&RRJHZ49SxP5b@hqj`G2c*xbjUHG)5|j!?=4Tg!MD(HhuxH5+Fy+q| z-?z&6U&Y5l%a$;{Vjgr?3{l;|)IQXc80^0}>t1`JXZ6BCBUFd+4h%lxJ)HLeY8iBB z^Z11)!B6M<9Q$l%_Oa%zw%kNQw{GI3C1a8-c74Xcd67>WB5mW4Bz<&PW5IWa#zp2_ z(u@t0+QXL6$v>}kq_MU3%K7a@4?hfDZYi2Cwm1BecQ)zH^>wShczfpps5j$|FZaMKcgIsf+F{kTC$IX|MTeKb4s#c*=<>!Sm@ zMQLld?fR`KJj#{)RkWEJx|SK%SZ`-C9G{3pCcnX z5|wi)(4AGjhlTv3s(s0|n?qA~R;|A|KeA_WTH=jxY4q-uU$qhXz|pPc^~c_Y@B=e$ zjKIqiUr8`dJ-@n>LdtRJ`@By5 PdjsWU<=S`6ajp3WsjVn1 literal 0 HcmV?d00001 diff --git a/src/assets/images/toolbar/icons/me-menu/profile.png b/src/assets/images/toolbar/icons/me-menu/profile.png new file mode 100644 index 0000000000000000000000000000000000000000..04964bfe64038532a534b1e5d1b31ff2d4f9135c GIT binary patch literal 468 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/toolbar/icons/message.png b/src/assets/images/toolbar/icons/message.png new file mode 100644 index 0000000000000000000000000000000000000000..c12d5bb4a4b0975f5507b76e4f10c96b8aab2a1b GIT binary patch literal 1099 zcmaJ=K}Zx)7@pEX1VciKy!0BQwA!7ST~~JoH_@G4U31rEU2qjDXJ_8JgU-Bh=IO4U zERpC?DHIe`bnxP#pdu-xgf0<<5POM85W)l@>LMOGY;RY0?a($b^WOiz@B9Amz4y=c zaHylIvbK_3=k?%^st%nT9urG#tQ39tV6H;voibD|4q#+oAk}@&+ z9=1?a*$Fio!?9quAS2B#hTw}iBZ&g%+fo=Y<{$V2e3hu$$23x2ox>{UY!3i(-3$Dd2-WY*OH*v_fRLzZj!EEbj-!cA8|O` z9rdlRuOo!A*(^z9G8r;y*zDLK!9g4e^^%aRJYSw)CYjlQ*cMHHxt;lRXbMyu2>;s5 zX3cVEb<%dpc!*!FChzQj^x#bO9PI+G@`V}h^z`Jc*|ioO>@D-#>ZS3u=-R2TkKZ00 zJ@Wn9-FFR%d&>3JY-Ph!;*ARULgJJ5!K;4&d;@RT literal 0 HcmV?d00001 diff --git a/src/assets/images/toolbar/icons/message_unsee.gif b/src/assets/images/toolbar/icons/message_unsee.gif new file mode 100644 index 0000000000000000000000000000000000000000..eddfe1cc2b7d244145d98d3e0429f758602fa39d GIT binary patch literal 1511 zcmZ?wbhEHblwwd|Sj5htm$xBo&tCZxN)4{^3rViZPPR-@vbR&Psj#ZZEyztRNmQuF&B-gas<2f`Ovz75wF0t1!um=I zU?nBlwn~m52?day&iO^D3Z{Cdy2%EHCJN@3dWNQkCKiS|3PuKo#`*@v`i923hDKHf zmR5$Q3Q(W~w5=#5%__*n4QdyVXRDM^Qc_^0uU}qXu2*iXmtT~wZ)j<0sc&GUZ)Btk zRH0j3nOBlnp_^B%3^TzcwK%ybv!En1KTiQIIg#cFVINM%8)eo$(0erZv1Dp0vH$f^P> z=c3falKi5O{QMkPCww={D!G<3DJG%|FxFn2UJbv8D2GPf{>>2=9ZF3nBND}m`v zLFhHZsTY(IatnYqyQCInmZhe+73JqDfPHM0iQ6s4IL(9VO~LIJ6P$YWfsWA!#Vb-g z!-Rl|2gHP@S|A6W?o;!CiM%OrteG>WPn$Yr@}!9q`ulo&y1P0% z+S^)NnwuIM>g#H2s;eq1%F9Yiii-*h^7C?Yva>QX($i8?l9Lh>;^SgtqN5@s!oxyC zf`bAB{QZ1=yuCa<+}&JVoShsU?CorAtgS39%*{+qjExKp^!0Rgw6!!f)YVi~l$8_} z2E%h009bK&rn)CWx>KY`bbWJEwSTbu`A>XQvu2%D> z#aQz1h^PwMy=OyX(X4~~`x_mm9(P#hAg_4o#O13(oFK2TqIqRA&@2B0rb(`zr5t>_ z;NQWt%KHK=ZHgkIoQpC~+2xfT`*G%qTF>!a%g{$uTZMxiKdbyyko3*0sxAZyD4@^}hd9@DpmBs0;Z4FH&Ufo9R zjm6Cw|gW!U_%O?XxI14-? ziy0WWg+Z8+Vb&Z8pdfpRr>`sfLrx|^289Xnil2Z&vY8S|xv6<2KrRD=b5Uwy zNotBhd1gt5g1e`0K#E=}J5cN9Otb&+B`H$r)!7-h*mM_6OWMD9e$&z@k*&GfYwrq%UHMdv9zj?o0G?D*( z)F^g=%!IF%#RvbpS;_NW&&!w9G(6&LlY4ndj?UC7F|n007m~hgIlpd#&O*0KR&pma q9orR))>`bkVfQ|&@C)lJtGBFE`qNVjcXm$&1&*hypUXO@geCydQkbm( literal 0 HcmV?d00001 diff --git a/src/assets/images/toolbar/icons/rooms.png b/src/assets/images/toolbar/icons/rooms.png new file mode 100644 index 0000000000000000000000000000000000000000..00261ceed4d77b87ce0c6d84fa6311558aff4ab3 GIT binary patch literal 1465 zcmV;q1xEUbP)lAfVmG`w17{&3ddB65q1b*V@@qL=F}1e423()JK#XEgF!%q z3ko8y0C9MRguDY(tLtBT&v-Bm5mhPF(yu*xZ14H@zyGyoY@6+BUZi#7Ujmw1Pf>C$ zMK>8-z*ZP?m0@e$ZmMIUf78G%jwS`NhBZ7nMQwqg9M@`aeCE2rX(gScw02ZLr>ALn zDn?NT_6NJk9|)0;TPwK5@#_FiDbVLrG%~3K;Ne(I9B%7k=wJ`|xb<}fWCfDz*9eS@ z*LplO!jL8~OB`lkINS!8wFLNz9lmIA0fxg-aaMss#`pXdSYJRz16jXl%zru=`Ql># z5G@W2eX3Z*Ew~naJ}JP)=T7$Gbg~~e`{FW3qxRDd%ekfa6PEu{#i>AhBa|43(4%|h z^r)h644|^1>5pbr+M@ zSwu_oem>{B5L|(>n}8Eg)`X{?`l`M+`-#42zT?W#edXw^r-0_11#~p-q@7MXLl@AD ztB_6^`gAXT?t5?)ppRWe3|dT6p$9bRtst+fSo)Xc{Hf}SzxGxtN9 zaMx3R)pyiaT`S-%W%v0r_s@gEQ3gBu#YykRoC3W4w1Rd!?9$G;3uxB$)j4plb~M&g z&9GGzWBv5FoWcw}*lHK>R`+cMzF>meOjiZ;hXz)oxQo63SJiM^W69{e0(BLLivZ0s zXm-S50%xAuX@|AjQAxY)71E#e+~)6msiI;5k9Akkbf}u98T@C46<}{UAD7DnFtI$~ z2((ed>iRtZN!0b@yLd3gc#j-ieK zJ6b2M0ysOaS^#f%C_uOjaDi{vu5*j`F&2sxVX>~E3o8v zRK+7B*|kf4O>mqOc;!CwfAU&wdoN#n@_@xCIz{tT+lthnv=&9$Kw+ zn&4@N+G}Nx<%>vO$5bo~P~#l>$v%G02-z5{(sXmzIM%S32*x_yTadAn8q!b-{lvH8 zI-r-WMYk~&rp;)Wm;R2pY&y)KX?A(l0A`1Oa~5iM_0GT<7V&Wkpe)KZ5xs4qR4+ez z_Sh`NS78<*Bo6CHSlvx6mTHG@i85>5lt~B9?!^$GH=_}O?&N|)6ad{>Qepz<9M*-# zu&I6qWiUEuRyu6aZ3C-Wpy9Z{NGQd{Etba z8^d99Ta%*GOaGGHOd6nXEszGZNVyGgL?s!@IgWZb)wgaqXo9W>88mSDXNs+nh`4UO zv*E6m(iv4i%{eeRw&!{6vr*M>VN&$HqpmTk?aBjt^p4W*d`6rk^?`?1LpVPF2<1YjyJCUfu4@} zLLasy8gk*cjej4714|ru5{OYsK(|Ey<&RPYjthU-l+inXUNK;d$=js8 zKJW8>pZ9$~@AE#r{&8#CTc4<|da#P3sOmtAzXM)p!1v6%D&gm%qle#zm#N8?E}f#L zO*h{a)X?S{idyip66!QMgG&TSjoU<74Fg*`o`k(A%F~!mic&8yEMd^2BpR&eN4~IH z6uH5=oDZ_Wqz^=umO%|H9c&FrgT0blwl;dJJZS+2hyz2kq~o!KE~Fc*nYaS{ZZ_n5`bat?-dFr0(t>;mf+INp+b zS-n;8n@5u)LWh4*Za8Q)SfhrK6c{FzO4(8lo2vCNoZIbYSUY34)367v4<2$KhPyjNg9fpu$Xbhu-a!dSgqzjg;#D}@nm73gq~vuDKcp>$#6E7nLrc_;$`vp zxJunSPM#Zg z3dyKysCrbLVBH1xgsV4{NO5z@mSB1&l!3G|fz0$|WtmjS?vMxOpWxGgXsB99Rb$?4 zmbK?+Z}IsobC)X#SxxDWdKk2KajT1gfL}C#7p9_{W_g-(g*Z-dx&%8npM^QemITT6 zL}CPC+RLILjuVA<3r=Tgl(N2P9932#12+|=*t0xFFen5Px*;Yc5b%59+HHy=3nFlZ zSy^(?PCgu_Mak))T|BVUJkQ&KEpv>Qlu zG%Gm)?UExAnya(d%Tk@2b=l>7=o0yu&~2Imi;x&A>0{3RpAIojm@z}OC^}4?fs&Mh ziIpicOTlyHt$9aG$&`zvsOi8wg$>sHF)Y-VLisQ?nbRzYl38fHl9{UjQYdRJPOG5I z$&|4hcypAq@k`_rkCBRk1TNvTp)$QD4x+0OBPD8}sR!o7-yFkiz;Xo|*~6GMIi~yz zd!UQ`x3&XL5Oz4~>s++V+Fdk|%{&Ld36nisXBQnDO#i|T|9^L&n7dL`O!NTR%jDBG z-|&Chw41AYw{~pGD*S(PY;N18WzS!E5zN=_u|?A(OF~4|;-cYI;$jbAl8GJdD9@kf)0y|m?@;3hZ)m`_U|+cbF44ZzuwNm7sHvK zIccdIm3HB!rEXMOR4z*>hS#1O4QVb>nRhl>7)#+zIPli-yo2{JXfvv4SJEKX=~v*7 zux|de*x|Y|8q_hau&YD}Sr&yQj+`)?3MIG8nVOt!g*W5ye*jbXBQU017k4T z@Fu~9=Yx0&E(8ePB)IT=5HG=n0KuCC7oHE|CAbhEc$475^Fh1>7Xk!t5?pvbh?n3( zfZ$Dn3(p7f5?lxnyh(83`5<0`3ju;R2`)Sz#7l4?K=3BPh3A8K2`&T(-Xys2d=M|e zg#f{uSX@=bM{7X>K2Mv156)I@oAM2O=++{&bOb4Ca5hD~@DfE`z5%a4QdA#HQRliT zO4vkE_p2LLjxM68ikSg_Qz(6Wc;mZm`xn-X{xJWjWBRnMU(Hx0Z?9VO(7|8T;h`y` zjXP&wo4)^0?Y47oE@@l8;n=_h_P4paz8m@YdPslk>gmr%cAS52*5;>X{rpH^_?OXV z7Os1H$MF?YBd_k8b1&6>?ds+JR7D_m$CZVnfBwxs2|>Qa8l!`m0YN6 zwXdo6?x(-JaUnX?{mBX8`#FoNDYjEQRyXqdzKhb#CtvZMIB;s+ylHQIds17r?5yXr z=l5K?sLXhlUR-_QsN>+FE!Q{BUcY7gnYL34<{gIFHx@_ XSX*->TJxg$2y>vh)xZ7m?p1#QUUeJ! literal 0 HcmV?d00001 diff --git a/src/assets/images/unique/catalog-info-amount-bg.png b/src/assets/images/unique/catalog-info-amount-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..4a56c9b265580af4a41d2db6a93c0749e5f447e3 GIT binary patch literal 757 zcmVHU6$*_|olkZ5@Yv}3=2k7Pe%oFutjyA1iMDh4Eu|d$HMaFJ z=2ly{Yq2PWwR(Deuiiet7P^0aQAw(wwh8Ml(_g8!bNVf%9Q!r4^)cpFTexe{2*TQc zx~w4qYrxt!tP4=5(tIsszs}VU3y9 zH>?)6P*BGLYq$ZvVYRSDf;t9R!)NskEA{=GL6zWvjY)$B>u^3x+gHHq7glbvVnIDP zITL5Pv_lD0IA|Qq7_1Qu@C~c9va+B`C#M}!^rsgH-2hky+k|xkR===n+&~so?qui; zYd}rg!^$WJJL0UqVb$KVu%J>WW6rP!)YNsrK*b#FNDBCd6|%#@ih{bfzCoUxUNl8N z1!}s?W1zw|BZK7|R*aqW`>#1{%TZ8g^D7co2%3~p!e(1I^;14=q+5N%N=Zg2KIB+>}-JDdeB|f|g<=C&WtyC;siVAV<*8kTW zwq-ke`*aEwGlYwKayLxeGEdcy2U&d2tkRDlJ>CH|jiXYqa=BV-Qh>X`!opJlt1nnJ zs{{$Ev^;Qzlu8ORNa8pAFLMtn*_Cw=S!`Qems2b z8&=EBA{+1@8rIk;;2Ty;WJ3gXNLXWAi*HyhVK^12L%|w*uzbU6nY36Rb!b>)!=P_i zEt6IU)OHu@n0A{6tdVZ5Rlg6Q18Tq;u#OQ{^d%G;eSH5klFCzIWn=BHRNFcImQs%W n8r%99b13w+KB3T9-#zsU?45%fJ6~3100000NkvXXu0mjfX8veL literal 0 HcmV?d00001 diff --git a/src/assets/images/unique/catalog-info-sold-out.png b/src/assets/images/unique/catalog-info-sold-out.png new file mode 100644 index 0000000000000000000000000000000000000000..79626e146ba750d69a301276da31ea7ef3f5cf9f GIT binary patch literal 1100 zcmeAS@N?(olHy`uVBq!ia0y~yV9Wus+RgrtArf|+Rp#}#h@3H{`@JgOMv(~k-itlr1eUQnY2zEJgu}q~=E*E`3LzkBxfcI$Ha zoR?RRw!isk>9x^iZBmtG$+}B>;(C9t`74n8`01uk?q6L@s|)uQXCGVm?C*oue}75I z-?m%*^+gEN+plxKF28zy>$&^7+e<#yPPMgj*PON@)JMPlT`1`s>Q`URu1dw%d2?_}ZGVsMjG(UZ?8Uof5(MBiQTMk^zG|X&(56}uOoK2XlD#}26OI$ zy_~Px&&P$m{QG?U;;i#?Cp$maI{W(SjZNR2ds?TTJ|%xu`0AR+TwB&!zZE)`)@W;5 zUZuzCaj@l|gLDS-Ti=MswR_&#&bOJgXVPpr&D1y7WVSIxGu^E(+9&A4w2j5=80)S7 z+FLGv-Ne1&W1W56^xbbVeHsfmlQ+j(y%K-Yu(tVAEANGOlDBjldcXhv@Jr-Ikssd; zvE;*g6TdL=zPjK-Yy-vGDH+=l}vO3nk(7@=}q~&Ww!c`w2|B3+^g! ziDr_1KY!bG-30%IwyA5@E#BenvS6-@y*5zy>;r!P-#w~b@V2*+;kH%yg1ISI1Fxta zQes)ayCwXgtl2>lXX~63)lQBr^fMl)FIoxPNd*l^4r<=G<(aWe4Ul=ZfC|c{bO+ zE{{+8J@ea1c5f^%@HO&o0fzpz$Jq(j*Gx!uO_{H)w7mAl)(d6g=NNKQJC#t6R%<#j)JtaNQL3H7$32mhPO37rxag2!TSYHz01FxapSF z+rqbso#WUtx$o)rFAZiF?94LMmu7n`>3{va`1o^;Q&+7ZDSKn;+ja?V{pnNVjkmJg zvOL!IJ5m1gQDD$~)BJZw0+_%9?+D&FT)>*^pLxsWiR|RZsvEP4Dvx`AzNT?%tM{E4 zzPYPS)|bY;yEE;5YJ76>t9{m~@zIu1KcwX2*Cp;vE3Fd9PzL2u&gK7^WQ+WSO%%1B Q0*eO*Pgg&ebxsLQ0Oxob-v9sr literal 0 HcmV?d00001 diff --git a/src/assets/images/unique/grid-bg-glass.png b/src/assets/images/unique/grid-bg-glass.png new file mode 100644 index 0000000000000000000000000000000000000000..5b64c480bf04361ebab979eab0f6caa99077be46 GIT binary patch literal 213 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB=6Jd|hE&{od)1q(!GMS5!p;TX z?fqOX^gaonAURcJmxhyY+-nhw_L;fIk1#*pcYW)(wCc(CO-;QPs|0`I@}9crkkQf= z8@x1i*916~ofh?4QD?nPXq8Ta_CjGlAJr0}$tzp(S~gF)P#YfM^)Xjm^Qoe0fYU5a zCe{BZQkwt( literal 0 HcmV?d00001 diff --git a/src/assets/images/unique/grid-bg-sold-out.png b/src/assets/images/unique/grid-bg-sold-out.png new file mode 100644 index 0000000000000000000000000000000000000000..94f66620ab811801e478e811b15ed62f043fe2a5 GIT binary patch literal 332 zcmV-S0ki&zP)h5Ga+``7kYa zTrp5p1lpq+Dta6m1np5m#S(C@u91AsKcnY-&l9qr>nW)@RV)E&&fkYF#tEcBQ4^U2 zZ{$lOwq}e8CghukP!gIn0E95cn-H1@36V#(F%1bZ%Rm!GURE*%-&|-w0mL6>0r77u zQ{eNEFc%?&ftMgkC}kdU5UPasM8Jd+e;q{-E0E?Ms)>LJwL}CHYJpgRS|iZkj;07+ eT_6ysi~0dr*QT5gZfC^+0000|k1|%Oc%$NbBI14-?iy0WWg+Q3`(%rg0K*4HH z7srr_TW_x~yg!L}n3b0WpUM0vb84vR#~Bx1Y>1Iw*T`_mi2y^Z(BE zzRp{_WrA=|eV(xBl!j{$LYY}O1QZ+^7#NwrjAIU&M^=4fe0tpC?5fn}H@|nx4UhTe syjCogNkzk9KWpfeG~H#3rf7d>%2~-dJ5BUh3eXu0p00i_>zopr0Jl*@f&c&j literal 0 HcmV?d00001 diff --git a/src/assets/images/unique/grid-count-bg.png b/src/assets/images/unique/grid-count-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..68e13bddbb5ecc5d535563bf0073c16f78a854d3 GIT binary patch literal 155 zcmeAS@N?(olHy`uVBq!ia0vp^DnQK1!3HFwjQR>doC1%?Vg?3oArNM~bhqvgP|(-Y z#WAGfRXdq6fvLB6=2f1V4r`oA!&m2g1p2Z^)DWM{Xe^ZuB<`N zn-z`^6=p1UlQl_^WhuYnkd$)#fUM~O2_A->-#iQ+f2}?PG?>BD)z4*}Q$iB};qNn{ literal 0 HcmV?d00001 diff --git a/src/assets/images/unique/inventory-info-amount-bg.png b/src/assets/images/unique/inventory-info-amount-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..af4e31e2e55f34b6625efeca646af58d95562065 GIT binary patch literal 521 zcmV+k0`~ohP)YULD%yF`E#M-(sSQ%7;Yg;D}8O$1^L97Js3?hS}G5YPT1SWL`X^?mRmxw+;EC`c`nZlA;cb=jmoWm68ZdHs;7zL(OcbOf3?*;_ z61#);4HJOWg4qC}Ek(ww>zg7l6o=an^Cvtb4AaL?O9TU#W_G_QvP=|*{UJ!K==8Nr z!S<~M1M5f)7_*L)hKbgZk}zhnmWBxm+P3!zJvR3~BMaKU_z&$Du$2Q`L=+;<00000 LNkvXXu0mjfB01-z literal 0 HcmV?d00001 diff --git a/src/assets/images/unique/numbers.png b/src/assets/images/unique/numbers.png new file mode 100644 index 0000000000000000000000000000000000000000..e1ece79f46a35183bb72afcc166d116bc04615ff GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0vp^hCs~9!3HGXl(Q@cQjwl6jv*DdmIga=F&J=s`ai#H z`auP+$-Ha}O|>OI@V$3EC=|ghwpJ=46%P_p!L9lxe&L5m1>4+P z>(ae#ibLh%1^N8Q1A>%QL>{F{R(hj`GUoMQEMBXMJcd-;4W%gp%3J=r+~Nh3t(uLf zZo0CAYr1V}b^AHl&~&V7uBz9OrhA(0S&lqC6o^)Yk+;#lG>rvM4W(aIxu>eb;jlKW z*LcxWb&P?-Pz?hC0+qX2MMfwq&(0XytfWPpS253I!AJr=s2U0=ourWFbK0z&+60EF zBa*9nO%o~2fWCh;G)?E|vRc`JC{w+M5|`cGoT(eEINDtV>ib?ND=U&w)@e5s z5LJug&@)|PGM!>%Vw1wfVkmG3MV1@kFmfUbo8ed&%x`l#5MGG+lg14>1&+C&9;=%s zqmUbM%mBHXsUcTqAqw0&wQ!(EIKnfwmx>tv9g^J5DpZBQ=ui#U%`iZYfg@zvc7Os) zbCE+_TBn%Uj^QZsowzNY@WSUIxLa4`ka`g>QUV8j^xFd*Ji}?WNvJ zeo1zY7Qa~O|5x!-YiXZkJqF9Cs)%${vCvEkqyD#McWOTCnjmRvZz zO(`*K!!=E$8Q4KVWMUNP1S1kT77HSa={T6ENaC5E39Wi`{IT-8mdx2?Sck=Mz5&3) zd<$V2BwoME zH?A%zAAa<}N$<=fzrMUrS_fB8KN)m>|EzWT(jVb7E!Fzv^55^0Z-2UF{_yk7558HG juYB=o`Pi3lU%&sW+F!419-NEBHr82LYk$7{#ykH20l7o+ literal 0 HcmV?d00001 diff --git a/src/assets/images/wired/icon_action.png b/src/assets/images/wired/icon_action.png new file mode 100644 index 0000000000000000000000000000000000000000..78e90e6302316b47e54429b3440551c8499eaf87 GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp@K+MO%1|+}KPrC%9iacE$Ln>}1Cmi7YXFP%9-}^gz z9@ra7DNhvnTmSP1%cf=j11k3{Q_en=ovq@tasGUHuHMsXJ60OBii(OB^q%;nagIr| zA#noR1PLEy9%XCeNiMUMCjNEqnXj}`yy}FR(&xgX!kfOnSmyrdQp@SENeq6x48`Bq VJ#7q2kpbGy;OXk;vd$@?2>>7KL7o5r literal 0 HcmV?d00001 diff --git a/src/assets/images/wired/icon_condition.png b/src/assets/images/wired/icon_condition.png new file mode 100644 index 0000000000000000000000000000000000000000..26925a63f58d0070286af95dcc6280d98af51d98 GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp@K+MO%1|+}KPrC%9N<3X0Ln>}1Cp0wt&#(~ElIwKk zTaph~(&hMDg!(;G-q3rL! V%ztc#SAq63c)I$ztaD0e0s!`tI1K;* literal 0 HcmV?d00001 diff --git a/src/assets/images/wired/icon_trigger.png b/src/assets/images/wired/icon_trigger.png new file mode 100644 index 0000000000000000000000000000000000000000..f48d13c8751c476841934a05d27981388a5b467c GIT binary patch literal 169 zcmeAS@N?(olHy`uVBq!ia0vp@K+MO%1|+}KPrC%93OrpLLn>}1Cmdk;zcFFYga4mnY&+^FqP9Q~q0J2GqVqmv+}x%E|>n*9!#1_LgA}Aj!gWf#JSWyN0W(_f#KYQv$3aY STb2XuX7F_Nb6Mw<&;$SpoITb6 literal 0 HcmV?d00001 diff --git a/src/assets/images/wired/icon_wired_around.png b/src/assets/images/wired/icon_wired_around.png new file mode 100644 index 0000000000000000000000000000000000000000..0b4b5a12545cb73a0f588dae5ad803c8954255af GIT binary patch literal 197 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~k!3HF)wbmGcI0YV&#S9GGLLkg|>2BR0pkTSD zi(^Q|t>lD+gdhE^_t_sYu(5bFbR`CNhi+(K6y;#}$P~oWuv1B{<8|W?Te;GNBqjlA zhC*h!*8dCHOdYNsiaGM2;aN;g+k$FeMwc_~Mju#3%+@!4d2?Kx?f1fU!3TsJBA7qg qisc{N^K0IXhz3jHNaL{gj0}9DSzfmuh6@6nz~JfX=d#Wzp$PzgMnP== literal 0 HcmV?d00001 diff --git a/src/assets/images/wired/icon_wired_left_right.png b/src/assets/images/wired/icon_wired_left_right.png new file mode 100644 index 0000000000000000000000000000000000000000..862d6d8138b8d597bd57b732bd0ca62e9c5db082 GIT binary patch literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~k!3HF)wbmGcI0YV&#S9GGLLkg|>2BR0pkSP* zi(^Q|t>gp+J{5+KwF>|GS%q0y8Y&nq*&0l)F@LOG zc-`2z6q4+wG-@xnc39*Q(~dvel4}^k7TehNS0f9h@EE^ Q2HM2n>FVdQ&MBb@0GiJ;hyVZp literal 0 HcmV?d00001 diff --git a/src/assets/images/wired/icon_wired_north_east.png b/src/assets/images/wired/icon_wired_north_east.png new file mode 100644 index 0000000000000000000000000000000000000000..3710854fdafa269ca0973583aca7e810ed450f44 GIT binary patch literal 157 zcmeAS@N?(olHy`uVBq!ia0vp@K+MU(1|(lrEz1IN3Opi<85p>QK$!8;-MT+OL4Qvd z$B>F!$q5b(EDCk}NB(yn&~RYvct3d+Ckso%4;#%{G9DW_Q>yGI{$tGMI*|OUcEwxH zj4T6|gbJoBtS{F6Y}dV#uFVdQ&MBb@02RV9 An*aa+ literal 0 HcmV?d00001 diff --git a/src/assets/images/wired/icon_wired_north_west.png b/src/assets/images/wired/icon_wired_north_west.png new file mode 100644 index 0000000000000000000000000000000000000000..09eeefc18a1708ce99e036dac88ed5e552db1e87 GIT binary patch literal 166 zcmeAS@N?(olHy`uVBq!ia0vp@K+MU(1|(lrEz1IN3Opi<85p>QK$!8;-MT+O!3a+m z$B>F!$q5MwKm1wh_>XY%cr;9C`|GggKm!{`<^e8_n0fz)_lKXE*DxGSV3_~JMY`J6+6HI`gQu&X J%Q~loCIBUrHKPCk literal 0 HcmV?d00001 diff --git a/src/assets/images/wired/icon_wired_rotate_clockwise.png b/src/assets/images/wired/icon_wired_rotate_clockwise.png new file mode 100644 index 0000000000000000000000000000000000000000..2827e3d23da0f4d5b51ecec406ccd267291579cd GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0vp^+(699!3HFsq+HVlaSA*li-F=oAk28_ZrvZCpo^!A zV@SoVmdKI;Vst08a5LT>t<8 literal 0 HcmV?d00001 diff --git a/src/assets/images/wired/icon_wired_rotate_counter_clockwise.png b/src/assets/images/wired/icon_wired_rotate_counter_clockwise.png new file mode 100644 index 0000000000000000000000000000000000000000..7e281bacbc901f43d3fd7afdc492c23fd85fe029 GIT binary patch literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^+(699!3HFsq+HVlaSA*li-F=oAk28_ZrvZCpqrBWJMJ@otT*XdC@@=TP1kj0&pGT#>})m+%Rbl%a2p6P7#tNY(9AgS q_!wJ~smX(4C+3Y@6HOn4a4=ZSRQ(^2`y>)*B7>)^pUXO@geCyMyDJ+2 literal 0 HcmV?d00001 diff --git a/src/assets/images/wired/icon_wired_south_east.png b/src/assets/images/wired/icon_wired_south_east.png new file mode 100644 index 0000000000000000000000000000000000000000..4217c4b830ff0c3101323d4440eb9fe1be8380a1 GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp@K+MU(1|(lrEz1IN3Opi<85p>QK$!8;-MT+O!B|fh z$B>F!$q5b&>QK$!8;-MT+O!Dvqx z$B>F!$q5QPEDaV6u>p+zZ0rjA_>cT={9&^xYJ!9P6_=FDzZVvrkVtW`VHRs-P`SJ@ zvnb(!VkfV^Lm98)(Spkp8SQwV7q(ugS2EF>wCbh2!2RY{DGmmPiA!Ci0_L~g23o`5 M>FVdQ&MBb@00|Z}u>b%7 literal 0 HcmV?d00001 diff --git a/src/assets/images/wired/icon_wired_up_down.png b/src/assets/images/wired/icon_wired_up_down.png new file mode 100644 index 0000000000000000000000000000000000000000..c2d243bae57101cfa041bcba5e4673e892e9fbc9 GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~k!3HF)wbmGcI0YV&#S9GGLLkg|>2BR0prE&> zi(^Q|t>lD+gdhE^_t_sYuqiQ`F>^_HF#P|xLupto?r7~m}M6D9WP*Hcs<85QdRWCZJ@CXp00i_>zopr0KBCt*#H0l literal 0 HcmV?d00001 diff --git a/src/assets/styles/bootstrap/_accordion.scss b/src/assets/styles/bootstrap/_accordion.scss new file mode 100644 index 0000000..fc62ceb --- /dev/null +++ b/src/assets/styles/bootstrap/_accordion.scss @@ -0,0 +1,118 @@ +// +// Base styles +// + +.accordion-button { + position: relative; + display: flex; + align-items: center; + width: 100%; + padding: $accordion-button-padding-y $accordion-button-padding-x; + @include font-size($font-size-base); + color: $accordion-button-color; + text-align: left; // Reset button style + background-color: $accordion-button-bg; + border: 0; + @include border-radius(0); + overflow-anchor: none; + @include transition($accordion-transition); + + &:not(.collapsed) { + color: $accordion-button-active-color; + background-color: $accordion-button-active-bg; + box-shadow: inset 0 ($accordion-border-width * -1) 0 $accordion-border-color; + + &::after { + background-image: escape-svg($accordion-button-active-icon); + transform: $accordion-icon-transform; + } + } + + // Accordion icon + &::after { + flex-shrink: 0; + width: $accordion-icon-width; + height: $accordion-icon-width; + margin-left: auto; + content: ""; + background-image: escape-svg($accordion-button-icon); + background-repeat: no-repeat; + background-size: $accordion-icon-width; + @include transition($accordion-icon-transition); + } + + &:hover { + z-index: 2; + } + + &:focus { + z-index: 3; + border-color: $accordion-button-focus-border-color; + outline: 0; + box-shadow: $accordion-button-focus-box-shadow; + } +} + +.accordion-header { + margin-bottom: 0; +} + +.accordion-item { + background-color: $accordion-bg; + border: $accordion-border-width solid $accordion-border-color; + + &:first-of-type { + @include border-top-radius($accordion-border-radius); + + .accordion-button { + @include border-top-radius($accordion-inner-border-radius); + } + } + + &:not(:first-of-type) { + border-top: 0; + } + + // Only set a border-radius on the last item if the accordion is collapsed + &:last-of-type { + @include border-bottom-radius($accordion-border-radius); + + .accordion-button { + &.collapsed { + @include border-bottom-radius($accordion-inner-border-radius); + } + } + + .accordion-collapse { + @include border-bottom-radius($accordion-border-radius); + } + } +} + +.accordion-body { + padding: $accordion-body-padding-y $accordion-body-padding-x; +} + + +// Flush accordion items +// +// Remove borders and border-radius to keep accordion items edge-to-edge. + +.accordion-flush { + .accordion-collapse { + border-width: 0; + } + + .accordion-item { + border-right: 0; + border-left: 0; + @include border-radius(0); + + &:first-child { border-top: 0; } + &:last-child { border-bottom: 0; } + + .accordion-button { + @include border-radius(0); + } + } +} diff --git a/src/assets/styles/bootstrap/_alert.scss b/src/assets/styles/bootstrap/_alert.scss new file mode 100644 index 0000000..80b6a43 --- /dev/null +++ b/src/assets/styles/bootstrap/_alert.scss @@ -0,0 +1,58 @@ +// +// Base styles +// +@use 'sass:math'; + +.alert { + position: relative; + padding: $alert-padding-y $alert-padding-x; + margin-bottom: $alert-margin-bottom; + border: $alert-border-width solid transparent; + @include border-radius($alert-border-radius); +} + +// Headings for larger alerts +.alert-heading { + // Specified to prevent conflicts of changing $headings-color + color: inherit; +} + +// Provide class for links that match alerts +.alert-link { + font-weight: $alert-link-font-weight; +} + + +// Dismissible alerts +// +// Expand the right padding and account for the close button's positioning. + +.alert-dismissible { + padding-right: $alert-dismissible-padding-r; + + // Adjust close link position + .btn-close { + position: absolute; + top: 0; + right: 0; + z-index: $stretched-link-z-index + 1; + padding: $alert-padding-y * 1.25 $alert-padding-x; + } +} + + +// scss-docs-start alert-modifiers +// Generate contextual modifier classes for colorizing the alert. + +@each $state, $value in $theme-colors { + $alert-background: shift-color($value, $alert-bg-scale); + $alert-border: shift-color($value, $alert-border-scale); + $alert-color: shift-color($value, $alert-color-scale); + @if (contrast-ratio($alert-background, $alert-color) < $min-contrast-ratio) { + $alert-color: mix($value, color-contrast($alert-background), math.abs($alert-color-scale)); + } + .alert-#{$state} { + @include alert-variant($alert-background, $alert-border, $alert-color); + } +} +// scss-docs-end alert-modifiers diff --git a/src/assets/styles/bootstrap/_badge.scss b/src/assets/styles/bootstrap/_badge.scss new file mode 100644 index 0000000..08df1b8 --- /dev/null +++ b/src/assets/styles/bootstrap/_badge.scss @@ -0,0 +1,29 @@ +// Base class +// +// Requires one of the contextual, color modifier classes for `color` and +// `background-color`. + +.badge { + display: inline-block; + padding: $badge-padding-y $badge-padding-x; + @include font-size($badge-font-size); + font-weight: $badge-font-weight; + line-height: 1; + color: $badge-color; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + @include border-radius($badge-border-radius); + @include gradient-bg(); + + // Empty badges collapse automatically + &:empty { + display: none; + } +} + +// Quick fix for badges in buttons +.btn .badge { + position: relative; + top: -1px; +} diff --git a/src/assets/styles/bootstrap/_breadcrumb.scss b/src/assets/styles/bootstrap/_breadcrumb.scss new file mode 100644 index 0000000..f7fafe7 --- /dev/null +++ b/src/assets/styles/bootstrap/_breadcrumb.scss @@ -0,0 +1,28 @@ +.breadcrumb { + display: flex; + flex-wrap: wrap; + padding: $breadcrumb-padding-y $breadcrumb-padding-x; + margin-bottom: $breadcrumb-margin-bottom; + @include font-size($breadcrumb-font-size); + list-style: none; + background-color: $breadcrumb-bg; + @include border-radius($breadcrumb-border-radius); +} + +.breadcrumb-item { + // The separator between breadcrumbs (by default, a forward-slash: "/") + + .breadcrumb-item { + padding-left: $breadcrumb-item-padding-x; + + &::before { + float: left; // Suppress inline spacings and underlining of the separator + padding-right: $breadcrumb-item-padding-x; + color: $breadcrumb-divider-color; + content: var(--#{$variable-prefix}breadcrumb-divider, escape-svg($breadcrumb-divider)) #{"/* rtl:"} var(--#{$variable-prefix}breadcrumb-divider, escape-svg($breadcrumb-divider-flipped)) #{"*/"}; + } + } + + &.active { + color: $breadcrumb-active-color; + } +} diff --git a/src/assets/styles/bootstrap/_button-group.scss b/src/assets/styles/bootstrap/_button-group.scss new file mode 100644 index 0000000..13aa056 --- /dev/null +++ b/src/assets/styles/bootstrap/_button-group.scss @@ -0,0 +1,139 @@ +// Make the div behave like a button +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-flex; + vertical-align: middle; // match .btn alignment given font-size hack above + + > .btn { + position: relative; + flex: 1 1 auto; + } + + // Bring the hover, focused, and "active" buttons to the front to overlay + // the borders properly + > .btn-check:checked + .btn, + > .btn-check:focus + .btn, + > .btn:hover, + > .btn:focus, + > .btn:active, + > .btn.active { + z-index: 1; + } +} + +// Optional: Group multiple button groups together for a toolbar +.btn-toolbar { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + + .input-group { + width: auto; + } +} + +.btn-group { + // Prevent double borders when buttons are next to each other + > .btn:not(:first-child), + > .btn-group:not(:first-child) { + margin-left: -$btn-border-width; + } + + // Reset rounded corners + > .btn:not(:last-child):not(.dropdown-toggle), + > .btn-group:not(:last-child) > .btn { + @include border-end-radius(0); + } + + // The left radius should be 0 if the button is: + // - the "third or more" child + // - the second child and the previous element isn't `.btn-check` (making it the first child visually) + // - part of a btn-group which isn't the first child + > .btn:nth-child(n + 3), + > :not(.btn-check) + .btn, + > .btn-group:not(:first-child) > .btn { + @include border-start-radius(0); + } +} + +// Sizing +// +// Remix the default button sizing classes into new ones for easier manipulation. + +.btn-group-sm > .btn { @extend .btn-sm; } +.btn-group-lg > .btn { @extend .btn-lg; } + + +// +// Split button dropdowns +// + +.dropdown-toggle-split { + padding-right: $btn-padding-x * .75; + padding-left: $btn-padding-x * .75; + + &::after, + .dropup &::after, + .dropend &::after { + margin-left: 0; + } + + .dropstart &::before { + margin-right: 0; + } +} + +.btn-sm + .dropdown-toggle-split { + padding-right: $btn-padding-x-sm * .75; + padding-left: $btn-padding-x-sm * .75; +} + +.btn-lg + .dropdown-toggle-split { + padding-right: $btn-padding-x-lg * .75; + padding-left: $btn-padding-x-lg * .75; +} + + +// The clickable button for toggling the menu +// Set the same inset shadow as the :active state +.btn-group.show .dropdown-toggle { + @include box-shadow($btn-active-box-shadow); + + // Show no shadow for `.btn-link` since it has no other button styles. + &.btn-link { + @include box-shadow(none); + } +} + + +// +// Vertical button groups +// + +.btn-group-vertical { + flex-direction: column; + align-items: flex-start; + justify-content: center; + + > .btn, + > .btn-group { + width: 100%; + } + + > .btn:not(:first-child), + > .btn-group:not(:first-child) { + margin-top: -$btn-border-width; + } + + // Reset rounded corners + > .btn:not(:last-child):not(.dropdown-toggle), + > .btn-group:not(:last-child) > .btn { + @include border-bottom-radius(0); + } + + > .btn ~ .btn, + > .btn-group:not(:first-child) > .btn { + @include border-top-radius(0); + } +} diff --git a/src/assets/styles/bootstrap/_buttons.scss b/src/assets/styles/bootstrap/_buttons.scss new file mode 100644 index 0000000..3c2cba9 --- /dev/null +++ b/src/assets/styles/bootstrap/_buttons.scss @@ -0,0 +1,116 @@ +// +// Base styles +// + +.btn { + display: inline-block; + font-family: $btn-font-family; + font-weight: $btn-font-weight; + line-height: $btn-line-height; + color: $body-color; + text-align: center; + text-decoration: if($link-decoration == none, null, none); + white-space: $btn-white-space; + vertical-align: middle; + cursor: if($enable-button-pointers, pointer, null); + user-select: none; + background-color: transparent; + border: $btn-border-width solid transparent; + @include button-size($btn-padding-y, $btn-padding-x, $btn-font-size, $btn-border-radius); + @include transition($btn-transition); + + &:hover { + color: $body-color; + text-decoration: if($link-hover-decoration == underline, none, null); + } + + .btn-check:focus + &, + &:focus { + outline: 0; + box-shadow: $btn-focus-box-shadow; + } + + .btn-check:checked + &, + .btn-check:active + &, + &:active, + &.active { + @include box-shadow($btn-active-box-shadow); + + &:focus { + @include box-shadow($btn-focus-box-shadow, $btn-active-box-shadow); + } + } + + &:disabled, + &.disabled, + fieldset:disabled & { + pointer-events: none; + opacity: $btn-disabled-opacity; + @include box-shadow(none); + } +} + + +// +// Alternate buttons +// + +// scss-docs-start btn-variant-loops +@each $color, $value in $theme-colors { + .btn-#{$color} { + @include button-variant($value, $value); + } +} + +@each $color, $value in $theme-colors { + .btn-outline-#{$color} { + @include button-outline-variant($value); + } +} +// scss-docs-end btn-variant-loops + + +// +// Link buttons +// + +// Make a button look and behave like a link +.btn-link { + font-weight: $font-weight-normal; + color: $btn-link-color; + text-decoration: $link-decoration; + box-shadow: none !important; + + &:active { + color: $btn-link-color !important; + } + + &:hover { + color: $btn-link-hover-color; + text-decoration: $link-hover-decoration; + } + + &:focus { + text-decoration: $link-hover-decoration; + } + + &:disabled, + &.disabled { + color: $btn-link-disabled-color; + } + + // No need for an active state here +} + + +// +// Button Sizes +// + +.btn-lg { + @include button-size($btn-padding-y-lg, $btn-padding-x-lg, $btn-font-size-lg, $btn-border-radius-lg); +} + +.btn-sm { + @include button-size($btn-padding-y-sm, $btn-padding-x-sm, $btn-font-size-sm, $btn-border-radius-sm); +} diff --git a/src/assets/styles/bootstrap/_card.scss b/src/assets/styles/bootstrap/_card.scss new file mode 100644 index 0000000..22890f5 --- /dev/null +++ b/src/assets/styles/bootstrap/_card.scss @@ -0,0 +1,216 @@ +// +// Base styles +// + +.card { + position: relative; + display: flex; + flex-direction: column; + min-width: 0; // See https://github.com/twbs/bootstrap/pull/22740#issuecomment-305868106 + height: $card-height; + word-wrap: break-word; + background-color: $card-bg; + background-clip: border-box; + border: $card-border-width solid $card-border-color; + @include border-radius($card-border-radius); + @include box-shadow($card-box-shadow); + + > hr { + margin-right: 0; + margin-left: 0; + } + + > .list-group { + border-top: inherit; + border-bottom: inherit; + + &:first-child { + border-top-width: 0; + @include border-top-radius($card-inner-border-radius); + } + + &:last-child { + border-bottom-width: 0; + @include border-bottom-radius($card-inner-border-radius); + } + } + + // Due to specificity of the above selector (`.card > .list-group`), we must + // use a child selector here to prevent double borders. + > .card-header + .list-group, + > .list-group + .card-footer { + border-top: 0; + } +} + +.card-body { + // Enable `flex-grow: 1` for decks and groups so that card blocks take up + // as much space as possible, ensuring footers are aligned to the bottom. + flex: 1 1 auto; + padding: $card-spacer-y $card-spacer-x; + color: $card-color; +} + +.card-title { + margin-bottom: $card-title-spacer-y; +} + +.card-subtitle { + margin-top: -$card-title-spacer-y * .5; + margin-bottom: 0; +} + +.card-text:last-child { + margin-bottom: 0; +} + +.card-link { + &:hover { + text-decoration: if($link-hover-decoration == underline, none, null); + } + + + .card-link { + margin-left: $card-spacer-x; + } +} + +// +// Optional textual caps +// + +.card-header { + padding: $card-cap-padding-y $card-cap-padding-x; + margin-bottom: 0; // Removes the default margin-bottom of + color: $card-cap-color; + background-color: $card-cap-bg; + border-bottom: $card-border-width solid $card-border-color; + + &:first-child { + @include border-radius($card-inner-border-radius $card-inner-border-radius 0 0); + } +} + +.card-footer { + padding: $card-cap-padding-y $card-cap-padding-x; + color: $card-cap-color; + background-color: $card-cap-bg; + border-top: $card-border-width solid $card-border-color; + + &:last-child { + @include border-radius(0 0 $card-inner-border-radius $card-inner-border-radius); + } +} + + +// +// Header navs +// + +.card-header-tabs { + margin-right: -$card-cap-padding-x * .5; + margin-bottom: -$card-cap-padding-y; + margin-left: -$card-cap-padding-x * .5; + border-bottom: 0; + + @if $nav-tabs-link-active-bg != $card-bg { + .nav-link.active { + background-color: $card-bg; + border-bottom-color: $card-bg; + } + } +} + +.card-header-pills { + margin-right: -$card-cap-padding-x * .5; + margin-left: -$card-cap-padding-x * .5; +} + +// Card image +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: $card-img-overlay-padding; + @include border-radius($card-inner-border-radius); +} + +.card-img, +.card-img-top, +.card-img-bottom { + width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch +} + +.card-img, +.card-img-top { + @include border-top-radius($card-inner-border-radius); +} + +.card-img, +.card-img-bottom { + @include border-bottom-radius($card-inner-border-radius); +} + + +// +// Card groups +// + +.card-group { + // The child selector allows nested `.card` within `.card-group` + // to display properly. + > .card { + margin-bottom: $card-group-margin; + } + + @include media-breakpoint-up(sm) { + display: flex; + flex-flow: row wrap; + // The child selector allows nested `.card` within `.card-group` + // to display properly. + > .card { + // Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4 + flex: 1 0 0%; + margin-bottom: 0; + + + .card { + margin-left: 0; + border-left: 0; + } + + // Handle rounded corners + @if $enable-rounded { + &:not(:last-child) { + @include border-end-radius(0); + + .card-img-top, + .card-header { + // stylelint-disable-next-line property-disallowed-list + border-top-right-radius: 0; + } + .card-img-bottom, + .card-footer { + // stylelint-disable-next-line property-disallowed-list + border-bottom-right-radius: 0; + } + } + + &:not(:first-child) { + @include border-start-radius(0); + + .card-img-top, + .card-header { + // stylelint-disable-next-line property-disallowed-list + border-top-left-radius: 0; + } + .card-img-bottom, + .card-footer { + // stylelint-disable-next-line property-disallowed-list + border-bottom-left-radius: 0; + } + } + } + } + } +} diff --git a/src/assets/styles/bootstrap/_carousel.scss b/src/assets/styles/bootstrap/_carousel.scss new file mode 100644 index 0000000..3d8fb15 --- /dev/null +++ b/src/assets/styles/bootstrap/_carousel.scss @@ -0,0 +1,229 @@ +// Notes on the classes: +// +// 1. .carousel.pointer-event should ideally be pan-y (to allow for users to scroll vertically) +// even when their scroll action started on a carousel, but for compatibility (with Firefox) +// we're preventing all actions instead +// 2. The .carousel-item-start and .carousel-item-end is used to indicate where +// the active slide is heading. +// 3. .active.carousel-item is the current slide. +// 4. .active.carousel-item-start and .active.carousel-item-end is the current +// slide in its in-transition state. Only one of these occurs at a time. +// 5. .carousel-item-next.carousel-item-start and .carousel-item-prev.carousel-item-end +// is the upcoming slide in transition. + +.carousel { + position: relative; +} + +.carousel.pointer-event { + touch-action: pan-y; +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; + @include clearfix(); +} + +.carousel-item { + position: relative; + display: none; + float: left; + width: 100%; + margin-right: -100%; + backface-visibility: hidden; + @include transition($carousel-transition); +} + +.carousel-item.active, +.carousel-item-next, +.carousel-item-prev { + display: block; +} + +/* rtl:begin:ignore */ +.carousel-item-next:not(.carousel-item-start), +.active.carousel-item-end { + transform: translateX(100%); +} + +.carousel-item-prev:not(.carousel-item-end), +.active.carousel-item-start { + transform: translateX(-100%); +} + +/* rtl:end:ignore */ + + +// +// Alternate transitions +// + +.carousel-fade { + .carousel-item { + opacity: 0; + transition-property: opacity; + transform: none; + } + + .carousel-item.active, + .carousel-item-next.carousel-item-start, + .carousel-item-prev.carousel-item-end { + z-index: 1; + opacity: 1; + } + + .active.carousel-item-start, + .active.carousel-item-end { + z-index: 0; + opacity: 0; + @include transition(opacity 0s $carousel-transition-duration); + } +} + + +// +// Left/right controls for nav +// + +.carousel-control-prev, +.carousel-control-next { + position: absolute; + top: 0; + bottom: 0; + z-index: 1; + // Use flex for alignment (1-3) + display: flex; // 1. allow flex styles + align-items: center; // 2. vertically center contents + justify-content: center; // 3. horizontally center contents + width: $carousel-control-width; + padding: 0; + color: $carousel-control-color; + text-align: center; + background: none; + border: 0; + opacity: $carousel-control-opacity; + @include transition($carousel-control-transition); + + // Hover/focus state + &:hover, + &:focus { + color: $carousel-control-color; + text-decoration: none; + outline: 0; + opacity: $carousel-control-hover-opacity; + } +} +.carousel-control-prev { + left: 0; + background-image: if($enable-gradients, linear-gradient(90deg, rgba($black, .25), rgba($black, .001)), null); +} +.carousel-control-next { + right: 0; + background-image: if($enable-gradients, linear-gradient(270deg, rgba($black, .25), rgba($black, .001)), null); +} + +// Icons for within +.carousel-control-prev-icon, +.carousel-control-next-icon { + display: inline-block; + width: $carousel-control-icon-width; + height: $carousel-control-icon-width; + background-repeat: no-repeat; + background-position: 50%; + background-size: 100% 100%; +} + +/* rtl:options: { + "autoRename": true, + "stringMap":[ { + "name" : "prev-next", + "search" : "prev", + "replace" : "next" + } ] +} */ +.carousel-control-prev-icon { + background-image: escape-svg($carousel-control-prev-icon-bg); +} +.carousel-control-next-icon { + background-image: escape-svg($carousel-control-next-icon-bg); +} + +// Optional indicator pips/controls +// +// Add a container (such as a list) with the following class and add an item (ideally a focusable control, +// like a button) with data-bs-target for each slide your carousel holds. + +.carousel-indicators { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 2; + display: flex; + justify-content: center; + padding: 0; + // Use the .carousel-control's width as margin so we don't overlay those + margin-right: $carousel-control-width; + margin-bottom: 1rem; + margin-left: $carousel-control-width; + list-style: none; + + [data-bs-target] { + box-sizing: content-box; + flex: 0 1 auto; + width: $carousel-indicator-width; + height: $carousel-indicator-height; + padding: 0; + margin-right: $carousel-indicator-spacer; + margin-left: $carousel-indicator-spacer; + text-indent: -999px; + cursor: pointer; + background-color: $carousel-indicator-active-bg; + background-clip: padding-box; + border: 0; + // Use transparent borders to increase the hit area by 10px on top and bottom. + border-top: $carousel-indicator-hit-area-height solid transparent; + border-bottom: $carousel-indicator-hit-area-height solid transparent; + opacity: $carousel-indicator-opacity; + @include transition($carousel-indicator-transition); + } + + .active { + opacity: $carousel-indicator-active-opacity; + } +} + + +// Optional captions +// +// + +.carousel-caption { + position: absolute; + right: (100% - $carousel-caption-width) * .5; + bottom: $carousel-caption-spacer; + left: (100% - $carousel-caption-width) * .5; + padding-top: $carousel-caption-padding-y; + padding-bottom: $carousel-caption-padding-y; + color: $carousel-caption-color; + text-align: center; +} + +// Dark mode carousel + +.carousel-dark { + .carousel-control-prev-icon, + .carousel-control-next-icon { + filter: $carousel-dark-control-icon-filter; + } + + .carousel-indicators [data-bs-target] { + background-color: $carousel-dark-indicator-active-bg; + } + + .carousel-caption { + color: $carousel-dark-caption-color; + } +} diff --git a/src/assets/styles/bootstrap/_close.scss b/src/assets/styles/bootstrap/_close.scss new file mode 100644 index 0000000..32a0f68 --- /dev/null +++ b/src/assets/styles/bootstrap/_close.scss @@ -0,0 +1,40 @@ +// transparent background and border properties included for button version. +// iOS requires the button element instead of an anchor tag. +// If you want the anchor version, it requires `href="#"`. +// See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile + +.btn-close { + box-sizing: content-box; + width: $btn-close-width; + height: $btn-close-height; + padding: $btn-close-padding-y $btn-close-padding-x; + color: $btn-close-color; + background: transparent escape-svg($btn-close-bg) center / $btn-close-width auto no-repeat; // include transparent for button elements + border: 0; // for button elements + @include border-radius(); + opacity: $btn-close-opacity; + + // Override 's hover style + &:hover { + color: $btn-close-color; + text-decoration: none; + opacity: $btn-close-hover-opacity; + } + + &:focus { + outline: 0; + box-shadow: $btn-close-focus-shadow; + opacity: $btn-close-focus-opacity; + } + + &:disabled, + &.disabled { + pointer-events: none; + user-select: none; + opacity: $btn-close-disabled-opacity; + } +} + +.btn-close-white { + filter: $btn-close-white-filter; +} diff --git a/src/assets/styles/bootstrap/_containers.scss b/src/assets/styles/bootstrap/_containers.scss new file mode 100644 index 0000000..f88f1e5 --- /dev/null +++ b/src/assets/styles/bootstrap/_containers.scss @@ -0,0 +1,41 @@ +// Container widths +// +// Set the container width, and override it for fixed navbars in media queries. + +@if $enable-grid-classes { + // Single container class with breakpoint max-widths + .container, + // 100% wide container at all breakpoints + .container-fluid { + @include make-container(); + } + + // Responsive containers that are 100% wide until a breakpoint + @each $breakpoint, $container-max-width in $container-max-widths { + .container-#{$breakpoint} { + @extend .container-fluid; + } + + @include media-breakpoint-up($breakpoint, $grid-breakpoints) { + %responsive-container-#{$breakpoint} { + max-width: $container-max-width; + } + + // Extend each breakpoint which is smaller or equal to the current breakpoint + $extend-breakpoint: true; + + @each $name, $width in $grid-breakpoints { + @if ($extend-breakpoint) { + .container#{breakpoint-infix($name, $grid-breakpoints)} { + @extend %responsive-container-#{$breakpoint}; + } + + // Once the current breakpoint is reached, stop extending + @if ($breakpoint == $name) { + $extend-breakpoint: false; + } + } + } + } + } +} diff --git a/src/assets/styles/bootstrap/_dropdown.scss b/src/assets/styles/bootstrap/_dropdown.scss new file mode 100644 index 0000000..adc1143 --- /dev/null +++ b/src/assets/styles/bootstrap/_dropdown.scss @@ -0,0 +1,240 @@ +// The dropdown wrapper (`

`) +.dropup, +.dropend, +.dropdown, +.dropstart { + position: relative; +} + +.dropdown-toggle { + white-space: nowrap; + + // Generate the caret automatically + @include caret(); +} + +// The dropdown menu +.dropdown-menu { + position: absolute; + z-index: $zindex-dropdown; + display: none; // none by default, but block on "open" of the menu + min-width: $dropdown-min-width; + padding: $dropdown-padding-y $dropdown-padding-x; + margin: 0; // Override default margin of ul + @include font-size($dropdown-font-size); + color: $dropdown-color; + text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer) + list-style: none; + background-color: $dropdown-bg; + background-clip: padding-box; + border: $dropdown-border-width solid $dropdown-border-color; + @include border-radius($dropdown-border-radius); + @include box-shadow($dropdown-box-shadow); + + &[data-bs-popper] { + top: 100%; + left: 0; + margin-top: $dropdown-spacer; + } +} + +// scss-docs-start responsive-breakpoints +// We deliberately hardcode the `bs-` prefix because we check +// this custom property in JS to determine Popper's positioning + +@each $breakpoint in map-keys($grid-breakpoints) { + @include media-breakpoint-up($breakpoint) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + + .dropdown-menu#{$infix}-start { + --bs-position: start; + + &[data-bs-popper] { + right: auto; + left: 0; + } + } + + .dropdown-menu#{$infix}-end { + --bs-position: end; + + &[data-bs-popper] { + right: 0; + left: auto; + } + } + } +} +// scss-docs-end responsive-breakpoints + +// Allow for dropdowns to go bottom up (aka, dropup-menu) +// Just add .dropup after the standard .dropdown class and you're set. +.dropup { + .dropdown-menu[data-bs-popper] { + top: auto; + bottom: 100%; + margin-top: 0; + margin-bottom: $dropdown-spacer; + } + + .dropdown-toggle { + @include caret(up); + } +} + +.dropend { + .dropdown-menu[data-bs-popper] { + top: 0; + right: auto; + left: 100%; + margin-top: 0; + margin-left: $dropdown-spacer; + } + + .dropdown-toggle { + @include caret(end); + &::after { + vertical-align: 0; + } + } +} + +.dropstart { + .dropdown-menu[data-bs-popper] { + top: 0; + right: 100%; + left: auto; + margin-top: 0; + margin-right: $dropdown-spacer; + } + + .dropdown-toggle { + @include caret(start); + &::before { + vertical-align: 0; + } + } +} + + +// Dividers (basically an `
`) within the dropdown +.dropdown-divider { + height: 0; + margin: $dropdown-divider-margin-y 0; + overflow: hidden; + border-top: 1px solid $dropdown-divider-bg; +} + +// Links, buttons, and more within the dropdown menu +// +// `
+ +
+ + + ); +}; diff --git a/src/common/layout/LayoutNotificationAlertView.tsx b/src/common/layout/LayoutNotificationAlertView.tsx new file mode 100644 index 0000000..8db771a --- /dev/null +++ b/src/common/layout/LayoutNotificationAlertView.tsx @@ -0,0 +1,35 @@ +import { FC, useMemo } from 'react'; +import { NotificationAlertType } from '../../api'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroCardViewProps } from '../card'; + +export interface LayoutNotificationAlertViewProps extends NitroCardViewProps +{ + title?: string; + type?: string; + onClose: () => void; +} + +export const LayoutNotificationAlertView: FC = props => +{ + const { title = '', onClose = null, classNames = [], children = null,type = NotificationAlertType.DEFAULT, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'nitro-alert' ]; + + newClassNames.push('nitro-alert-' + type); + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames, type ]); + + return ( + + + + { children } + + + ); +} diff --git a/src/common/layout/LayoutNotificationBubbleView.tsx b/src/common/layout/LayoutNotificationBubbleView.tsx new file mode 100644 index 0000000..256837d --- /dev/null +++ b/src/common/layout/LayoutNotificationBubbleView.tsx @@ -0,0 +1,52 @@ +import { FC, useEffect, useMemo, useState } from 'react'; +import { Flex, FlexProps } from '../Flex'; +import { TransitionAnimation, TransitionAnimationTypes } from '../transitions'; + +export interface LayoutNotificationBubbleViewProps extends FlexProps +{ + fadesOut?: boolean; + timeoutMs?: number; + onClose: () => void; +} + +export const LayoutNotificationBubbleView: FC = props => +{ + const { fadesOut = true, timeoutMs = 8000, onClose = null, overflow = 'hidden', classNames = [], ...rest } = props; + const [ isVisible, setIsVisible ] = useState(false); + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'nitro-notification-bubble', 'rounded' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + useEffect(() => + { + setIsVisible(true); + + return () => setIsVisible(false); + }, []); + + useEffect(() => + { + if(!fadesOut) return; + + const timeout = setTimeout(() => + { + setIsVisible(false); + + setTimeout(() => onClose(), 300); + }, timeoutMs); + + return () => clearTimeout(timeout); + }, [ fadesOut, timeoutMs, onClose ]); + + return ( + + + + ); +} diff --git a/src/common/layout/LayoutPetImageView.tsx b/src/common/layout/LayoutPetImageView.tsx new file mode 100644 index 0000000..e052ddf --- /dev/null +++ b/src/common/layout/LayoutPetImageView.tsx @@ -0,0 +1,123 @@ +import { GetRoomEngine, IPetCustomPart, PetFigureData, TextureUtils, Vector3d } from '@nitrots/nitro-renderer'; +import { CSSProperties, FC, useEffect, useMemo, useRef, useState } from 'react'; +import { Base, BaseProps } from '../Base'; + +interface LayoutPetImageViewProps extends BaseProps +{ + figure?: string; + typeId?: number; + paletteId?: number; + petColor?: number; + customParts?: IPetCustomPart[]; + posture?: string; + headOnly?: boolean; + direction?: number; + scale?: number; +} + +export const LayoutPetImageView: FC = props => +{ + const { figure = '', typeId = -1, paletteId = -1, petColor = 0xFFFFFF, customParts = [], posture = 'std', headOnly = false, direction = 0, scale = 1, style = {}, ...rest } = props; + const [ petUrl, setPetUrl ] = useState(null); + const [ width, setWidth ] = useState(0); + const [ height, setHeight ] = useState(0); + const isDisposed = useRef(false); + + const getStyle = useMemo(() => + { + let newStyle: CSSProperties = {}; + + if(petUrl && petUrl.length) newStyle.backgroundImage = `url(${ petUrl })`; + + if(scale !== 1) + { + newStyle.transform = `scale(${ scale })`; + + if(!(scale % 1)) newStyle.imageRendering = 'pixelated'; + } + + newStyle.width = width; + newStyle.height = height; + + if(Object.keys(style).length) newStyle = { ...newStyle, ...style }; + + return newStyle; + }, [ petUrl, scale, style, width, height ]); + + useEffect(() => + { + let url = null; + + let petTypeId = typeId; + let petPaletteId = paletteId; + let petColor1 = petColor; + let petCustomParts: IPetCustomPart[] = customParts; + let petHeadOnly = headOnly; + + if(figure && figure.length) + { + const petFigureData = new PetFigureData(figure); + + petTypeId = petFigureData.typeId; + petPaletteId = petFigureData.paletteId; + petColor1 = petFigureData.color; + petCustomParts = petFigureData.customParts; + } + + if(petTypeId === 16) petHeadOnly = false; + + const imageResult = GetRoomEngine().getRoomObjectPetImage(petTypeId, petPaletteId, petColor1, new Vector3d((direction * 45)), 64, { + imageReady: async (id, texture, image) => + { + if(isDisposed.current) return; + + if(image) + { + setPetUrl(image.src); + setWidth(image.width); + setHeight(image.height); + } + + else if(texture) + { + setPetUrl(await TextureUtils.generateImageUrl(texture)); + setWidth(texture.width); + setHeight(texture.height); + } + }, + imageFailed: (id) => + { + + } + }, petHeadOnly, 0, petCustomParts, posture); + + if(imageResult) + { + (async () => + { + const image = await imageResult.getImage(); + + if(image) + { + setPetUrl(image.src); + setWidth(image.width); + setHeight(image.height); + } + })(); + } + }, [ figure, typeId, paletteId, petColor, customParts, posture, headOnly, direction ]); + + useEffect(() => + { + isDisposed.current = false; + + return () => + { + isDisposed.current = true; + } + }, []); + + const url = `url('${ petUrl }')`; + + return ; +} diff --git a/src/common/layout/LayoutPrizeProductImageView.tsx b/src/common/layout/LayoutPrizeProductImageView.tsx new file mode 100644 index 0000000..206c7a8 --- /dev/null +++ b/src/common/layout/LayoutPrizeProductImageView.tsx @@ -0,0 +1,30 @@ +import { FC } from 'react'; +import { ProductTypeEnum } from '../../api'; +import { LayoutBadgeImageView } from './LayoutBadgeImageView'; +import { LayoutCurrencyIcon } from './LayoutCurrencyIcon'; +import { LayoutFurniImageView } from './LayoutFurniImageView'; + +interface LayoutPrizeProductImageViewProps +{ + productType: string; + classId: number; + extraParam?: string; +} + +export const LayoutPrizeProductImageView: FC = props => +{ + const { productType = ProductTypeEnum.FLOOR, classId = -1, extraParam = undefined } = props; + + switch(productType) + { + case ProductTypeEnum.WALL: + case ProductTypeEnum.FLOOR: + return + case ProductTypeEnum.BADGE: + return + case ProductTypeEnum.HABBO_CLUB: + return + } + + return null; +} diff --git a/src/common/layout/LayoutProgressBar.tsx b/src/common/layout/LayoutProgressBar.tsx new file mode 100644 index 0000000..9b5fb96 --- /dev/null +++ b/src/common/layout/LayoutProgressBar.tsx @@ -0,0 +1,34 @@ +import { FC, useMemo } from 'react'; +import { Base } from '../Base'; +import { Column, ColumnProps } from '../Column'; +import { Flex } from '../Flex'; + +interface LayoutProgressBarProps extends ColumnProps +{ + text?: string; + progress: number; + maxProgress?: number; +} + +export const LayoutProgressBar: FC = props => +{ + const { text = '', progress = 0, maxProgress = 100, position = 'relative', justifyContent = 'center', classNames = [], children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'nitro-progress-bar', 'text-white' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + return ( + + { text && (text.length > 0) && + { text } } + + { children } + + ); +} diff --git a/src/common/layout/LayoutRarityLevelView.tsx b/src/common/layout/LayoutRarityLevelView.tsx new file mode 100644 index 0000000..6fd84d2 --- /dev/null +++ b/src/common/layout/LayoutRarityLevelView.tsx @@ -0,0 +1,28 @@ +import { FC, useMemo } from 'react'; +import { Base, BaseProps } from '../Base'; + +interface LayoutRarityLevelViewProps extends BaseProps +{ + level: number; +} + +export const LayoutRarityLevelView: FC = props => +{ + const { level = 0, classNames = [], children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'nitro-rarity-level' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + return ( + +
{ level }
+ { children } + + ); +} diff --git a/src/common/layout/LayoutRoomObjectImageView.tsx b/src/common/layout/LayoutRoomObjectImageView.tsx new file mode 100644 index 0000000..cc668f8 --- /dev/null +++ b/src/common/layout/LayoutRoomObjectImageView.tsx @@ -0,0 +1,59 @@ +import { GetRoomEngine, TextureUtils, Vector3d } from '@nitrots/nitro-renderer'; +import { CSSProperties, FC, useEffect, useMemo, useState } from 'react'; +import { Base, BaseProps } from '../Base'; + +interface LayoutRoomObjectImageViewProps extends BaseProps +{ + roomId: number; + objectId: number; + category: number; + direction?: number; + scale?: number; +} + +export const LayoutRoomObjectImageView: FC = props => +{ + const { roomId = -1, objectId = 1, category = -1, direction = 2, scale = 1, style = {}, ...rest } = props; + const [ imageElement, setImageElement ] = useState(null); + + const getStyle = useMemo(() => + { + let newStyle: CSSProperties = {}; + + if(imageElement?.src?.length) + { + newStyle.backgroundImage = `url('${ imageElement.src }')`; + newStyle.width = imageElement.width; + newStyle.height = imageElement.height; + } + + if(scale !== 1) + { + newStyle.transform = `scale(${ scale })`; + + if(!(scale % 1)) newStyle.imageRendering = 'pixelated'; + } + + if(Object.keys(style).length) newStyle = { ...newStyle, ...style }; + + return newStyle; + }, [ imageElement, scale, style ]); + + useEffect(() => + { + const imageResult = GetRoomEngine().getRoomObjectImage(roomId, objectId, category, new Vector3d(direction * 45), 64, { + imageReady: async (id, texture, image) => setImageElement(await TextureUtils.generateImage(texture)), + imageFailed: null + }); + + // needs (roomObjectImage.data.width > 140) || (roomObjectImage.data.height > 200) scale 1 + + if(!imageResult) return; + + (async () => setImageElement(await TextureUtils.generateImage(imageResult.data)))(); + }, [ roomId, objectId, category, direction, scale ]); + + if(!imageElement) return null; + + return ; +} diff --git a/src/common/layout/LayoutRoomPreviewerView.tsx b/src/common/layout/LayoutRoomPreviewerView.tsx new file mode 100644 index 0000000..e6a005b --- /dev/null +++ b/src/common/layout/LayoutRoomPreviewerView.tsx @@ -0,0 +1,88 @@ +import { GetRenderer, GetTicker, NitroTicker, RoomPreviewer, TextureUtils } from '@nitrots/nitro-renderer'; +import { FC, MouseEvent, ReactNode, useEffect, useRef } from 'react'; + +export interface LayoutRoomPreviewerViewProps +{ + roomPreviewer: RoomPreviewer; + height?: number; + children?: ReactNode; +} + +export const LayoutRoomPreviewerView: FC = props => +{ + const { roomPreviewer = null, height = 0, children = null } = props; + const elementRef = useRef(); + + const onClick = (event: MouseEvent) => + { + if(!roomPreviewer) return; + + if(event.shiftKey) roomPreviewer.changeRoomObjectDirection(); + else roomPreviewer.changeRoomObjectState(); + } + + useEffect(() => + { + if(!elementRef) return; + + const width = elementRef.current.parentElement.clientWidth; + const texture = TextureUtils.createRenderTexture(width, height); + + const update = async (ticker: NitroTicker) => + { + if(!roomPreviewer || !elementRef.current) return; + + roomPreviewer.updatePreviewRoomView(); + + const renderingCanvas = roomPreviewer.getRenderingCanvas(); + + if(!renderingCanvas.canvasUpdated) return; + + GetRenderer().render({ + target: texture, + container: renderingCanvas.master, + clear: true + }); + + let canvas = GetRenderer().texture.generateCanvas(texture); + const base64 = canvas.toDataURL('image/png'); + + canvas = null; + + elementRef.current.style.backgroundImage = `url(${ base64 })`; + } + + GetTicker().add(update); + + const resizeObserver = new ResizeObserver(() => + { + if(!roomPreviewer || !elementRef.current) return; + + const width = elementRef.current.parentElement.offsetWidth; + + roomPreviewer.modifyRoomCanvas(width, height); + + update(GetTicker()); + }); + + roomPreviewer.getRoomCanvas(width, height); + + resizeObserver.observe(elementRef.current); + + return () => + { + GetTicker().remove(update); + + resizeObserver.disconnect(); + + texture.destroy(true); + } + }, [ roomPreviewer, elementRef, height ]); + + return ( +
+
+ { children } +
+ ); +} diff --git a/src/common/layout/LayoutRoomThumbnailView.tsx b/src/common/layout/LayoutRoomThumbnailView.tsx new file mode 100644 index 0000000..144c1f2 --- /dev/null +++ b/src/common/layout/LayoutRoomThumbnailView.tsx @@ -0,0 +1,37 @@ +import { FC, useMemo } from 'react'; +import { GetConfigurationValue } from '../../api'; +import { Base, BaseProps } from '../Base'; + +export interface LayoutRoomThumbnailViewProps extends BaseProps +{ + roomId?: number; + customUrl?: string; +} + +export const LayoutRoomThumbnailView: FC = props => +{ + const { roomId = -1, customUrl = null, shrink = true, overflow = 'hidden', classNames = [], children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'room-thumbnail', 'rounded', 'border' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + const getImageUrl = useMemo(() => + { + if(customUrl && customUrl.length) return (GetConfigurationValue('image.library.url') + customUrl); + + return (GetConfigurationValue('thumbnails.url').replace('%thumbnail%', roomId.toString())); + }, [ customUrl, roomId ]); + + return ( + + { getImageUrl && } + { children } + + ); +} diff --git a/src/common/layout/LayoutTrophyView.tsx b/src/common/layout/LayoutTrophyView.tsx new file mode 100644 index 0000000..a65cd95 --- /dev/null +++ b/src/common/layout/LayoutTrophyView.tsx @@ -0,0 +1,42 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../api'; +import { Base } from '../Base'; +import { Column } from '../Column'; +import { Flex } from '../Flex'; +import { Text } from '../Text'; +import { DraggableWindow } from '../draggable-window'; + +interface LayoutTrophyViewProps +{ + color: string; + message: string; + date: string; + senderName: string; + customTitle?: string; + onCloseClick: () => void; +} + +export const LayoutTrophyView: FC = props => +{ + const { color = '', message = '', date = '', senderName = '', customTitle = null, onCloseClick = null } = props; + + return ( + + + + + { LocalizeText('widget.furni.trophy.title') } + + + { customTitle && + { customTitle } } + { message } + + + { date } + { senderName } + + + + ); +} diff --git a/src/common/layout/UserProfileIconView.tsx b/src/common/layout/UserProfileIconView.tsx new file mode 100644 index 0000000..d31fa66 --- /dev/null +++ b/src/common/layout/UserProfileIconView.tsx @@ -0,0 +1,29 @@ +import { FC, useMemo } from 'react'; +import { GetUserProfile } from '../../api'; +import { Base, BaseProps } from '../Base'; + +export interface UserProfileIconViewProps extends BaseProps +{ + userId?: number; + userName?: string; +} + +export const UserProfileIconView: FC = props => +{ + const { userId = 0, userName = null, classNames = [], pointer = true, children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'nitro-friends-spritesheet', 'icon-profile-sm' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + return ( + GetUserProfile(userId) } { ... rest }> + { children } + + ); +} diff --git a/src/common/layout/index.ts b/src/common/layout/index.ts new file mode 100644 index 0000000..cbb0568 --- /dev/null +++ b/src/common/layout/index.ts @@ -0,0 +1,24 @@ +export * from './LayoutAvatarImageView'; +export * from './LayoutBackgroundImage'; +export * from './LayoutBadgeImageView'; +export * from './LayoutCounterTimeView'; +export * from './LayoutCurrencyIcon'; +export * from './LayoutFurniIconImageView'; +export * from './LayoutFurniImageView'; +export * from './LayoutGiftTagView'; +export * from './LayoutGridItem'; +export * from './LayoutImage'; +export * from './LayoutItemCountView'; +export * from './LayoutLoadingSpinnerView'; +export * from './LayoutMiniCameraView'; +export * from './LayoutNotificationAlertView'; +export * from './LayoutNotificationBubbleView'; +export * from './LayoutPetImageView'; +export * from './LayoutProgressBar'; +export * from './LayoutRarityLevelView'; +export * from './LayoutRoomObjectImageView'; +export * from './LayoutRoomPreviewerView'; +export * from './LayoutRoomThumbnailView'; +export * from './LayoutTrophyView'; +export * from './UserProfileIconView'; +export * from './limited-edition'; diff --git a/src/common/layout/limited-edition/LayoutLimitedEditionCompactPlateView.tsx b/src/common/layout/limited-edition/LayoutLimitedEditionCompactPlateView.tsx new file mode 100644 index 0000000..ee41c6c --- /dev/null +++ b/src/common/layout/limited-edition/LayoutLimitedEditionCompactPlateView.tsx @@ -0,0 +1,35 @@ +import { FC, useMemo } from 'react'; +import { Base, BaseProps } from '../../Base'; +import { LayoutLimitedEditionStyledNumberView } from './LayoutLimitedEditionStyledNumberView'; + +interface LayoutLimitedEditionCompactPlateViewProps extends BaseProps +{ + uniqueNumber: number; + uniqueSeries: number; +} + +export const LayoutLimitedEditionCompactPlateView: FC = props => +{ + const { uniqueNumber = 0, uniqueSeries = 0, classNames = [], children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'unique-compact-plate', 'z-index-1' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + return ( + +
+ +
+
+ +
+ { children } + + ); +} diff --git a/src/common/layout/limited-edition/LayoutLimitedEditionCompletePlateView.tsx b/src/common/layout/limited-edition/LayoutLimitedEditionCompletePlateView.tsx new file mode 100644 index 0000000..c83e230 --- /dev/null +++ b/src/common/layout/limited-edition/LayoutLimitedEditionCompletePlateView.tsx @@ -0,0 +1,41 @@ +import { FC, useMemo } from 'react'; +import { LocalizeText } from '../../../api'; +import { Base, BaseProps } from '../../Base'; +import { Column } from '../../Column'; +import { Flex } from '../../Flex'; +import { LayoutLimitedEditionStyledNumberView } from './LayoutLimitedEditionStyledNumberView'; + +interface LayoutLimitedEditionCompletePlateViewProps extends BaseProps +{ + uniqueLimitedItemsLeft: number; + uniqueLimitedSeriesSize: number; +} + +export const LayoutLimitedEditionCompletePlateView: FC = props => +{ + const { uniqueLimitedItemsLeft = 0, uniqueLimitedSeriesSize = 0, classNames = [], ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'unique-complete-plate' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + return ( + + + + { LocalizeText('unique.items.left') } +
+
+ + { LocalizeText('unique.items.number.sold') } +
+
+
+ + ); +} diff --git a/src/common/layout/limited-edition/LayoutLimitedEditionStyledNumberView.tsx b/src/common/layout/limited-edition/LayoutLimitedEditionStyledNumberView.tsx new file mode 100644 index 0000000..fe34ba4 --- /dev/null +++ b/src/common/layout/limited-edition/LayoutLimitedEditionStyledNumberView.tsx @@ -0,0 +1,18 @@ +import { FC } from 'react'; + +interface LayoutLimitedEditionStyledNumberViewProps +{ + value: number; +} + +export const LayoutLimitedEditionStyledNumberView: FC = props => +{ + const { value = 0 } = props; + const numbers = value.toString().split(''); + + return ( + <> + { numbers.map((number, index) => ) } + + ); +} diff --git a/src/common/layout/limited-edition/index.ts b/src/common/layout/limited-edition/index.ts new file mode 100644 index 0000000..ee41cf9 --- /dev/null +++ b/src/common/layout/limited-edition/index.ts @@ -0,0 +1,3 @@ +export * from './LayoutLimitedEditionCompactPlateView'; +export * from './LayoutLimitedEditionCompletePlateView'; +export * from './LayoutLimitedEditionStyledNumberView'; diff --git a/src/common/transitions/TransitionAnimation.tsx b/src/common/transitions/TransitionAnimation.tsx new file mode 100644 index 0000000..6eefd2d --- /dev/null +++ b/src/common/transitions/TransitionAnimation.tsx @@ -0,0 +1,52 @@ +import { FC, ReactNode, useEffect, useState } from 'react'; +import { Transition } from 'react-transition-group'; +import { getTransitionAnimationStyle } from './TransitionAnimationStyles'; + +interface TransitionAnimationProps +{ + type: string; + inProp: boolean; + timeout?: number; + className?: string; + children?: ReactNode; +} + +export const TransitionAnimation: FC = props => +{ + const { type = null, inProp = false, timeout = 300, className = null, children = null } = props; + + const [ isChildrenVisible, setChildrenVisible ] = useState(false); + + useEffect(() => + { + let timeoutData: ReturnType = null; + + if(inProp) + { + setChildrenVisible(true); + } + else + { + timeoutData = setTimeout(() => + { + setChildrenVisible(false); + clearTimeout(timeout); + }, timeout); + } + + return () => + { + if(timeoutData) clearTimeout(timeoutData); + } + }, [ inProp, timeout ]); + + return ( + + { state => ( +
+ { isChildrenVisible && children } +
+ ) } +
+ ); +} diff --git a/src/common/transitions/TransitionAnimationStyles.ts b/src/common/transitions/TransitionAnimationStyles.ts new file mode 100644 index 0000000..0d512d0 --- /dev/null +++ b/src/common/transitions/TransitionAnimationStyles.ts @@ -0,0 +1,136 @@ +import { CSSProperties } from 'react'; +import { TransitionStatus } from 'react-transition-group'; +import { ENTERING, EXITING } from 'react-transition-group/Transition'; +import { TransitionAnimationTypes } from './TransitionAnimationTypes'; + +export function getTransitionAnimationStyle(type: string, transition: TransitionStatus, timeout: number = 300): Partial +{ + switch(type) + { + case TransitionAnimationTypes.BOUNCE: + switch(transition) + { + default: + return {} + case ENTERING: + return { + animationName: 'bounceIn', + animationDuration: `${ timeout }ms` + } + case EXITING: + return { + animationName: 'bounceOut', + animationDuration: `${ timeout }ms` + } + } + case TransitionAnimationTypes.SLIDE_LEFT: + switch(transition) + { + default: + return {} + case ENTERING: + return { + animationName: 'slideInLeft', + animationDuration: `${ timeout }ms` + } + case EXITING: + return { + animationName: 'slideOutLeft', + animationDuration: `${ timeout }ms` + } + } + case TransitionAnimationTypes.SLIDE_RIGHT: + switch(transition) + { + default: + return {} + case ENTERING: + return { + animationName: 'slideInRight', + animationDuration: `${ timeout }ms` + } + case EXITING: + return { + animationName: 'slideOutRight', + animationDuration: `${ timeout }ms` + } + } + case TransitionAnimationTypes.FLIP_X: + switch(transition) + { + default: + return {} + case ENTERING: + return { + animationName: 'flipInX', + animationDuration: `${ timeout }ms` + } + case EXITING: + return { + animationName: 'flipOutX', + animationDuration: `${ timeout }ms` + } + } + case TransitionAnimationTypes.FADE_UP: + switch(transition) + { + default: + return {} + case ENTERING: + return { + animationName: 'fadeInUp', + animationDuration: `${ timeout }ms` + } + case EXITING: + return { + animationName: 'fadeOutDown', + animationDuration: `${ timeout }ms` + } + } + case TransitionAnimationTypes.FADE_IN: + switch(transition) + { + default: + return {} + case ENTERING: + return { + animationName: 'fadeIn', + animationDuration: `${ timeout }ms` + } + case EXITING: + return { + animationName: 'fadeOut', + animationDuration: `${ timeout }ms` + } + } + case TransitionAnimationTypes.FADE_DOWN: + switch(transition) + { + default: + return {} + case ENTERING: + return { + animationName: 'fadeInDown', + animationDuration: `${ timeout }ms` + } + case EXITING: + return { + animationName: 'fadeOutUp', + animationDuration: `${ timeout }ms` + } + } + case TransitionAnimationTypes.HEAD_SHAKE: + switch(transition) + { + default: + return {} + case ENTERING: + return { + animationName: 'headShake', + animationDuration: `${ timeout }ms` + } + } + } + + return null; +} diff --git a/src/common/transitions/TransitionAnimationTypes.ts b/src/common/transitions/TransitionAnimationTypes.ts new file mode 100644 index 0000000..4ecc23b --- /dev/null +++ b/src/common/transitions/TransitionAnimationTypes.ts @@ -0,0 +1,11 @@ +export class TransitionAnimationTypes +{ + public static BOUNCE: string = 'bounce'; + public static SLIDE_LEFT: string = 'slideLeft'; + public static SLIDE_RIGHT: string = 'slideRight'; + public static FLIP_X: string = 'flipX'; + public static FADE_IN: string = 'fadeIn'; + public static FADE_DOWN: string = 'fadeDown'; + public static FADE_UP: string = 'fadeUp'; + public static HEAD_SHAKE: string = 'headShake'; +} diff --git a/src/common/transitions/index.ts b/src/common/transitions/index.ts new file mode 100644 index 0000000..283a005 --- /dev/null +++ b/src/common/transitions/index.ts @@ -0,0 +1,3 @@ +export * from './TransitionAnimation'; +export * from './TransitionAnimationStyles'; +export * from './TransitionAnimationTypes'; diff --git a/src/common/types/AlignItemType.ts b/src/common/types/AlignItemType.ts new file mode 100644 index 0000000..5a61476 --- /dev/null +++ b/src/common/types/AlignItemType.ts @@ -0,0 +1 @@ +export type AlignItemType = 'start' | 'end' | 'center' | 'baseline' | 'stretch'; diff --git a/src/common/types/AlignSelfType.ts b/src/common/types/AlignSelfType.ts new file mode 100644 index 0000000..8e26378 --- /dev/null +++ b/src/common/types/AlignSelfType.ts @@ -0,0 +1 @@ +export type AlignSelfType = 'start' | 'end' | 'center' | 'baseline' | 'stretch'; diff --git a/src/common/types/ButtonSizeType.ts b/src/common/types/ButtonSizeType.ts new file mode 100644 index 0000000..0f9dd4a --- /dev/null +++ b/src/common/types/ButtonSizeType.ts @@ -0,0 +1 @@ +export type ButtonSizeType = 'lg' | 'sm'; diff --git a/src/common/types/ColorVariantType.ts b/src/common/types/ColorVariantType.ts new file mode 100644 index 0000000..1ff6f60 --- /dev/null +++ b/src/common/types/ColorVariantType.ts @@ -0,0 +1 @@ +export type ColorVariantType = 'primary' | 'success' | 'danger' | 'secondary' | 'link' | 'black' | 'white' | 'dark' | 'warning' | 'muted' | 'light'; diff --git a/src/common/types/ColumnSizesType.ts b/src/common/types/ColumnSizesType.ts new file mode 100644 index 0000000..2b130d8 --- /dev/null +++ b/src/common/types/ColumnSizesType.ts @@ -0,0 +1 @@ +export type ColumnSizesType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; diff --git a/src/common/types/DisplayType.ts b/src/common/types/DisplayType.ts new file mode 100644 index 0000000..7551d72 --- /dev/null +++ b/src/common/types/DisplayType.ts @@ -0,0 +1 @@ +export type DisplayType = 'none' | 'inline' | 'inline-block' | 'block' | 'grid' | 'table' | 'table-cell' | 'table-row' | 'flex' | 'inline-flex'; diff --git a/src/common/types/FloatType.ts b/src/common/types/FloatType.ts new file mode 100644 index 0000000..63e495f --- /dev/null +++ b/src/common/types/FloatType.ts @@ -0,0 +1 @@ +export type FloatType = 'start' | 'end' | 'none'; diff --git a/src/common/types/FontSizeType.ts b/src/common/types/FontSizeType.ts new file mode 100644 index 0000000..120c11c --- /dev/null +++ b/src/common/types/FontSizeType.ts @@ -0,0 +1 @@ +export type FontSizeType = 1 | 2 | 3 | 4 | 5 | 6; diff --git a/src/common/types/FontWeightType.ts b/src/common/types/FontWeightType.ts new file mode 100644 index 0000000..c7c9286 --- /dev/null +++ b/src/common/types/FontWeightType.ts @@ -0,0 +1 @@ +export type FontWeightType = 'bold' | 'bolder' | 'normal' | 'light' | 'lighter'; diff --git a/src/common/types/JustifyContentType.ts b/src/common/types/JustifyContentType.ts new file mode 100644 index 0000000..73a318d --- /dev/null +++ b/src/common/types/JustifyContentType.ts @@ -0,0 +1 @@ +export type JustifyContentType = 'start' | 'end' | 'center' | 'between' | 'around' | 'evenly'; diff --git a/src/common/types/OverflowType.ts b/src/common/types/OverflowType.ts new file mode 100644 index 0000000..9231ff9 --- /dev/null +++ b/src/common/types/OverflowType.ts @@ -0,0 +1 @@ +export type OverflowType = 'auto' | 'hidden' | 'visible' | 'scroll' | 'y-scroll' | 'unset'; diff --git a/src/common/types/PositionType.ts b/src/common/types/PositionType.ts new file mode 100644 index 0000000..4e20b2f --- /dev/null +++ b/src/common/types/PositionType.ts @@ -0,0 +1 @@ +export type PositionType = 'static' | 'relative' | 'fixed' | 'absolute' | 'sticky'; diff --git a/src/common/types/SpacingType.ts b/src/common/types/SpacingType.ts new file mode 100644 index 0000000..91c2bb5 --- /dev/null +++ b/src/common/types/SpacingType.ts @@ -0,0 +1 @@ +export type SpacingType = 0 | 1 | 2 | 3 | 4 | 5; diff --git a/src/common/types/TextAlignType.ts b/src/common/types/TextAlignType.ts new file mode 100644 index 0000000..cb82648 --- /dev/null +++ b/src/common/types/TextAlignType.ts @@ -0,0 +1 @@ +export type TextAlignType = 'start' | 'center' | 'end'; diff --git a/src/common/types/index.ts b/src/common/types/index.ts new file mode 100644 index 0000000..333177e --- /dev/null +++ b/src/common/types/index.ts @@ -0,0 +1,14 @@ +export * from './AlignItemType'; +export * from './AlignSelfType'; +export * from './ButtonSizeType'; +export * from './ColorVariantType'; +export * from './ColumnSizesType'; +export * from './DisplayType'; +export * from './FloatType'; +export * from './FontSizeType'; +export * from './FontWeightType'; +export * from './JustifyContentType'; +export * from './OverflowType'; +export * from './PositionType'; +export * from './SpacingType'; +export * from './TextAlignType'; diff --git a/src/common/utils/CreateTransitionToIcon.ts b/src/common/utils/CreateTransitionToIcon.ts new file mode 100644 index 0000000..656eea5 --- /dev/null +++ b/src/common/utils/CreateTransitionToIcon.ts @@ -0,0 +1,13 @@ +import { GetEventDispatcher, NitroToolbarAnimateIconEvent } from '@nitrots/nitro-renderer'; + +export const CreateTransitionToIcon = (image: HTMLImageElement, fromElement: HTMLElement, icon: string) => +{ + const bounds = fromElement.getBoundingClientRect(); + const x = (bounds.x + (bounds.width / 2)); + const y = (bounds.y + (bounds.height / 2)); + const event = new NitroToolbarAnimateIconEvent(image, x, y); + + event.iconName = icon; + + GetEventDispatcher().dispatchEvent(event); +} diff --git a/src/common/utils/FriendlyTimeView.tsx b/src/common/utils/FriendlyTimeView.tsx new file mode 100644 index 0000000..cf5a635 --- /dev/null +++ b/src/common/utils/FriendlyTimeView.tsx @@ -0,0 +1,28 @@ +import { FC, useEffect, useMemo, useState } from 'react'; +import { FriendlyTime } from '../../api'; +import { Base, BaseProps } from '../Base'; + +interface FriendlyTimeViewProps extends BaseProps +{ + seconds: number; + isShort?: boolean; +} + +export const FriendlyTimeView: FC = props => +{ + const { seconds = 0, isShort = false, children = null, ...rest } = props; + const [ updateId, setUpdateId ] = useState(-1); + + const getStartSeconds = useMemo(() => (Math.round(new Date().getSeconds()) - seconds), [ seconds ]); + + useEffect(() => + { + const interval = setInterval(() => setUpdateId(prevValue => (prevValue + 1)), 10000); + + return () => clearInterval(interval); + }, []); + + const value = (Math.round(new Date().getSeconds()) - getStartSeconds); + + return { isShort ? FriendlyTime.shortFormat(value) : FriendlyTime.format(value) }; +} diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts new file mode 100644 index 0000000..11d60a3 --- /dev/null +++ b/src/common/utils/index.ts @@ -0,0 +1,2 @@ +export * from './CreateTransitionToIcon'; +export * from './FriendlyTimeView'; diff --git a/src/components/achievements/AchievementsView.scss b/src/components/achievements/AchievementsView.scss new file mode 100644 index 0000000..3fa61ac --- /dev/null +++ b/src/components/achievements/AchievementsView.scss @@ -0,0 +1,15 @@ +.nitro-achievements { + width: $achievement-width; + height: $achievement-height; +} + +.nitro-achievements-back-arrow { + background: url('@/assets/images/achievements/back-arrow.png') no-repeat center; + width: 33px; + height: 34px; +} + +.nitro-achievements-badge-image { + width: 80px !important; + height: 80px !important; +} diff --git a/src/components/achievements/AchievementsView.tsx b/src/components/achievements/AchievementsView.tsx new file mode 100644 index 0000000..3af9b98 --- /dev/null +++ b/src/components/achievements/AchievementsView.tsx @@ -0,0 +1,72 @@ +import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { AchievementUtilities, LocalizeText } from '../../api'; +import { Base, Column, LayoutImage, LayoutProgressBar, NitroCardContentView, NitroCardHeaderView, NitroCardSubHeaderView, NitroCardView, Text } from '../../common'; +import { useAchievements } from '../../hooks'; +import { AchievementCategoryView } from './views/AchievementCategoryView'; +import { AchievementsCategoryListView } from './views/category-list/AchievementsCategoryListView'; + +export const AchievementsView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const { achievementCategories = [], selectedCategoryCode = null, setSelectedCategoryCode = null, achievementScore = 0, getProgress = 0, getMaxProgress = 0, selectedCategory = null } = useAchievements(); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'show': + setIsVisible(true); + return; + case 'hide': + setIsVisible(false); + return; + case 'toggle': + setIsVisible(prevValue => !prevValue); + return; + } + }, + eventUrlPrefix: 'achievements/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + if(!isVisible) return null; + + return ( + + setIsVisible(false) } /> + { selectedCategory && + + setSelectedCategoryCode(null) } className="nitro-achievements-back-arrow" /> + + { LocalizeText(`quests.${ selectedCategory.code }.name`) } + { LocalizeText('achievements.details.categoryprogress', [ 'progress', 'limit' ], [ selectedCategory.getProgress().toString(), selectedCategory.getMaxProgress().toString() ]) } + + + } + + { !selectedCategory && + <> + + + { LocalizeText('achievements.categories.score', [ 'score' ], [ achievementScore.toString() ]) } + + + } + { selectedCategory && + } + + + ); +}; diff --git a/src/components/achievements/views/AchievementBadgeView.tsx b/src/components/achievements/views/AchievementBadgeView.tsx new file mode 100644 index 0000000..5b0d4f9 --- /dev/null +++ b/src/components/achievements/views/AchievementBadgeView.tsx @@ -0,0 +1,19 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { AchievementUtilities } from '../../../api'; +import { BaseProps, LayoutBadgeImageView } from '../../../common'; + +interface AchievementBadgeViewProps extends BaseProps +{ + achievement: AchievementData; + scale?: number; +} + +export const AchievementBadgeView: FC = props => +{ + const { achievement = null, scale = 1, ...rest } = props; + + if(!achievement) return null; + + return ; +} diff --git a/src/components/achievements/views/AchievementCategoryView.tsx b/src/components/achievements/views/AchievementCategoryView.tsx new file mode 100644 index 0000000..8774d20 --- /dev/null +++ b/src/components/achievements/views/AchievementCategoryView.tsx @@ -0,0 +1,37 @@ +import { FC, useEffect } from 'react'; +import { AchievementCategory } from '../../../api'; +import { Column } from '../../../common'; +import { useAchievements } from '../../../hooks'; +import { AchievementListView } from './achievement-list'; +import { AchievementDetailsView } from './AchievementDetailsView'; + +interface AchievementCategoryViewProps +{ + category: AchievementCategory; +} + +export const AchievementCategoryView: FC = props => +{ + const { category = null } = props; + const { selectedAchievement = null, setSelectedAchievementId = null } = useAchievements(); + + useEffect(() => + { + if(!category) return; + + if(!selectedAchievement) + { + setSelectedAchievementId(category?.achievements?.[0]?.achievementId); + } + }, [ category, selectedAchievement, setSelectedAchievementId ]); + + if(!category) return null; + + return ( + + + { !!selectedAchievement && + } + + ); +} diff --git a/src/components/achievements/views/AchievementDetailsView.tsx b/src/components/achievements/views/AchievementDetailsView.tsx new file mode 100644 index 0000000..b04fa18 --- /dev/null +++ b/src/components/achievements/views/AchievementDetailsView.tsx @@ -0,0 +1,53 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { AchievementUtilities, LocalizeBadgeDescription, LocalizeBadgeName, LocalizeText } from '../../../api'; +import { Column, Flex, LayoutCurrencyIcon, LayoutProgressBar, Text } from '../../../common'; +import { AchievementBadgeView } from './AchievementBadgeView'; + +interface AchievementDetailsViewProps +{ + achievement: AchievementData; +} + +export const AchievementDetailsView: FC = props => +{ + const { achievement = null } = props; + + if(!achievement) return null; + + return ( + + + + + { LocalizeText('achievements.details.level', [ 'level', 'limit' ], [ AchievementUtilities.getAchievementLevel(achievement).toString(), achievement.levelCount.toString() ]) } + + + + + + { LocalizeBadgeName(AchievementUtilities.getAchievementBadgeCode(achievement)) } + + + { LocalizeBadgeDescription(AchievementUtilities.getAchievementBadgeCode(achievement)) } + + + { ((achievement.levelRewardPoints > 0) || (achievement.scoreLimit > 0)) && + + { (achievement.levelRewardPoints > 0) && + + + { LocalizeText('achievements.details.reward') } + + + { achievement.levelRewardPoints } + + + } + { (achievement.scoreLimit > 0) && + } + } + + + ) +} diff --git a/src/components/achievements/views/achievement-list/AchievementListItemView.tsx b/src/components/achievements/views/achievement-list/AchievementListItemView.tsx new file mode 100644 index 0000000..9da632b --- /dev/null +++ b/src/components/achievements/views/achievement-list/AchievementListItemView.tsx @@ -0,0 +1,24 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { LayoutGridItem } from '../../../../common'; +import { useAchievements } from '../../../../hooks'; +import { AchievementBadgeView } from '../AchievementBadgeView'; + +interface AchievementListItemViewProps +{ + achievement: AchievementData; +} + +export const AchievementListItemView: FC = props => +{ + const { achievement = null } = props; + const { selectedAchievement = null, setSelectedAchievementId = null } = useAchievements(); + + if(!achievement) return null; + + return ( + 0) } onClick={ event => setSelectedAchievementId(achievement.achievementId) }> + + + ); +} diff --git a/src/components/achievements/views/achievement-list/AchievementListView.tsx b/src/components/achievements/views/achievement-list/AchievementListView.tsx new file mode 100644 index 0000000..f009581 --- /dev/null +++ b/src/components/achievements/views/achievement-list/AchievementListView.tsx @@ -0,0 +1,20 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { AutoGrid } from '../../../../common'; +import { AchievementListItemView } from './AchievementListItemView'; + +interface AchievementListViewProps +{ + achievements: AchievementData[]; +} + +export const AchievementListView: FC = props => +{ + const { achievements = null } = props; + + return ( + + { achievements && (achievements.length > 0) && achievements.map((achievement, index) => ) } + + ); +} diff --git a/src/components/achievements/views/achievement-list/index.ts b/src/components/achievements/views/achievement-list/index.ts new file mode 100644 index 0000000..87ccb43 --- /dev/null +++ b/src/components/achievements/views/achievement-list/index.ts @@ -0,0 +1,2 @@ +export * from './AchievementListItemView'; +export * from './AchievementListView'; diff --git a/src/components/achievements/views/category-list/AchievementsCategoryListItemView.tsx b/src/components/achievements/views/category-list/AchievementsCategoryListItemView.tsx new file mode 100644 index 0000000..91b96c6 --- /dev/null +++ b/src/components/achievements/views/category-list/AchievementsCategoryListItemView.tsx @@ -0,0 +1,31 @@ +import { Dispatch, FC, SetStateAction } from 'react'; +import { AchievementUtilities, IAchievementCategory, LocalizeText } from '../../../../api'; +import { LayoutBackgroundImage, LayoutGridItem, Text } from '../../../../common'; + +interface AchievementCategoryListItemViewProps +{ + category: IAchievementCategory; + selectedCategoryCode: string; + setSelectedCategoryCode: Dispatch>; +} + +export const AchievementsCategoryListItemView: FC = props => +{ + const { category = null, selectedCategoryCode = null, setSelectedCategoryCode = null } = props; + + if(!category) return null; + + const progress = AchievementUtilities.getAchievementCategoryProgress(category); + const maxProgress = AchievementUtilities.getAchievementCategoryMaxProgress(category); + const getCategoryImage = AchievementUtilities.getAchievementCategoryImageUrl(category, progress); + const getTotalUnseen = AchievementUtilities.getAchievementCategoryTotalUnseen(category); + + return ( + setSelectedCategoryCode(category.code) }> + { LocalizeText(`quests.${ category.code }.name`) } + + { progress } / { maxProgress } + + + ); +} diff --git a/src/components/achievements/views/category-list/AchievementsCategoryListView.tsx b/src/components/achievements/views/category-list/AchievementsCategoryListView.tsx new file mode 100644 index 0000000..ca36296 --- /dev/null +++ b/src/components/achievements/views/category-list/AchievementsCategoryListView.tsx @@ -0,0 +1,22 @@ +import { Dispatch, FC, SetStateAction } from 'react'; +import { IAchievementCategory } from '../../../../api'; +import { AutoGrid } from '../../../../common'; +import { AchievementsCategoryListItemView } from './AchievementsCategoryListItemView'; + +interface AchievementsCategoryListViewProps +{ + categories: IAchievementCategory[]; + selectedCategoryCode: string; + setSelectedCategoryCode: Dispatch>; +} + +export const AchievementsCategoryListView: FC = props => +{ + const { categories = null, selectedCategoryCode = null, setSelectedCategoryCode = null } = props; + + return ( + + { categories && (categories.length > 0) && categories.map((category, index) => ) } + + ); +}; diff --git a/src/components/achievements/views/category-list/index.ts b/src/components/achievements/views/category-list/index.ts new file mode 100644 index 0000000..5a367f8 --- /dev/null +++ b/src/components/achievements/views/category-list/index.ts @@ -0,0 +1,2 @@ +export * from './AchievementsCategoryListItemView'; +export * from './AchievementsCategoryListView'; diff --git a/src/components/achievements/views/index.ts b/src/components/achievements/views/index.ts new file mode 100644 index 0000000..576c635 --- /dev/null +++ b/src/components/achievements/views/index.ts @@ -0,0 +1,5 @@ +export * from './achievement-list'; +export * from './AchievementBadgeView'; +export * from './AchievementCategoryView'; +export * from './AchievementDetailsView'; +export * from './category-list'; diff --git a/src/components/avatar-editor-new/AvatarEditorView.scss b/src/components/avatar-editor-new/AvatarEditorView.scss new file mode 100644 index 0000000..22b873d --- /dev/null +++ b/src/components/avatar-editor-new/AvatarEditorView.scss @@ -0,0 +1,336 @@ +.nitro-avatar-editor-spritesheet { + background: url('@/assets/images/avatareditor/avatar-editor-spritesheet.png') transparent no-repeat; + + &.arrow-left-icon { + width: 28px; + height: 21px; + background-position: -226px -131px; + } + + &.arrow-right-icon { + width: 28px; + height: 21px; + background-position: -226px -162px; + } + + &.ca-icon { + width: 25px; + height: 25px; + background-position: -226px -61px; + + &.selected { + width: 25px; + height: 25px; + background-position: -226px -96px; + } + } + + &.cc-icon { + width: 31px; + height: 29px; + background-position: -145px -5px; + + &.selected { + width: 31px; + height: 29px; + background-position: -145px -44px; + } + } + + &.ch-icon { + width: 29px; + height: 24px; + background-position: -186px -39px; + + &.selected { + width: 29px; + height: 24px; + background-position: -186px -73px; + } + } + + &.clear-icon { + width: 27px; + height: 27px; + background-position: -145px -157px; + } + + &.cp-icon { + width: 30px; + height: 24px; + background-position: -145px -264px; + + &.selected { + width: 30px; + height: 24px; + background-position: -186px -5px; + } + } + + + &.ea-icon { + width: 35px; + height: 16px; + background-position: -226px -193px; + + &.selected { + width: 35px; + height: 16px; + background-position: -226px -219px; + } + } + + &.fa-icon { + width: 27px; + height: 20px; + background-position: -186px -137px; + + &.selected { + width: 27px; + height: 20px; + background-position: -186px -107px; + } + } + + &.female-icon { + width: 18px; + height: 27px; + background-position: -186px -202px; + + &.selected { + width: 18px; + height: 27px; + background-position: -186px -239px; + } + } + + &.ha-icon { + width: 25px; + height: 22px; + background-position: -226px -245px; + + &.selected { + width: 25px; + height: 22px; + background-position: -226px -277px; + } + } + + &.he-icon { + width: 31px; + height: 27px; + background-position: -145px -83px; + + &.selected { + width: 31px; + height: 27px; + background-position: -145px -120px; + } + } + + &.hr-icon { + width: 29px; + height: 25px; + background-position: -145px -194px; + + &.selected { + width: 29px; + height: 25px; + background-position: -145px -229px; + } + } + + &.lg-icon { + width: 19px; + height: 20px; + background-position: -303px -45px; + + &.selected { + width: 19px; + height: 20px; + background-position: -303px -75px; + } + } + + &.loading-icon { + width: 21px; + height: 25px; + background-position: -186px -167px; + } + + + &.male-icon { + width: 21px; + height: 21px; + background-position: -186px -276px; + + &.selected { + width: 21px; + height: 21px; + background-position: -272px -5px; + } + } + + + &.sellable-icon { + width: 17px; + height: 15px; + background-position: -303px -105px; + } + + + &.sh-icon { + width: 37px; + height: 10px; + background-position: -303px -5px; + + &.selected { + width: 37px; + height: 10px; + background-position: -303px -25px; + } + } + + + &.spotlight-icon { + width: 130px; + height: 305px; + background-position: -5px -5px; + } + + + &.wa-icon { + width: 36px; + height: 18px; + background-position: -226px -5px; + + &.selected { + width: 36px; + height: 18px; + background-position: -226px -33px; + } + } +} + +.nitro-avatar-editor-wardrobe-figure-preview { + background-color: $pale-sky; + overflow: hidden; + z-index: 1; + + .avatar-image { + position: absolute; + bottom: -15px; + margin: 0 auto; + z-index: 4; + } + + .avatar-shadow { + position: absolute; + left: 0; + right: 0; + bottom: 25px; + width: 40px; + height: 20px; + margin: 0 auto; + border-radius: 100%; + background-color: rgba(0, 0, 0, 0.20); + z-index: 2; + } + + &:after { + position: absolute; + content: ''; + top: 75%; + bottom: 0; + left: 0; + right: 0; + border-radius: 50%; + background-color: $pale-sky; + box-shadow: 0 0 8px 2px rgba($white,.6); + transform: scale(2); + } + + .button-container { + position: absolute; + bottom: 0; + z-index: 5; + } +} + +.nitro-avatar-editor { + width: $avatar-editor-width; + height: $avatar-editor-height; + + .category-item { + height: 40px; + } + + .figure-preview-container { + position: relative; + height: 100%; + background-color: $pale-sky; + overflow: hidden; + z-index: 1; + + .arrow-container { + position: absolute; + width: 100%; + margin: 0 auto; + padding: 0 10px; + display: flex; + justify-content: space-between; + bottom: 12px; + z-index: 5; + + .icon { + cursor: pointer; + } + } + + .avatar-image { + position: absolute; + left: 0; + right: 0; + bottom: 50px; + margin: 0 auto; + z-index: 4; + } + + .avatar-spotlight { + position: absolute; + top: -10px; + left: 0; + right: 0; + margin: 0 auto; + opacity: 0.3; + pointer-events: none; + z-index: 3; + } + + .avatar-shadow { + position: absolute; + left: 0; + right: 0; + bottom: 15px; + width: 70px; + height: 30px; + margin: 0 auto; + border-radius: 100%; + background-color: rgba(0, 0, 0, 0.20); + z-index: 2; + } + + &:after { + position: absolute; + content: ''; + top: 75%; + bottom: 0; + left: 0; + right: 0; + border-radius: 50%; + background-color: $pale-sky; + box-shadow: 0 0 8px 2px rgba($white,.6); + transform: scale(2); + } + } +} diff --git a/src/components/avatar-editor-new/AvatarEditorView.tsx b/src/components/avatar-editor-new/AvatarEditorView.tsx new file mode 100644 index 0000000..eb1973d --- /dev/null +++ b/src/components/avatar-editor-new/AvatarEditorView.tsx @@ -0,0 +1,114 @@ +import { AddLinkEventTracker, AvatarEditorFigureCategory, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { FaDice, FaTrash, FaUndo } from 'react-icons/fa'; +import { AvatarEditorAction, LocalizeText } from '../../api'; +import { Button, ButtonGroup, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common'; +import { useAvatarEditor } from '../../hooks'; +import { AvatarEditorModelView } from './views/AvatarEditorModelView'; + +const DEFAULT_MALE_FIGURE: string = 'hr-100.hd-180-7.ch-215-66.lg-270-79.sh-305-62.ha-1002-70.wa-2007'; +const DEFAULT_FEMALE_FIGURE: string = 'hr-515-33.hd-600-1.ch-635-70.lg-716-66-62.sh-735-68'; + +export const AvatarEditorNewView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const { setIsVisible: setEditorVisibility, avatarModels, activeModelKey, setActiveModelKey } = useAvatarEditor(); + + const processAction = (action: string) => + { + switch(action) + { + case AvatarEditorAction.ACTION_CLEAR: + return; + case AvatarEditorAction.ACTION_RESET: + return; + case AvatarEditorAction.ACTION_RANDOMIZE: + return; + case AvatarEditorAction.ACTION_SAVE: + return; + } + } + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'show': + setIsVisible(true); + return; + case 'hide': + setIsVisible(false); + return; + case 'toggle': + setIsVisible(prevValue => !prevValue); + return; + } + }, + eventUrlPrefix: 'avatar-editor/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + useEffect(() => + { + setEditorVisibility(isVisible) + }, [ isVisible, setEditorVisibility ]); + + if(!isVisible) return null; + + return ( + + setIsVisible(false) } /> + + { Object.keys(avatarModels).map(modelKey => + { + const isActive = (activeModelKey === modelKey); + + return ( + setActiveModelKey(modelKey) }> + { LocalizeText(`avatareditor.category.${ modelKey }`) } + + ); + }) } + + + + + { ((activeModelKey.length > 0) && (activeModelKey !== AvatarEditorFigureCategory.WARDROBE)) && + } + { (activeModelKey === AvatarEditorFigureCategory.WARDROBE) } + + + { /* */ } + + + + + + + + + + + + + ); +} diff --git a/src/components/avatar-editor-new/views/AvatarEditorIcon.tsx b/src/components/avatar-editor-new/views/AvatarEditorIcon.tsx new file mode 100644 index 0000000..a05baa1 --- /dev/null +++ b/src/components/avatar-editor-new/views/AvatarEditorIcon.tsx @@ -0,0 +1,30 @@ +import { FC, useMemo } from 'react'; +import { Base, BaseProps } from '../../../common'; + +type AvatarIconType = 'male' | 'female' | 'clear' | 'sellable' | string; + +export interface AvatarEditorIconProps extends BaseProps +{ + icon: AvatarIconType; + selected?: boolean; +} + +export const AvatarEditorIcon: FC = props => +{ + const { icon = null, selected = false, classNames = [], children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'nitro-avatar-editor-spritesheet' ]; + + if(icon && icon.length) newClassNames.push(icon + '-icon'); + + if(selected) newClassNames.push('selected'); + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ icon, selected, classNames ]); + + return +} diff --git a/src/components/avatar-editor-new/views/AvatarEditorModelView.tsx b/src/components/avatar-editor-new/views/AvatarEditorModelView.tsx new file mode 100644 index 0000000..c012383 --- /dev/null +++ b/src/components/avatar-editor-new/views/AvatarEditorModelView.tsx @@ -0,0 +1,85 @@ +import { AvatarEditorFigureCategory } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useMemo, useState } from 'react'; +import { FigureData, IAvatarEditorCategory } from '../../../api'; +import { Column, Flex, Grid } from '../../../common'; +import { useAvatarEditor } from '../../../hooks'; +import { AvatarEditorIcon } from './AvatarEditorIcon'; +import { AvatarEditorFigureSetView } from './figure-set'; +import { AvatarEditorPaletteSetView } from './palette-set'; + +export const AvatarEditorModelView: FC<{ + name: string, + categories: IAvatarEditorCategory[] +}> = props => +{ + const { name = '', categories = [] } = props; + const [ activeSetType, setActiveSetType ] = useState(''); + const { maxPaletteCount = 1 } = useAvatarEditor(); + + const activeCategory = useMemo(() => + { + return categories.find(category => category.setType === activeSetType) ?? null; + }, [ categories, activeSetType ]); + + const setGender = (gender: string) => + { + // + } + + useEffect(() => + { + if(!activeCategory) return; + + // we need to run this when we change which parts r selected + /* for(const partItem of activeCategory.partItems) + { + if(!partItem || !part.isSelected) continue; + + setMaxPaletteCount(part.maxColorIndex || 1); + + break; + } */ + }, [ activeCategory ]) + + useEffect(() => + { + if(!categories || !categories.length) return; + + setActiveSetType(categories[0]?.setType) + }, [ categories ]); + + if(!activeCategory) return null; + + return ( + + + { (name === AvatarEditorFigureCategory.GENERIC) && + <> + setGender(FigureData.MALE) }> + + + setGender(FigureData.FEMALE) }> + + + } + { (name !== AvatarEditorFigureCategory.GENERIC) && (categories.length > 0) && categories.map(category => + { + return ( + setActiveSetType(category.setType) }> + + + ); + }) } + + + + + + { (maxPaletteCount >= 1) && + } + { (maxPaletteCount === 2) && + } + + + ); +} diff --git a/src/components/avatar-editor-new/views/figure-set/AvatarEditorFigureSetItemView.tsx b/src/components/avatar-editor-new/views/figure-set/AvatarEditorFigureSetItemView.tsx new file mode 100644 index 0000000..1c00fc9 --- /dev/null +++ b/src/components/avatar-editor-new/views/figure-set/AvatarEditorFigureSetItemView.tsx @@ -0,0 +1,53 @@ +import { FC, useEffect, useState } from 'react'; +import { AvatarEditorThumbnailsHelper, FigureData, GetConfigurationValue, IAvatarEditorCategoryPartItem } from '../../../../api'; +import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common'; +import { useAvatarEditor } from '../../../../hooks'; +import { AvatarEditorIcon } from '../AvatarEditorIcon'; + +export const AvatarEditorFigureSetItemView: FC<{ + setType: string; + partItem: IAvatarEditorCategoryPartItem; + isSelected: boolean; +} & LayoutGridItemProps> = props => +{ + const { setType = null, partItem = null, isSelected = false, ...rest } = props; + const [ assetUrl, setAssetUrl ] = useState(''); + const { selectedColorParts = null, getFigureStringWithFace = null } = useAvatarEditor(); + + const isHC = !GetConfigurationValue('hc.disabled', false) && ((partItem.partSet?.clubLevel ?? 0) > 0); + + useEffect(() => + { + if(!setType || !setType.length || !partItem) return; + + const loadImage = async () => + { + const isHC = !GetConfigurationValue('hc.disabled', false) && ((partItem.partSet?.clubLevel ?? 0) > 0); + + let url: string = null; + + if(setType === FigureData.FACE) + { + url = await AvatarEditorThumbnailsHelper.buildForFace(getFigureStringWithFace(partItem.id), isHC); + } + else + { + url = await AvatarEditorThumbnailsHelper.build(setType, partItem, partItem.usesColor, selectedColorParts[setType] ?? null, isHC); + } + + if(url && url.length) setAssetUrl(url); + } + + loadImage(); + }, [ setType, partItem, selectedColorParts, getFigureStringWithFace ]); + + if(!partItem) return null; + + return ( + + { !partItem.isClear && isHC && } + { partItem.isClear && } + { !partItem.isClear && partItem.partSet.isSellable && } + + ); +} diff --git a/src/components/avatar-editor-new/views/figure-set/AvatarEditorFigureSetView.tsx b/src/components/avatar-editor-new/views/figure-set/AvatarEditorFigureSetView.tsx new file mode 100644 index 0000000..7db3743 --- /dev/null +++ b/src/components/avatar-editor-new/views/figure-set/AvatarEditorFigureSetView.tsx @@ -0,0 +1,36 @@ +import { FC, useRef } from 'react'; +import { IAvatarEditorCategory, IAvatarEditorCategoryPartItem } from '../../../../api'; +import { InfiniteGrid } from '../../../../common'; +import { useAvatarEditor } from '../../../../hooks'; +import { AvatarEditorFigureSetItemView } from './AvatarEditorFigureSetItemView'; + +export const AvatarEditorFigureSetView: FC<{ + category: IAvatarEditorCategory +}> = props => +{ + const { category = null } = props; + const { selectedParts = null, selectEditorPart } = useAvatarEditor(); + const elementRef = useRef(null); + + const isPartItemSelected = (partItem: IAvatarEditorCategoryPartItem) => + { + if(!category || !category.setType || !selectedParts || !selectedParts[category.setType]) return false; + + const partId = selectedParts[category.setType]; + + return (partId === partItem.id); + } + + const columnCount = 3; + + return ( + + { + if(!item) return null; + + return ( + selectEditorPart(category.setType, item.partSet?.id ?? -1) } /> + ) + } } /> + ); +} diff --git a/src/components/avatar-editor-new/views/figure-set/index.ts b/src/components/avatar-editor-new/views/figure-set/index.ts new file mode 100644 index 0000000..0c5880b --- /dev/null +++ b/src/components/avatar-editor-new/views/figure-set/index.ts @@ -0,0 +1,2 @@ +export * from './AvatarEditorFigureSetItemView'; +export * from './AvatarEditorFigureSetView'; diff --git a/src/components/avatar-editor-new/views/palette-set/AvatarEditorPaletteSetItemView.tsx b/src/components/avatar-editor-new/views/palette-set/AvatarEditorPaletteSetItemView.tsx new file mode 100644 index 0000000..672c356 --- /dev/null +++ b/src/components/avatar-editor-new/views/palette-set/AvatarEditorPaletteSetItemView.tsx @@ -0,0 +1,27 @@ +import { ColorConverter, IPartColor } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { GetConfigurationValue } from '../../../../api'; +import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common'; + +export interface AvatarEditorPaletteSetItemProps extends LayoutGridItemProps +{ + setType: string; + partColor: IPartColor; + isSelected: boolean; +} + +// its disabled if its hc and you dont have it +export const AvatarEditorPaletteSetItem: FC = props => +{ + const { setType = null, partColor = null, isSelected = false, ...rest } = props; + + if(!partColor) return null; + + const isHC = !GetConfigurationValue('hc.disabled', false) && (partColor.clubLevel > 0); + + return ( + + { isHC && } + + ); +} diff --git a/src/components/avatar-editor-new/views/palette-set/AvatarEditorPaletteSetView.tsx b/src/components/avatar-editor-new/views/palette-set/AvatarEditorPaletteSetView.tsx new file mode 100644 index 0000000..2e72cd4 --- /dev/null +++ b/src/components/avatar-editor-new/views/palette-set/AvatarEditorPaletteSetView.tsx @@ -0,0 +1,33 @@ +import { IPartColor } from '@nitrots/nitro-renderer'; +import { FC, useRef } from 'react'; +import { IAvatarEditorCategory } from '../../../../api'; +import { AutoGrid } from '../../../../common'; +import { useAvatarEditor } from '../../../../hooks'; +import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView'; + +export const AvatarEditorPaletteSetView: FC<{ + category: IAvatarEditorCategory, + paletteIndex: number; +}> = props => +{ + const { category = null, paletteIndex = -1 } = props; + const paletteSet = category?.colorItems[paletteIndex] ?? null; + const { selectedColors = null, selectEditorColor } = useAvatarEditor(); + const elementRef = useRef(null); + + const isPartColorSelected = (partColor: IPartColor) => + { + if(!category || !category.setType || !selectedColors || !selectedColors[category.setType] || !selectedColors[category.setType][paletteIndex]) return false; + + const colorId = selectedColors[category.setType][paletteIndex]; + + return (colorId === partColor.id); + } + + return ( + + { (paletteSet.length > 0) && paletteSet.map(item => + selectEditorColor(category.setType, paletteIndex, item.id) } />) } + + ); +} diff --git a/src/components/avatar-editor-new/views/palette-set/index.ts b/src/components/avatar-editor-new/views/palette-set/index.ts new file mode 100644 index 0000000..977e5b9 --- /dev/null +++ b/src/components/avatar-editor-new/views/palette-set/index.ts @@ -0,0 +1,2 @@ +export * from './AvatarEditorPaletteSetItemView'; +export * from './AvatarEditorPaletteSetView'; diff --git a/src/components/avatar-editor/AvatarEditorView.scss b/src/components/avatar-editor/AvatarEditorView.scss new file mode 100644 index 0000000..22b873d --- /dev/null +++ b/src/components/avatar-editor/AvatarEditorView.scss @@ -0,0 +1,336 @@ +.nitro-avatar-editor-spritesheet { + background: url('@/assets/images/avatareditor/avatar-editor-spritesheet.png') transparent no-repeat; + + &.arrow-left-icon { + width: 28px; + height: 21px; + background-position: -226px -131px; + } + + &.arrow-right-icon { + width: 28px; + height: 21px; + background-position: -226px -162px; + } + + &.ca-icon { + width: 25px; + height: 25px; + background-position: -226px -61px; + + &.selected { + width: 25px; + height: 25px; + background-position: -226px -96px; + } + } + + &.cc-icon { + width: 31px; + height: 29px; + background-position: -145px -5px; + + &.selected { + width: 31px; + height: 29px; + background-position: -145px -44px; + } + } + + &.ch-icon { + width: 29px; + height: 24px; + background-position: -186px -39px; + + &.selected { + width: 29px; + height: 24px; + background-position: -186px -73px; + } + } + + &.clear-icon { + width: 27px; + height: 27px; + background-position: -145px -157px; + } + + &.cp-icon { + width: 30px; + height: 24px; + background-position: -145px -264px; + + &.selected { + width: 30px; + height: 24px; + background-position: -186px -5px; + } + } + + + &.ea-icon { + width: 35px; + height: 16px; + background-position: -226px -193px; + + &.selected { + width: 35px; + height: 16px; + background-position: -226px -219px; + } + } + + &.fa-icon { + width: 27px; + height: 20px; + background-position: -186px -137px; + + &.selected { + width: 27px; + height: 20px; + background-position: -186px -107px; + } + } + + &.female-icon { + width: 18px; + height: 27px; + background-position: -186px -202px; + + &.selected { + width: 18px; + height: 27px; + background-position: -186px -239px; + } + } + + &.ha-icon { + width: 25px; + height: 22px; + background-position: -226px -245px; + + &.selected { + width: 25px; + height: 22px; + background-position: -226px -277px; + } + } + + &.he-icon { + width: 31px; + height: 27px; + background-position: -145px -83px; + + &.selected { + width: 31px; + height: 27px; + background-position: -145px -120px; + } + } + + &.hr-icon { + width: 29px; + height: 25px; + background-position: -145px -194px; + + &.selected { + width: 29px; + height: 25px; + background-position: -145px -229px; + } + } + + &.lg-icon { + width: 19px; + height: 20px; + background-position: -303px -45px; + + &.selected { + width: 19px; + height: 20px; + background-position: -303px -75px; + } + } + + &.loading-icon { + width: 21px; + height: 25px; + background-position: -186px -167px; + } + + + &.male-icon { + width: 21px; + height: 21px; + background-position: -186px -276px; + + &.selected { + width: 21px; + height: 21px; + background-position: -272px -5px; + } + } + + + &.sellable-icon { + width: 17px; + height: 15px; + background-position: -303px -105px; + } + + + &.sh-icon { + width: 37px; + height: 10px; + background-position: -303px -5px; + + &.selected { + width: 37px; + height: 10px; + background-position: -303px -25px; + } + } + + + &.spotlight-icon { + width: 130px; + height: 305px; + background-position: -5px -5px; + } + + + &.wa-icon { + width: 36px; + height: 18px; + background-position: -226px -5px; + + &.selected { + width: 36px; + height: 18px; + background-position: -226px -33px; + } + } +} + +.nitro-avatar-editor-wardrobe-figure-preview { + background-color: $pale-sky; + overflow: hidden; + z-index: 1; + + .avatar-image { + position: absolute; + bottom: -15px; + margin: 0 auto; + z-index: 4; + } + + .avatar-shadow { + position: absolute; + left: 0; + right: 0; + bottom: 25px; + width: 40px; + height: 20px; + margin: 0 auto; + border-radius: 100%; + background-color: rgba(0, 0, 0, 0.20); + z-index: 2; + } + + &:after { + position: absolute; + content: ''; + top: 75%; + bottom: 0; + left: 0; + right: 0; + border-radius: 50%; + background-color: $pale-sky; + box-shadow: 0 0 8px 2px rgba($white,.6); + transform: scale(2); + } + + .button-container { + position: absolute; + bottom: 0; + z-index: 5; + } +} + +.nitro-avatar-editor { + width: $avatar-editor-width; + height: $avatar-editor-height; + + .category-item { + height: 40px; + } + + .figure-preview-container { + position: relative; + height: 100%; + background-color: $pale-sky; + overflow: hidden; + z-index: 1; + + .arrow-container { + position: absolute; + width: 100%; + margin: 0 auto; + padding: 0 10px; + display: flex; + justify-content: space-between; + bottom: 12px; + z-index: 5; + + .icon { + cursor: pointer; + } + } + + .avatar-image { + position: absolute; + left: 0; + right: 0; + bottom: 50px; + margin: 0 auto; + z-index: 4; + } + + .avatar-spotlight { + position: absolute; + top: -10px; + left: 0; + right: 0; + margin: 0 auto; + opacity: 0.3; + pointer-events: none; + z-index: 3; + } + + .avatar-shadow { + position: absolute; + left: 0; + right: 0; + bottom: 15px; + width: 70px; + height: 30px; + margin: 0 auto; + border-radius: 100%; + background-color: rgba(0, 0, 0, 0.20); + z-index: 2; + } + + &:after { + position: absolute; + content: ''; + top: 75%; + bottom: 0; + left: 0; + right: 0; + border-radius: 50%; + background-color: $pale-sky; + box-shadow: 0 0 8px 2px rgba($white,.6); + transform: scale(2); + } + } +} diff --git a/src/components/avatar-editor/AvatarEditorView.tsx b/src/components/avatar-editor/AvatarEditorView.tsx new file mode 100644 index 0000000..f2b9c0b --- /dev/null +++ b/src/components/avatar-editor/AvatarEditorView.tsx @@ -0,0 +1,316 @@ +import { AvatarEditorFigureCategory, FigureSetIdsMessageEvent, GetAvatarRenderManager, GetSessionDataManager, GetWardrobeMessageComposer, IAvatarFigureContainer, UserFigureComposer, UserWardrobePageEvent } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { FaDice, FaTrash, FaUndo } from 'react-icons/fa'; +import { AvatarEditorAction, AvatarEditorUtilities, BodyModel, FigureData, GetClubMemberLevel, GetConfigurationValue, HeadModel, IAvatarEditorCategoryModel, LegModel, LocalizeText, SendMessageComposer, TorsoModel, generateRandomFigure } from '../../api'; +import { Button, ButtonGroup, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common'; +import { useMessageEvent } from '../../hooks'; +import { AvatarEditorFigurePreviewView } from './views/AvatarEditorFigurePreviewView'; +import { AvatarEditorModelView } from './views/AvatarEditorModelView'; +import { AvatarEditorWardrobeView } from './views/AvatarEditorWardrobeView'; + +const DEFAULT_MALE_FIGURE: string = 'hr-100.hd-180-7.ch-215-66.lg-270-79.sh-305-62.ha-1002-70.wa-2007'; +const DEFAULT_FEMALE_FIGURE: string = 'hr-515-33.hd-600-1.ch-635-70.lg-716-66-62.sh-735-68'; + +export const AvatarEditorView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ figures, setFigures ] = useState>(null); + const [ figureData, setFigureData ] = useState(null); + const [ categories, setCategories ] = useState>(null); + const [ activeCategory, setActiveCategory ] = useState(null); + const [ figureSetIds, setFigureSetIds ] = useState([]); + const [ boundFurnitureNames, setBoundFurnitureNames ] = useState([]); + const [ savedFigures, setSavedFigures ] = useState<[ IAvatarFigureContainer, string ][]>([]); + const [ isWardrobeVisible, setIsWardrobeVisible ] = useState(false); + const [ lastFigure, setLastFigure ] = useState(null); + const [ lastGender, setLastGender ] = useState(null); + const [ needsReset, setNeedsReset ] = useState(true); + const [ isInitalized, setIsInitalized ] = useState(false); + + const maxWardrobeSlots = useMemo(() => GetConfigurationValue('avatar.wardrobe.max.slots', 10), []); + + useMessageEvent(FigureSetIdsMessageEvent, event => + { + const parser = event.getParser(); + + setFigureSetIds(parser.figureSetIds); + setBoundFurnitureNames(parser.boundsFurnitureNames); + }); + + useMessageEvent(UserWardrobePageEvent, event => + { + const parser = event.getParser(); + const savedFigures: [ IAvatarFigureContainer, string ][] = []; + + let i = 0; + + while(i < maxWardrobeSlots) + { + savedFigures.push([ null, null ]); + + i++; + } + + for(let [ index, [ look, gender ] ] of parser.looks.entries()) + { + const container = GetAvatarRenderManager().createFigureContainer(look); + + savedFigures[(index - 1)] = [ container, gender ]; + } + + setSavedFigures(savedFigures); + }); + + const selectCategory = useCallback((name: string) => + { + if(!categories) return; + + setActiveCategory(categories.get(name)); + }, [ categories ]); + + const resetCategories = useCallback(() => + { + const categories = new Map(); + + categories.set(AvatarEditorFigureCategory.GENERIC, new BodyModel()); + categories.set(AvatarEditorFigureCategory.HEAD, new HeadModel()); + categories.set(AvatarEditorFigureCategory.TORSO, new TorsoModel()); + categories.set(AvatarEditorFigureCategory.LEGS, new LegModel()); + + setCategories(categories); + }, []); + + const setupFigures = useCallback(() => + { + const figures: Map = new Map(); + + const maleFigure = new FigureData(); + const femaleFigure = new FigureData(); + + maleFigure.loadAvatarData(DEFAULT_MALE_FIGURE, FigureData.MALE); + femaleFigure.loadAvatarData(DEFAULT_FEMALE_FIGURE, FigureData.FEMALE); + + figures.set(FigureData.MALE, maleFigure); + figures.set(FigureData.FEMALE, femaleFigure); + + setFigures(figures); + setFigureData(figures.get(FigureData.MALE)); + }, []); + + const loadAvatarInEditor = useCallback((figure: string, gender: string, reset: boolean = true) => + { + gender = AvatarEditorUtilities.getGender(gender); + + let newFigureData = figureData; + + if(gender !== newFigureData.gender) newFigureData = figures.get(gender); + + if(figure !== newFigureData.getFigureString()) newFigureData.loadAvatarData(figure, gender); + + if(newFigureData !== figureData) setFigureData(newFigureData); + + if(reset) + { + setLastFigure(figureData.getFigureString()); + setLastGender(figureData.gender); + } + }, [ figures, figureData ]); + + const processAction = useCallback((action: string) => + { + switch(action) + { + case AvatarEditorAction.ACTION_CLEAR: + loadAvatarInEditor(figureData.getFigureStringWithFace(0, false), figureData.gender, false); + resetCategories(); + return; + case AvatarEditorAction.ACTION_RESET: + loadAvatarInEditor(lastFigure, lastGender); + resetCategories(); + return; + case AvatarEditorAction.ACTION_RANDOMIZE: + const figure = generateRandomFigure(figureData, figureData.gender, GetClubMemberLevel(), figureSetIds, [ FigureData.FACE ]); + + loadAvatarInEditor(figure, figureData.gender, false); + resetCategories(); + return; + case AvatarEditorAction.ACTION_SAVE: + SendMessageComposer(new UserFigureComposer(figureData.gender, figureData.getFigureString())); + setIsVisible(false); + return; + } + }, [ figureData, lastFigure, lastGender, figureSetIds, loadAvatarInEditor, resetCategories ]) + + const setGender = useCallback((gender: string) => + { + gender = AvatarEditorUtilities.getGender(gender); + + setFigureData(figures.get(gender)); + }, [ figures ]); + + /* useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'show': + setIsVisible(true); + return; + case 'hide': + setIsVisible(false); + return; + case 'toggle': + setIsVisible(prevValue => !prevValue); + return; + } + }, + eventUrlPrefix: 'avatar-editor/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); */ + + useEffect(() => + { + setSavedFigures(new Array(maxWardrobeSlots)); + }, [ maxWardrobeSlots ]); + + useEffect(() => + { + if(!isWardrobeVisible) return; + + setActiveCategory(null); + SendMessageComposer(new GetWardrobeMessageComposer()); + }, [ isWardrobeVisible ]); + + useEffect(() => + { + if(!activeCategory) return; + + setIsWardrobeVisible(false); + }, [ activeCategory ]); + + useEffect(() => + { + if(!categories) return; + + selectCategory(AvatarEditorFigureCategory.GENERIC); + }, [ categories, selectCategory ]); + + useEffect(() => + { + if(!figureData) return; + + AvatarEditorUtilities.CURRENT_FIGURE = figureData; + + resetCategories(); + + return () => AvatarEditorUtilities.CURRENT_FIGURE = null; + }, [ figureData, resetCategories ]); + + useEffect(() => + { + AvatarEditorUtilities.FIGURE_SET_IDS = figureSetIds; + AvatarEditorUtilities.BOUND_FURNITURE_NAMES = boundFurnitureNames; + + resetCategories(); + + return () => + { + AvatarEditorUtilities.FIGURE_SET_IDS = null; + AvatarEditorUtilities.BOUND_FURNITURE_NAMES = null; + } + }, [ figureSetIds, boundFurnitureNames, resetCategories ]); + + useEffect(() => + { + if(!isVisible) return; + + if(!figures) + { + setupFigures(); + + setIsInitalized(true); + + return; + } + }, [ isVisible, figures, setupFigures ]); + + useEffect(() => + { + if(!isVisible || !isInitalized || !needsReset) return; + + loadAvatarInEditor(GetSessionDataManager().figure, GetSessionDataManager().gender); + setNeedsReset(false); + }, [ isVisible, isInitalized, needsReset, loadAvatarInEditor ]); + + useEffect(() => + { + if(isVisible) return; + + return () => + { + setNeedsReset(true); + } + }, [ isVisible ]); + + if(!isVisible || !figureData) return null; + + return ( + + setIsVisible(false) } /> + + { categories && (categories.size > 0) && Array.from(categories.keys()).map(category => + { + const isActive = (activeCategory && (activeCategory.name === category)); + + return ( + selectCategory(category) }> + { LocalizeText(`avatareditor.category.${ category }`) } + + ); + }) } + setIsWardrobeVisible(true) }> + { LocalizeText('avatareditor.category.wardrobe') } + + + + + + { (activeCategory && !isWardrobeVisible) && + } + { isWardrobeVisible && + } + + + + + + + + + + + + + + + + ); +} diff --git a/src/components/avatar-editor/views/AvatarEditorFigurePreviewView.tsx b/src/components/avatar-editor/views/AvatarEditorFigurePreviewView.tsx new file mode 100644 index 0000000..d5715ac --- /dev/null +++ b/src/components/avatar-editor/views/AvatarEditorFigurePreviewView.tsx @@ -0,0 +1,55 @@ +import { AvatarDirectionAngle } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { FigureData } from '../../../api'; +import { Base, Column, LayoutAvatarImageView } from '../../../common'; +import { AvatarEditorIcon } from './AvatarEditorIcon'; + +export interface AvatarEditorFigurePreviewViewProps +{ + figureData: FigureData; +} + +export const AvatarEditorFigurePreviewView: FC = props => +{ + const { figureData = null } = props; + const [ updateId, setUpdateId ] = useState(-1); + + const rotateFigure = (direction: number) => + { + if(direction < AvatarDirectionAngle.MIN_DIRECTION) + { + direction = (AvatarDirectionAngle.MAX_DIRECTION + (direction + 1)); + } + + if(direction > AvatarDirectionAngle.MAX_DIRECTION) + { + direction = (direction - (AvatarDirectionAngle.MAX_DIRECTION + 1)); + } + + figureData.direction = direction; + } + + useEffect(() => + { + if(!figureData) return; + + figureData.notify = () => setUpdateId(prevValue => (prevValue + 1)); + + return () => + { + figureData.notify = null; + } + }, [ figureData ] ); + + return ( + + + + + + rotateFigure(figureData.direction + 1) } /> + rotateFigure(figureData.direction - 1) } /> + + + ); +} diff --git a/src/components/avatar-editor/views/AvatarEditorIcon.tsx b/src/components/avatar-editor/views/AvatarEditorIcon.tsx new file mode 100644 index 0000000..a05baa1 --- /dev/null +++ b/src/components/avatar-editor/views/AvatarEditorIcon.tsx @@ -0,0 +1,30 @@ +import { FC, useMemo } from 'react'; +import { Base, BaseProps } from '../../../common'; + +type AvatarIconType = 'male' | 'female' | 'clear' | 'sellable' | string; + +export interface AvatarEditorIconProps extends BaseProps +{ + icon: AvatarIconType; + selected?: boolean; +} + +export const AvatarEditorIcon: FC = props => +{ + const { icon = null, selected = false, classNames = [], children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'nitro-avatar-editor-spritesheet' ]; + + if(icon && icon.length) newClassNames.push(icon + '-icon'); + + if(selected) newClassNames.push('selected'); + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ icon, selected, classNames ]); + + return +} diff --git a/src/components/avatar-editor/views/AvatarEditorModelView.tsx b/src/components/avatar-editor/views/AvatarEditorModelView.tsx new file mode 100644 index 0000000..6eb8fe3 --- /dev/null +++ b/src/components/avatar-editor/views/AvatarEditorModelView.tsx @@ -0,0 +1,88 @@ +import { Dispatch, FC, SetStateAction, useCallback, useEffect, useState } from 'react'; +import { CategoryData, FigureData, IAvatarEditorCategoryModel } from '../../../api'; +import { Column, Flex, Grid } from '../../../common'; +import { AvatarEditorIcon } from './AvatarEditorIcon'; +import { AvatarEditorFigureSetView } from './figure-set/AvatarEditorFigureSetView'; +import { AvatarEditorPaletteSetView } from './palette-set/AvatarEditorPaletteSetView'; +export interface AvatarEditorModelViewProps +{ + model: IAvatarEditorCategoryModel; + gender: string; + setGender: Dispatch>; +} + +export const AvatarEditorModelView: FC = props => +{ + const { model = null, gender = null, setGender = null } = props; + const [ activeCategory, setActiveCategory ] = useState(null); + const [ maxPaletteCount, setMaxPaletteCount ] = useState(1); + + const selectCategory = useCallback((name: string) => + { + const category = model.categories.get(name); + + if(!category) return; + + category.init(); + + setActiveCategory(category); + + for(const part of category.parts) + { + if(!part || !part.isSelected) continue; + + setMaxPaletteCount(part.maxColorIndex || 1); + + break; + } + }, [ model ]); + + useEffect(() => + { + model.init(); + + for(const name of model.categories.keys()) + { + selectCategory(name); + + break; + } + }, [ model, selectCategory ]); + + if(!model || !activeCategory) return null; + + return ( + + + { model.canSetGender && + <> + setGender(FigureData.MALE) }> + + + setGender(FigureData.FEMALE) }> + + + } + { !model.canSetGender && model.categories && (model.categories.size > 0) && Array.from(model.categories.keys()).map(name => + { + const category = model.categories.get(name); + + return ( + selectCategory(name) }> + + + ); + }) } + + + + + + { (maxPaletteCount >= 1) && + } + { (maxPaletteCount === 2) && + } + + + ); +} diff --git a/src/components/avatar-editor/views/AvatarEditorWardrobeView.tsx b/src/components/avatar-editor/views/AvatarEditorWardrobeView.tsx new file mode 100644 index 0000000..015d3b1 --- /dev/null +++ b/src/components/avatar-editor/views/AvatarEditorWardrobeView.tsx @@ -0,0 +1,79 @@ +import { GetAvatarRenderManager, IAvatarFigureContainer, SaveWardrobeOutfitMessageComposer } from '@nitrots/nitro-renderer'; +import { Dispatch, FC, SetStateAction, useCallback, useMemo } from 'react'; +import { FigureData, GetClubMemberLevel, GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../../api'; +import { AutoGrid, Base, Button, Flex, LayoutAvatarImageView, LayoutCurrencyIcon, LayoutGridItem } from '../../../common'; + +export interface AvatarEditorWardrobeViewProps +{ + figureData: FigureData; + savedFigures: [ IAvatarFigureContainer, string ][]; + setSavedFigures: Dispatch>; + loadAvatarInEditor: (figure: string, gender: string, reset?: boolean) => void; +} + +export const AvatarEditorWardrobeView: FC = props => +{ + const { figureData = null, savedFigures = [], setSavedFigures = null, loadAvatarInEditor = null } = props; + + const hcDisabled = GetConfigurationValue('hc.disabled', false); + + const wearFigureAtIndex = useCallback((index: number) => + { + if((index >= savedFigures.length) || (index < 0)) return; + + const [ figure, gender ] = savedFigures[index]; + + loadAvatarInEditor(figure.getFigureString(), gender); + }, [ savedFigures, loadAvatarInEditor ]); + + const saveFigureAtWardrobeIndex = useCallback((index: number) => + { + if(!figureData || (index >= savedFigures.length) || (index < 0)) return; + + const newFigures = [ ...savedFigures ]; + + const figure = figureData.getFigureString(); + const gender = figureData.gender; + + newFigures[index] = [ GetAvatarRenderManager().createFigureContainer(figure), gender ]; + + setSavedFigures(newFigures); + SendMessageComposer(new SaveWardrobeOutfitMessageComposer((index + 1), figure, gender)); + }, [ figureData, savedFigures, setSavedFigures ]); + + const figures = useMemo(() => + { + if(!savedFigures || !savedFigures.length) return []; + + const items: JSX.Element[] = []; + + savedFigures.forEach(([ figureContainer, gender ], index) => + { + let clubLevel = 0; + + if(figureContainer) clubLevel = GetAvatarRenderManager().getFigureClubLevel(figureContainer, gender); + + items.push( + + { figureContainer && + } + + { !hcDisabled && (clubLevel > 0) && } + + + { figureContainer && + } + + + ); + }); + + return items; + }, [ savedFigures, hcDisabled, saveFigureAtWardrobeIndex, wearFigureAtIndex ]); + + return ( + + { figures } + + ); +} diff --git a/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetItemView.tsx b/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetItemView.tsx new file mode 100644 index 0000000..3cd6383 --- /dev/null +++ b/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetItemView.tsx @@ -0,0 +1,35 @@ +import { FC, useEffect, useState } from 'react'; +import { AvatarEditorGridPartItem, GetConfigurationValue } from '../../../../api'; +import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common'; +import { AvatarEditorIcon } from '../AvatarEditorIcon'; + +export interface AvatarEditorFigureSetItemViewProps extends LayoutGridItemProps +{ + partItem: AvatarEditorGridPartItem; +} + +export const AvatarEditorFigureSetItemView: FC = props => +{ + const { partItem = null, children = null, ...rest } = props; + const [ updateId, setUpdateId ] = useState(-1); + + const hcDisabled = GetConfigurationValue('hc.disabled', false); + + useEffect(() => + { + const rerender = () => setUpdateId(prevValue => (prevValue + 1)); + + partItem.notify = rerender; + + return () => partItem.notify = null; + }, [ partItem ]); + + return ( + + { !hcDisabled && partItem.isHC && } + { partItem.isClear && } + { partItem.isSellable && } + { children } + + ); +} diff --git a/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetView.tsx b/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetView.tsx new file mode 100644 index 0000000..3755731 --- /dev/null +++ b/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetView.tsx @@ -0,0 +1,44 @@ +import { Dispatch, FC, SetStateAction, useCallback, useEffect, useRef } from 'react'; +import { AvatarEditorGridPartItem, CategoryData, IAvatarEditorCategoryModel } from '../../../../api'; +import { AutoGrid } from '../../../../common'; +import { AvatarEditorFigureSetItemView } from './AvatarEditorFigureSetItemView'; + +export interface AvatarEditorFigureSetViewProps +{ + model: IAvatarEditorCategoryModel; + category: CategoryData; + setMaxPaletteCount: Dispatch>; +} + +export const AvatarEditorFigureSetView: FC = props => +{ + const { model = null, category = null, setMaxPaletteCount = null } = props; + const elementRef = useRef(null); + + const selectPart = useCallback((item: AvatarEditorGridPartItem) => + { + const index = category.parts.indexOf(item); + + if(index === -1) return; + + model.selectPart(category.name, index); + + const partItem = category.getCurrentPart(); + + setMaxPaletteCount(partItem.maxColorIndex || 1); + }, [ model, category, setMaxPaletteCount ]); + + useEffect(() => + { + if(!model || !category || !elementRef || !elementRef.current) return; + + elementRef.current.scrollTop = 0; + }, [ model, category ]); + + return ( + + { (category.parts.length > 0) && category.parts.map((item, index) => + selectPart(item) } />) } + + ); +} diff --git a/src/components/avatar-editor/views/figure-set/index.ts b/src/components/avatar-editor/views/figure-set/index.ts new file mode 100644 index 0000000..0c5880b --- /dev/null +++ b/src/components/avatar-editor/views/figure-set/index.ts @@ -0,0 +1,2 @@ +export * from './AvatarEditorFigureSetItemView'; +export * from './AvatarEditorFigureSetView'; diff --git a/src/components/avatar-editor/views/index.ts b/src/components/avatar-editor/views/index.ts new file mode 100644 index 0000000..a92b3b7 --- /dev/null +++ b/src/components/avatar-editor/views/index.ts @@ -0,0 +1,6 @@ +export * from './AvatarEditorFigurePreviewView'; +export * from './AvatarEditorIcon'; +export * from './AvatarEditorModelView'; +export * from './AvatarEditorWardrobeView'; +export * from './figure-set'; +export * from './palette-set'; diff --git a/src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetItemView.tsx b/src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetItemView.tsx new file mode 100644 index 0000000..7868057 --- /dev/null +++ b/src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetItemView.tsx @@ -0,0 +1,32 @@ +import { FC, useEffect, useState } from 'react'; +import { AvatarEditorGridColorItem, GetConfigurationValue } from '../../../../api'; +import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common'; + +export interface AvatarEditorPaletteSetItemProps extends LayoutGridItemProps +{ + colorItem: AvatarEditorGridColorItem; +} + +export const AvatarEditorPaletteSetItem: FC = props => +{ + const { colorItem = null, children = null, ...rest } = props; + const [ updateId, setUpdateId ] = useState(-1); + + const hcDisabled = GetConfigurationValue('hc.disabled', false); + + useEffect(() => + { + const rerender = () => setUpdateId(prevValue => (prevValue + 1)); + + colorItem.notify = rerender; + + return () => colorItem.notify = null; + }, [ colorItem ]); + + return ( + + { !hcDisabled && colorItem.isHC && } + { children } + + ); +} diff --git a/src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetView.tsx b/src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetView.tsx new file mode 100644 index 0000000..c55dcb4 --- /dev/null +++ b/src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetView.tsx @@ -0,0 +1,41 @@ +import { FC, useCallback, useEffect, useRef } from 'react'; +import { AvatarEditorGridColorItem, CategoryData, IAvatarEditorCategoryModel } from '../../../../api'; +import { AutoGrid } from '../../../../common'; +import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView'; + +export interface AvatarEditorPaletteSetViewProps +{ + model: IAvatarEditorCategoryModel; + category: CategoryData; + paletteSet: AvatarEditorGridColorItem[]; + paletteIndex: number; +} + +export const AvatarEditorPaletteSetView: FC = props => +{ + const { model = null, category = null, paletteSet = [], paletteIndex = -1 } = props; + const elementRef = useRef(null); + + const selectColor = useCallback((item: AvatarEditorGridColorItem) => + { + const index = paletteSet.indexOf(item); + + if(index === -1) return; + + model.selectColor(category.name, index, paletteIndex); + }, [ model, category, paletteSet, paletteIndex ]); + + useEffect(() => + { + if(!model || !category || !elementRef || !elementRef.current) return; + + elementRef.current.scrollTop = 0; + }, [ model, category ]); + + return ( + + { (paletteSet.length > 0) && paletteSet.map((item, index) => + selectColor(item) } />) } + + ); +} diff --git a/src/components/avatar-editor/views/palette-set/index.ts b/src/components/avatar-editor/views/palette-set/index.ts new file mode 100644 index 0000000..977e5b9 --- /dev/null +++ b/src/components/avatar-editor/views/palette-set/index.ts @@ -0,0 +1,2 @@ +export * from './AvatarEditorPaletteSetItemView'; +export * from './AvatarEditorPaletteSetView'; diff --git a/src/components/camera/CameraWidgetView.scss b/src/components/camera/CameraWidgetView.scss new file mode 100644 index 0000000..aecd0b1 --- /dev/null +++ b/src/components/camera/CameraWidgetView.scss @@ -0,0 +1,133 @@ +.nitro-camera-capture { + position: relative; + + .header-close { + top: 8px; + right: 8px; + border-radius: $border-radius; + box-shadow: 0 0 0 1.5px $white; + border: 2px solid #921911; + background: repeating-linear-gradient( + rgba(245, 80, 65, 1), + rgba(245, 80, 65, 1) 50%, + rgba(194, 48, 39, 1) 50%, + rgba(194, 48, 39, 1) 100% + ); + cursor: pointer; + line-height: 1; + padding: 1px 3px; + + &:hover { + filter: brightness(1.2); + } + + &:active { + filter: brightness(0.8); + } + } + + .camera-area { + position: absolute; + top: 37px; + left: 10px; + width: 320px; + height: 320px; + } + + .camera-canvas { + position: relative; + width: 340px; + height: 462px; + background-image: url('@/assets/images/room-widgets/camera-widget/camera-spritesheet.png'); + background-position: -1px -1px; + z-index: 2; + + .camera-button { + width: 94px; + height: 94px; + cursor: pointer; + margin-top: 362px; + + background-image: url('@/assets/images/room-widgets/camera-widget/camera-spritesheet.png'); + background-position: -343px -321px; + + &:hover { + background-position: -535px -321px; + } + + &:active { + background-position: -439px -321px; + } + } + + .camera-view-finder { + background-image: url('@/assets/images/room-widgets/camera-widget/camera-spritesheet.png'); + background-position: -343px -1px; + } + + .camera-frame { + .camera-frame-preview-actions { + background: rgba(0, 0, 0, 0.5); + } + } + } + + .camera-roll { + width: 330px; + background: #bab8b4; + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; + border: 1px solid black; + box-shadow: inset 1px 0px white, inset -1px -1px white; + + img { + width: 56px; + height: 56px; + border: 1px solid black; + object-fit: contain; + image-rendering: initial; + } + } +} + +.nitro-camera-editor { + width: $camera-editor-width; + height: $camera-editor-height; + + .picture-preview { + width: 320px; + height: 320px; + } + + .layout-grid-item { + height: 60px !important; + max-height: 60px !important; + } + + .effect-thumbnail-image { + img { + width: 50px; + height: 50px; + image-rendering: auto; + object-fit: contain; + } + } + + .remove-effect { + position: absolute; + top: 1px; + right: 1px; + padding: 2px; + font-size: 10px; + min-height: unset; + } +} + +.nitro-camera-checkout { + width: $camera-checkout-width; + + .picture-preview { + width: 320px; + height: 320px; + } +} diff --git a/src/components/camera/CameraWidgetView.tsx b/src/components/camera/CameraWidgetView.tsx new file mode 100644 index 0000000..62af9ca --- /dev/null +++ b/src/components/camera/CameraWidgetView.tsx @@ -0,0 +1,96 @@ +import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker, RoomSessionEvent } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { useCamera, useNitroEvent } from '../../hooks'; +import { CameraWidgetCaptureView } from './views/CameraWidgetCaptureView'; +import { CameraWidgetCheckoutView } from './views/CameraWidgetCheckoutView'; +import { CameraWidgetEditorView } from './views/editor/CameraWidgetEditorView'; + +const MODE_NONE: number = 0; +const MODE_CAPTURE: number = 1; +const MODE_EDITOR: number = 2; +const MODE_CHECKOUT: number = 3; + +export const CameraWidgetView: FC<{}> = props => +{ + const [ mode, setMode ] = useState(MODE_NONE); + const [ base64Url, setSavedPictureUrl ] = useState(null); + const { availableEffects = [], selectedPictureIndex = -1, cameraRoll = [], setCameraRoll = null, myLevel = 0, price = { credits: 0, duckets: 0, publishDucketPrice: 0 }} = useCamera(); + + const processAction = (type: string) => + { + switch(type) + { + case 'close': + setMode(MODE_NONE); + return; + case 'edit': + setMode(MODE_EDITOR); + return; + case 'delete': + setCameraRoll(prevValue => + { + const clone = [ ...prevValue ]; + + clone.splice(selectedPictureIndex, 1); + + return clone; + }); + return; + case 'editor_cancel': + setMode(MODE_CAPTURE); + return; + } + } + + const checkoutPictureUrl = (pictureUrl: string) => + { + setSavedPictureUrl(pictureUrl); + setMode(MODE_CHECKOUT); + } + + useNitroEvent(RoomSessionEvent.ENDED, event => setMode(MODE_NONE)); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'show': + setMode(MODE_CAPTURE); + return; + case 'hide': + setMode(MODE_NONE); + return; + case 'toggle': + setMode(prevValue => + { + if(!prevValue) return MODE_CAPTURE; + else return MODE_NONE; + }); + return; + } + }, + eventUrlPrefix: 'camera/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + if(mode === MODE_NONE) return null; + + return ( + <> + { (mode === MODE_CAPTURE) && processAction('close') } onEdit={ () => processAction('edit') } onDelete={ () => processAction('delete') } /> } + { (mode === MODE_EDITOR) && processAction('close') } onCancel={ () => processAction('editor_cancel') } onCheckout={ checkoutPictureUrl } availableEffects={ availableEffects } /> } + { (mode === MODE_CHECKOUT) && processAction('close') } onCancelClick={ () => processAction('editor_cancel') } price={ price }> } + + ); +} diff --git a/src/components/camera/index.ts b/src/components/camera/index.ts new file mode 100644 index 0000000..43c10cd --- /dev/null +++ b/src/components/camera/index.ts @@ -0,0 +1,4 @@ +export * from './CameraWidgetView'; +export * from './views'; +export * from './views/editor'; +export * from './views/editor/effect-list'; diff --git a/src/components/camera/views/CameraWidgetCaptureView.tsx b/src/components/camera/views/CameraWidgetCaptureView.tsx new file mode 100644 index 0000000..ef92884 --- /dev/null +++ b/src/components/camera/views/CameraWidgetCaptureView.tsx @@ -0,0 +1,90 @@ +import { GetRoomEngine, NitroRectangle, TextureUtils } from '@nitrots/nitro-renderer'; +import { FC, useRef } from 'react'; +import { FaTimes } from 'react-icons/fa'; +import { CameraPicture, GetRoomSession, LocalizeText, PlaySound, SoundNames } from '../../../api'; +import { Column, DraggableWindow, Flex } from '../../../common'; +import { useCamera, useNotification } from '../../../hooks'; + +export interface CameraWidgetCaptureViewProps +{ + onClose: () => void; + onEdit: () => void; + onDelete: () => void; +} + +const CAMERA_ROLL_LIMIT: number = 5; + +export const CameraWidgetCaptureView: FC = props => +{ + const { onClose = null, onEdit = null, onDelete = null } = props; + const { cameraRoll = null, setCameraRoll = null, selectedPictureIndex = -1, setSelectedPictureIndex = null } = useCamera(); + const { simpleAlert = null } = useNotification(); + const elementRef = useRef(); + + const selectedPicture = ((selectedPictureIndex > -1) ? cameraRoll[selectedPictureIndex] : null); + + const getCameraBounds = () => + { + if(!elementRef || !elementRef.current) return null; + + const frameBounds = elementRef.current.getBoundingClientRect(); + + return new NitroRectangle(Math.floor(frameBounds.x), Math.floor(frameBounds.y), Math.floor(frameBounds.width), Math.floor(frameBounds.height)); + } + + const takePicture = async () => + { + if(selectedPictureIndex > -1) + { + setSelectedPictureIndex(-1); + return; + } + + const texture = GetRoomEngine().createTextureFromRoom(GetRoomSession().roomId, 1, getCameraBounds()); + + const clone = [ ...cameraRoll ]; + + if(clone.length >= CAMERA_ROLL_LIMIT) + { + simpleAlert(LocalizeText('camera.full.body')); + + clone.pop(); + } + + PlaySound(SoundNames.CAMERA_SHUTTER); + clone.push(new CameraPicture(texture, await TextureUtils.generateImageUrl(texture))); + + setCameraRoll(clone); + } + + return ( + + + { selectedPicture && } +
+
+ +
+ { !selectedPicture &&
} + { selectedPicture && +
+
+ + +
+
} +
+
+
+
+ { (cameraRoll.length > 0) && + + { cameraRoll.map((picture, index) => + { + return setSelectedPictureIndex(index) } />; + }) } + } + + + ); +} diff --git a/src/components/camera/views/CameraWidgetCheckoutView.tsx b/src/components/camera/views/CameraWidgetCheckoutView.tsx new file mode 100644 index 0000000..100e076 --- /dev/null +++ b/src/components/camera/views/CameraWidgetCheckoutView.tsx @@ -0,0 +1,159 @@ +import { CameraPublishStatusMessageEvent, CameraPurchaseOKMessageEvent, CameraStorageUrlMessageEvent, CreateLinkEvent, GetRoomEngine, PublishPhotoMessageComposer, PurchasePhotoMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useMemo, useState } from 'react'; +import { GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../../api'; +import { Button, Column, Flex, LayoutCurrencyIcon, LayoutImage, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; +import { useMessageEvent } from '../../../hooks'; + +export interface CameraWidgetCheckoutViewProps +{ + base64Url: string; + onCloseClick: () => void; + onCancelClick: () => void; + price: { credits: number, duckets: number, publishDucketPrice: number }; +} + +export const CameraWidgetCheckoutView: FC = props => +{ + const { base64Url = null, onCloseClick = null, onCancelClick = null, price = null } = props; + const [ pictureUrl, setPictureUrl ] = useState(null); + const [ publishUrl, setPublishUrl ] = useState(null); + const [ picturesBought, setPicturesBought ] = useState(0); + const [ wasPicturePublished, setWasPicturePublished ] = useState(false); + const [ isWaiting, setIsWaiting ] = useState(false); + const [ publishCooldown, setPublishCooldown ] = useState(0); + + const publishDisabled = useMemo(() => GetConfigurationValue('camera.publish.disabled', false), []); + + useMessageEvent(CameraPurchaseOKMessageEvent, event => + { + setPicturesBought(value => (value + 1)); + setIsWaiting(false); + }); + + useMessageEvent(CameraPublishStatusMessageEvent, event => + { + const parser = event.getParser(); + + setPublishUrl(parser.extraDataId); + setPublishCooldown(parser.secondsToWait); + setWasPicturePublished(parser.ok); + setIsWaiting(false); + }); + + useMessageEvent(CameraStorageUrlMessageEvent, event => + { + const parser = event.getParser(); + + setPictureUrl(GetConfigurationValue('camera.url') + '/' + parser.url); + }); + + const processAction = (type: string, value: string | number = null) => + { + switch(type) + { + case 'close': + onCloseClick(); + return; + case 'buy': + if(isWaiting) return; + + setIsWaiting(true); + SendMessageComposer(new PurchasePhotoMessageComposer('')); + return; + case 'publish': + if(isWaiting) return; + + setIsWaiting(true); + SendMessageComposer(new PublishPhotoMessageComposer()); + return; + case 'cancel': + onCancelClick(); + return; + } + } + + useEffect(() => + { + if(!base64Url) return; + + GetRoomEngine().saveBase64AsScreenshot(base64Url); + }, [ base64Url ]); + + if(!price) return null; + + return ( + + processAction('close') } /> + + + { (pictureUrl && pictureUrl.length) && + } + { (!pictureUrl || !pictureUrl.length) && + + { LocalizeText('camera.loading') } + } + + + + + { LocalizeText('camera.purchase.header') } + + { ((price.credits > 0) || (price.duckets > 0)) && + + { LocalizeText('catalog.purchase.confirmation.dialog.cost') } + { (price.credits > 0) && + + { price.credits } + + } + { (price.duckets > 0) && + + { price.duckets } + + } + } + { (picturesBought > 0) && + + { LocalizeText('camera.purchase.count.info') } { picturesBought } + CreateLinkEvent('inventory/toggle') }>{ LocalizeText('camera.open.inventory') } + } + + + + + + { !publishDisabled && + + + + { LocalizeText(wasPicturePublished ? 'camera.publish.successful' : 'camera.publish.explanation') } + + + { LocalizeText(wasPicturePublished ? 'camera.publish.success.short.info' : 'camera.publish.detailed.explanation') } + + { wasPicturePublished &&
{ LocalizeText('camera.link.to.published') } } + { !wasPicturePublished && (price.publishDucketPrice > 0) && + + { LocalizeText('catalog.purchase.confirmation.dialog.cost') } + + { price.publishDucketPrice } + + + } + { (publishCooldown > 0) &&
{ LocalizeText('camera.publish.wait', [ 'minutes' ], [ Math.ceil( publishCooldown / 60).toString() ]) }
} + + { !wasPicturePublished && + + + } + } + { LocalizeText('camera.warning.disclaimer') } + + + + + + ); +} diff --git a/src/components/camera/views/CameraWidgetShowPhotoView.tsx b/src/components/camera/views/CameraWidgetShowPhotoView.tsx new file mode 100644 index 0000000..8614695 --- /dev/null +++ b/src/components/camera/views/CameraWidgetShowPhotoView.tsx @@ -0,0 +1,71 @@ +import { FC, useEffect, useState } from 'react'; +import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'; +import { GetUserProfile, IPhotoData, LocalizeText } from '../../../api'; +import { Flex, Grid, Text } from '../../../common'; + +export interface CameraWidgetShowPhotoViewProps +{ + currentIndex: number; + currentPhotos: IPhotoData[]; +} + +export const CameraWidgetShowPhotoView: FC = props => +{ + const { currentIndex = -1, currentPhotos = null } = props; + const [ imageIndex, setImageIndex ] = useState(0); + + const currentImage = (currentPhotos && currentPhotos.length) ? currentPhotos[imageIndex] : null; + + const next = () => + { + setImageIndex(prevValue => + { + let newIndex = (prevValue + 1); + + if(newIndex >= currentPhotos.length) newIndex = 0; + + return newIndex; + }); + } + + const previous = () => + { + setImageIndex(prevValue => + { + let newIndex = (prevValue - 1); + + if(newIndex < 0) newIndex = (currentPhotos.length - 1); + + return newIndex; + }); + } + + useEffect(() => + { + setImageIndex(currentIndex); + }, [ currentIndex ]); + + if(!currentImage) return null; + + return ( + + + { !currentImage.w && + { LocalizeText('camera.loading') } } + + { currentImage.m && currentImage.m.length && + { currentImage.m } } + + { (currentImage.n || '') } + { new Date(currentImage.t * 1000).toLocaleDateString() } + + { (currentPhotos.length > 1) && + + + GetUserProfile(currentImage.oi) }>{ currentImage.o } + + + } + + ); +} diff --git a/src/components/camera/views/editor/CameraWidgetEditorView.tsx b/src/components/camera/views/editor/CameraWidgetEditorView.tsx new file mode 100644 index 0000000..1946c64 --- /dev/null +++ b/src/components/camera/views/editor/CameraWidgetEditorView.tsx @@ -0,0 +1,243 @@ +import { GetRoomCameraWidgetManager, IRoomCameraWidgetEffect, IRoomCameraWidgetSelectedEffect, RoomCameraWidgetSelectedEffect } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { FaSave, FaSearchMinus, FaSearchPlus, FaTrash } from 'react-icons/fa'; +import ReactSlider from 'react-slider'; +import { CameraEditorTabs, CameraPicture, CameraPictureThumbnail, LocalizeText } from '../../../../api'; +import { Button, ButtonGroup, Column, Flex, Grid, LayoutImage, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text } from '../../../../common'; +import { CameraWidgetEffectListView } from './effect-list'; + +export interface CameraWidgetEditorViewProps +{ + picture: CameraPicture; + availableEffects: IRoomCameraWidgetEffect[]; + myLevel: number; + onClose: () => void; + onCancel: () => void; + onCheckout: (pictureUrl: string) => void; +} + +const TABS: string[] = [ CameraEditorTabs.COLORMATRIX, CameraEditorTabs.COMPOSITE ]; + +export const CameraWidgetEditorView: FC = props => +{ + const { picture = null, availableEffects = null, myLevel = 1, onClose = null, onCancel = null, onCheckout = null } = props; + const [ currentTab, setCurrentTab ] = useState(TABS[0]); + const [ selectedEffectName, setSelectedEffectName ] = useState(null); + const [ selectedEffects, setSelectedEffects ] = useState([]); + const [ effectsThumbnails, setEffectsThumbnails ] = useState([]); + const [ isZoomed, setIsZoomed ] = useState(false); + const [ currentPictureUrl, setCurrentPictureUrl ] = useState(''); + + const getColorMatrixEffects = useMemo(() => + { + return availableEffects.filter(effect => effect.colorMatrix); + }, [ availableEffects ]); + + const getCompositeEffects = useMemo(() => + { + return availableEffects.filter(effect => effect.texture); + }, [ availableEffects ]); + + const getEffectList = useCallback(() => + { + if(currentTab === CameraEditorTabs.COLORMATRIX) + { + return getColorMatrixEffects; + } + + return getCompositeEffects; + }, [ currentTab, getColorMatrixEffects, getCompositeEffects ]); + + const getSelectedEffectIndex = useCallback((name: string) => + { + if(!name || !name.length || !selectedEffects || !selectedEffects.length) return -1; + + return selectedEffects.findIndex(effect => (effect.effect.name === name)); + }, [ selectedEffects ]) + + const getCurrentEffectIndex = useMemo(() => + { + return getSelectedEffectIndex(selectedEffectName) + }, [ selectedEffectName, getSelectedEffectIndex ]) + + const getCurrentEffect = useMemo(() => + { + if(!selectedEffectName) return null; + + return (selectedEffects[getCurrentEffectIndex] || null); + }, [ selectedEffectName, getCurrentEffectIndex, selectedEffects ]); + + const setSelectedEffectAlpha = useCallback((alpha: number) => + { + const index = getCurrentEffectIndex; + + if(index === -1) return; + + setSelectedEffects(prevValue => + { + const clone = [ ...prevValue ]; + const currentEffect = clone[index]; + + clone[getCurrentEffectIndex] = new RoomCameraWidgetSelectedEffect(currentEffect.effect, alpha); + + return clone; + }); + }, [ getCurrentEffectIndex, setSelectedEffects ]); + + const processAction = useCallback((type: string, effectName: string = null) => + { + switch(type) + { + case 'close': + onClose(); + return; + case 'cancel': + onCancel(); + return; + case 'checkout': + onCheckout(currentPictureUrl); + return; + case 'change_tab': + setCurrentTab(String(effectName)); + return; + case 'select_effect': { + let existingIndex = getSelectedEffectIndex(effectName); + + if(existingIndex >= 0) return; + + const effect = availableEffects.find(effect => (effect.name === effectName)); + + if(!effect) return; + + setSelectedEffects(prevValue => + { + return [ ...prevValue, new RoomCameraWidgetSelectedEffect(effect, 1) ]; + }); + + setSelectedEffectName(effect.name); + return; + } + case 'remove_effect': { + let existingIndex = getSelectedEffectIndex(effectName); + + if(existingIndex === -1) return; + + setSelectedEffects(prevValue => + { + const clone = [ ...prevValue ]; + + clone.splice(existingIndex, 1); + + return clone; + }); + + if(selectedEffectName === effectName) setSelectedEffectName(null); + return; + } + case 'clear_effects': + setSelectedEffectName(null); + setSelectedEffects([]); + return; + case 'download': { + (async () => + { + const image = new Image(); + + image.src = currentPictureUrl + + const newWindow = window.open(''); + newWindow.document.write(image.outerHTML); + })(); + return; + } + case 'zoom': + setIsZoomed(!isZoomed); + return; + } + }, [ isZoomed, availableEffects, selectedEffectName, currentPictureUrl, getSelectedEffectIndex, onCancel, onCheckout, onClose, setIsZoomed, setSelectedEffects ]); + + useEffect(() => + { + (async () => + { + const thumbnails: CameraPictureThumbnail[] = []; + + for await (const effect of availableEffects) + { + const image = await GetRoomCameraWidgetManager().applyEffects(picture.texture, [ new RoomCameraWidgetSelectedEffect(effect, 1) ], false); + + thumbnails.push(new CameraPictureThumbnail(effect.name, image.src)); + } + + setEffectsThumbnails(thumbnails); + })(); + }, [ picture, availableEffects ]); + + useEffect(() => + { + (async () => + { + const imageUrl = await GetRoomCameraWidgetManager().applyEffects(picture.texture, selectedEffects, isZoomed); + + setCurrentPictureUrl(imageUrl.src); + })(); + }, [ picture, selectedEffects, isZoomed ]); + + return ( + + processAction('close') } /> + + { TABS.map(tab => + { + return processAction('change_tab', tab) }> + }) } + + + + + + + + + + { selectedEffectName && + + { LocalizeText('camera.effect.name.' + selectedEffectName) } + setSelectedEffectAlpha(event) } + renderThumb={ (props, state) =>
{ state.valueNow }
} /> +
} +
+ + + + + + + + + + + +
+
+
+
+ ); +} diff --git a/src/components/camera/views/editor/effect-list/CameraWidgetEffectListItemView.tsx b/src/components/camera/views/editor/effect-list/CameraWidgetEffectListItemView.tsx new file mode 100644 index 0000000..cac3a34 --- /dev/null +++ b/src/components/camera/views/editor/effect-list/CameraWidgetEffectListItemView.tsx @@ -0,0 +1,40 @@ +import { IRoomCameraWidgetEffect } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { FaLock, FaTimes } from 'react-icons/fa'; +import { LocalizeText } from '../../../../../api'; +import { Button, LayoutGridItem, Text } from '../../../../../common'; + +export interface CameraWidgetEffectListItemViewProps +{ + effect: IRoomCameraWidgetEffect; + thumbnailUrl: string; + isActive: boolean; + isLocked: boolean; + selectEffect: () => void; + removeEffect: () => void; +} + +export const CameraWidgetEffectListItemView: FC = props => +{ + const { effect = null, thumbnailUrl = null, isActive = false, isLocked = false, selectEffect = null, removeEffect = null } = props; + + return ( + (!isActive && selectEffect()) }> + { isActive && + } + { !isLocked && (thumbnailUrl && thumbnailUrl.length > 0) && +
+ +
} + { isLocked && + +
+ +
+ { effect.minLevel } +
} +
+ ); +} diff --git a/src/components/camera/views/editor/effect-list/CameraWidgetEffectListView.tsx b/src/components/camera/views/editor/effect-list/CameraWidgetEffectListView.tsx new file mode 100644 index 0000000..3f67cea --- /dev/null +++ b/src/components/camera/views/editor/effect-list/CameraWidgetEffectListView.tsx @@ -0,0 +1,31 @@ +import { IRoomCameraWidgetEffect, IRoomCameraWidgetSelectedEffect } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { CameraPictureThumbnail } from '../../../../../api'; +import { Grid } from '../../../../../common'; +import { CameraWidgetEffectListItemView } from './CameraWidgetEffectListItemView'; + +export interface CameraWidgetEffectListViewProps +{ + myLevel: number; + selectedEffects: IRoomCameraWidgetSelectedEffect[]; + effects: IRoomCameraWidgetEffect[]; + thumbnails: CameraPictureThumbnail[]; + processAction: (type: string, name: string) => void; +} + +export const CameraWidgetEffectListView: FC = props => +{ + const { myLevel = 0, selectedEffects = [], effects = [], thumbnails = [], processAction = null } = props; + + return ( + + { effects && (effects.length > 0) && effects.map((effect, index) => + { + const thumbnailUrl = (thumbnails.find(thumbnail => (thumbnail.effectName === effect.name))); + const isActive = (selectedEffects.findIndex(selectedEffect => (selectedEffect.effect.name === effect.name)) > -1); + + return myLevel) } selectEffect={ () => processAction('select_effect', effect.name) } removeEffect={ () => processAction('remove_effect', effect.name) } /> + }) } + + ); +} diff --git a/src/components/camera/views/editor/effect-list/index.ts b/src/components/camera/views/editor/effect-list/index.ts new file mode 100644 index 0000000..7a4ebdb --- /dev/null +++ b/src/components/camera/views/editor/effect-list/index.ts @@ -0,0 +1,2 @@ +export * from './CameraWidgetEffectListItemView'; +export * from './CameraWidgetEffectListView'; diff --git a/src/components/camera/views/editor/index.ts b/src/components/camera/views/editor/index.ts new file mode 100644 index 0000000..49c615e --- /dev/null +++ b/src/components/camera/views/editor/index.ts @@ -0,0 +1,2 @@ +export * from './CameraWidgetEditorView'; +export * from './effect-list'; diff --git a/src/components/camera/views/index.ts b/src/components/camera/views/index.ts new file mode 100644 index 0000000..cf44449 --- /dev/null +++ b/src/components/camera/views/index.ts @@ -0,0 +1,5 @@ +export * from './CameraWidgetCaptureView'; +export * from './CameraWidgetCheckoutView'; +export * from './CameraWidgetShowPhotoView'; +export * from './editor'; +export * from './editor/effect-list'; diff --git a/src/components/campaign/CalendarItemView.tsx b/src/components/campaign/CalendarItemView.tsx new file mode 100644 index 0000000..040414b --- /dev/null +++ b/src/components/campaign/CalendarItemView.tsx @@ -0,0 +1,53 @@ +import { GetRoomEngine, GetSessionDataManager } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { CalendarItemState, GetConfigurationValue, ICalendarItem } from '../../api'; +import { Base, Column, Flex, LayoutImage } from '../../common'; + +interface CalendarItemViewProps +{ + itemId: number; + state: number; + active?: boolean; + product?: ICalendarItem; + onClick: (itemId: number) => void; +} + +export const CalendarItemView: FC = props => +{ + const { itemId = -1, state = null, product = null, active = false, onClick = null } = props; + + const getFurnitureIcon = (name: string) => + { + let furniData = GetSessionDataManager().getFloorItemDataByName(name); + let url = null; + + if(furniData) url = GetRoomEngine().getFurnitureFloorIconUrl(furniData.id); + else + { + furniData = GetSessionDataManager().getWallItemDataByName(name); + + if(furniData) url = GetRoomEngine().getFurnitureWallIconUrl(furniData.id); + } + + return url; + } + + return ( + onClick(itemId) }> + { (state === CalendarItemState.STATE_UNLOCKED) && + + + { product && + ('image.library.url') + product.customImage : getFurnitureIcon(product.productName) } /> } + + } + { (state !== CalendarItemState.STATE_UNLOCKED) && + + { (state === CalendarItemState.STATE_LOCKED_AVAILABLE) && + } + { ((state === CalendarItemState.STATE_LOCKED_EXPIRED) || (state === CalendarItemState.STATE_LOCKED_FUTURE)) && + } + } + + ); +} diff --git a/src/components/campaign/CalendarView.tsx b/src/components/campaign/CalendarView.tsx new file mode 100644 index 0000000..3178f91 --- /dev/null +++ b/src/components/campaign/CalendarView.tsx @@ -0,0 +1,144 @@ +import { GetSessionDataManager } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { CalendarItemState, ICalendarItem, LocalizeText } from '../../api'; +import { Base, Button, Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common'; +import { CalendarItemView } from './CalendarItemView'; + +interface CalendarViewProps +{ + onClose(): void; + openPackage(id: number, asStaff: boolean): void; + receivedProducts: Map; + campaignName: string; + currentDay: number; + numDays: number; + openedDays: number[]; + missedDays: number[]; +} + +const TOTAL_SHOWN_ITEMS = 5; + +export const CalendarView: FC = props => +{ + const { onClose = null, campaignName = null, currentDay = null, numDays = null, missedDays = null, openedDays = null, openPackage = null, receivedProducts = null } = props; + const [ selectedDay, setSelectedDay ] = useState(currentDay); + const [ index, setIndex ] = useState(Math.max(0, (selectedDay - 1))); + + const getDayState = (day: number) => + { + if(openedDays.includes(day)) return CalendarItemState.STATE_UNLOCKED; + + if(day > currentDay) return CalendarItemState.STATE_LOCKED_FUTURE; + + if(missedDays.includes(day)) return CalendarItemState.STATE_LOCKED_EXPIRED; + + return CalendarItemState.STATE_LOCKED_AVAILABLE; + } + + const dayMessage = (day: number) => + { + const state = getDayState(day); + + switch(state) + { + case CalendarItemState.STATE_UNLOCKED: + return LocalizeText('campaign.calendar.info.unlocked'); + case CalendarItemState.STATE_LOCKED_FUTURE: + return LocalizeText('campaign.calendar.info.future'); + case CalendarItemState.STATE_LOCKED_EXPIRED: + return LocalizeText('campaign.calendar.info.expired'); + default: + return LocalizeText('campaign.calendar.info.available.desktop'); + } + } + + const onClickNext = () => + { + const nextDay = (selectedDay + 1); + + if(nextDay === numDays) return; + + setSelectedDay(nextDay); + + if((index + TOTAL_SHOWN_ITEMS) < (nextDay + 1)) setIndex(index + 1); + } + + const onClickPrev = () => + { + const prevDay = (selectedDay - 1); + + if(prevDay < 0) return; + + setSelectedDay(prevDay); + + if(index > prevDay) setIndex(index - 1); + } + + const onClickItem = (item: number) => + { + if(selectedDay === item) + { + const state = getDayState(item); + + if(state === CalendarItemState.STATE_LOCKED_AVAILABLE) openPackage(item, false); + + return; + } + + setSelectedDay(item); + } + + const forceOpen = () => + { + const id = selectedDay; + const state = getDayState(id); + + if(state !== CalendarItemState.STATE_UNLOCKED) openPackage(id, true); + } + + return ( + + + + + + + + + { LocalizeText('campaign.calendar.heading.day', [ 'number' ], [ (selectedDay + 1).toString() ]) } + { dayMessage(selectedDay) } + +
+ { GetSessionDataManager().isModerator && + } +
+
+
+ +
+ + + + + + + { [ ...Array(TOTAL_SHOWN_ITEMS) ].map((e, i) => + { + const day = (index + i); + + return ( + + + + ); + }) } + + + + + + +
+
+ ) +} diff --git a/src/components/campaign/CampaignView.scss b/src/components/campaign/CampaignView.scss new file mode 100644 index 0000000..cea973f --- /dev/null +++ b/src/components/campaign/CampaignView.scss @@ -0,0 +1,71 @@ +.nitro-campaign-calendar { + width: $nitro-calendar-width; + height: $nitro-calendar-height; + + .calendar-item { + filter: brightness(80%); + + &.active { + filter: brightness(100%); + } + } +} + +.campaign-spritesheet { + display: block; + background: transparent url('@/assets/images/campaign/campaign_spritesheet.png') no-repeat; + + &.available { + width: 69px; + height: 78px; + background-position: -5px -5px; + } + + &.campaign-day-generic-bg { + max-width: 202px; + height: 447px; + background-position: -84px -5px; + } + + &.campaign-opened { + width: 96px; + height: 66px; + background-position: -296px -5px; + } + + &.locked { + width: 42px; + height: 42px; + background-position: -296px -81px; + } + + &.locked-bg { + width: 132px; + height: 132px; + background-position: -402px -5px; + } + + &.next { + width: 33px; + height: 34px; + background-position: -5px -147px; + } + + &.prev { + width: 33px; + height: 34px; + background-position: -296px -147px; + } + + &.unavailable { + width: 68px; + height: 78px; + background-position: -339px -147px; + } + + &.unlocked-bg { + width: 190px; + height: 189px; + background-position: -296px -235px; + } +} diff --git a/src/components/campaign/CampaignView.tsx b/src/components/campaign/CampaignView.tsx new file mode 100644 index 0000000..5e10927 --- /dev/null +++ b/src/components/campaign/CampaignView.tsx @@ -0,0 +1,101 @@ +import { AddLinkEventTracker, CampaignCalendarData, CampaignCalendarDataMessageEvent, CampaignCalendarDoorOpenedMessageEvent, ILinkEventTracker, OpenCampaignCalendarDoorAsStaffComposer, OpenCampaignCalendarDoorComposer, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { CalendarItem, SendMessageComposer } from '../../api'; +import { useMessageEvent } from '../../hooks'; +import { CalendarView } from './CalendarView'; + +export const CampaignView: FC<{}> = props => +{ + const [ calendarData, setCalendarData ] = useState(null); + const [ lastOpenAttempt, setLastOpenAttempt ] = useState(-1); + const [ receivedProducts, setReceivedProducts ] = useState>(new Map()); + const [ isCalendarOpen, setCalendarOpen ] = useState(false); + + const openPackage = (id: number, asStaff = false) => + { + if(!calendarData) return; + + setLastOpenAttempt(id); + + if(asStaff) + { + SendMessageComposer(new OpenCampaignCalendarDoorAsStaffComposer(calendarData.campaignName, id)); + } + + else + { + SendMessageComposer(new OpenCampaignCalendarDoorComposer(calendarData.campaignName, id)); + } + } + + useMessageEvent(CampaignCalendarDataMessageEvent, event => + { + const parser = event.getParser(); + + if(!parser) return; + + setCalendarData(parser.calendarData); + }); + + useMessageEvent(CampaignCalendarDoorOpenedMessageEvent, event => + { + const parser = event.getParser(); + + if(!parser) return; + + const lastAttempt = lastOpenAttempt; + + if(parser.doorOpened) + { + setCalendarData(prev => + { + const copy = prev.clone(); + copy.openedDays.push(lastOpenAttempt); + + return copy; + }); + + setReceivedProducts(prev => + { + const copy = new Map(prev); + copy.set(lastAttempt, new CalendarItem(parser.productName, parser.customImage,parser.furnitureClassName)); + + return copy; + }); + } + + setLastOpenAttempt(-1); + }); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const value = url.split('/'); + + if(value.length < 2) return; + + switch(value[1]) + { + case 'calendar': + setCalendarOpen(true); + break; + } + }, + eventUrlPrefix: 'openView/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + return ( + <> + { (calendarData && isCalendarOpen) && + setCalendarOpen(false) } campaignName={ calendarData.campaignName } currentDay={ calendarData.currentDay } numDays={ calendarData.campaignDays } openedDays={ calendarData.openedDays } missedDays={ calendarData.missedDays } openPackage={ openPackage } receivedProducts={ receivedProducts } /> + } + + ) +} diff --git a/src/components/catalog/CatalogView.scss b/src/components/catalog/CatalogView.scss new file mode 100644 index 0000000..824ddb7 --- /dev/null +++ b/src/components/catalog/CatalogView.scss @@ -0,0 +1,158 @@ +.nitro-catalog { + width: $catalog-width; + height: $catalog-height; + + font[size='16'] { + font-size: 20px; + } + + .catalog-search-button { + min-width: 30px; + width: 30px; + } + + .quantity-input { + min-height: 17px; + height: 17px; + width: 28px; + padding: 0 4px; + text-align: right; + } +} + +.nitro-catalog-gift { + width: 325px; + + .gift-preview { + width: 80px; + height: 80px; + overflow: hidden; + display: flex; + justify-content: center; + align-items: center; + } + + .gift-color { + width: 15px; + height: 15px; + border-radius: $border-radius; + } +} + +.nitro-catalog-navigation-grid-container { + border-color: #b6bec5 !important; + background-color: #cdd3d9; + border: 2px solid; + + .nitro-catalog-navigation-section { + display: grid; + + .nitro-catalog-navigation-section { + padding-left: 5px; + border-left: 2px solid #b6bec5; + } + } + + .layout-grid-item { + font-size: $font-size-sm; + height: 23px !important; + border-color: unset !important; + background-color: #cdd3d9; + border: 0 !important; + padding: 1px 3px; + + .svg-inline--fa { + color: $black; + font-size: 10px; + padding: 1px; + } + } +} + +.nitro-catalog-layout-info-loyalty { + .info-loyalty-content { + background-repeat: no-repeat; + background-position: top right; + background-image: url('@/assets/images/catalog/diamond_info_illustration.gif'); + padding-right: 123px; + } + + .info-image { + width: 123px; + height: 350px; + background-image: url('@/assets/images/catalog/diamond_info_illustration.gif'); + } +} + +.nitro-catalog-layout-vip-buy-grid { + .layout-grid-item { + height: 50px !important; + max-height: 50px !important; + + .icon-hc-banner { + width: 68px; + height: 40px; + background: url('@/assets/images/catalog/hc_big.png') center + no-repeat; + } + } +} + +.nitro-catalog-layout-marketplace-grid { + .layout-grid-item { + height: 75px !important; + } +} + +.nitro-catalog-layout-vip-gifts-grid { + .layout-grid-item { + height: 55px !important; + max-height: 55px !important; + } +} + +.nitro-catalog-layout-marketplace-post-offer { + width: $marketplace-post-offer-width; + height: $marketplace-post-offer-height; +} + +.nitro-catalog-layout-bundle-grid { + .layout-grid-item { + background-color: transparent; + } +} + +.nitro-catalog-header { + width: 290px; + height: 60px; +} + +.autocomplete-gift-container { + background: #fff; + padding: 8px; + list-style-type: none; + min-width: 307px; + border-radius: 0.2rem; + position: absolute; + font-size: 0.7875rem; + top: 81px; + left: 8px; + border: 1px solid #b6c1ce; + margin: 0; + border-radius: 2px; + margin: 0; + box-sizing: border-box; + max-height: 280px; + overflow-y: auto; + z-index: 1; + + .autocomplete-gift-item { + width: 100%; + box-sizing: border-box; + &:hover { + background-color: #ebf4ff; + } + } +} + +@import './views/targeted-offer/Offer.scss'; diff --git a/src/components/catalog/CatalogView.tsx b/src/components/catalog/CatalogView.tsx new file mode 100644 index 0000000..3df32be --- /dev/null +++ b/src/components/catalog/CatalogView.tsx @@ -0,0 +1,111 @@ +import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; +import { FC, useEffect } from 'react'; +import { GetConfigurationValue, LocalizeText } from '../../api'; +import { Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common'; +import { useCatalog } from '../../hooks'; +import { CatalogIconView } from './views/catalog-icon/CatalogIconView'; +import { CatalogGiftView } from './views/gift/CatalogGiftView'; +import { CatalogNavigationView } from './views/navigation/CatalogNavigationView'; +import { GetCatalogLayout } from './views/page/layout/GetCatalogLayout'; +import { MarketplacePostOfferView } from './views/page/layout/marketplace/MarketplacePostOfferView'; + +export const CatalogView: FC<{}> = props => +{ + const { isVisible = false, setIsVisible = null, rootNode = null, currentPage = null, navigationHidden = false, setNavigationHidden = null, activeNodes = [], searchResult = null, setSearchResult = null, openPageByName = null, openPageByOfferId = null, activateNode = null, getNodeById } = useCatalog(); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'show': + setIsVisible(true); + return; + case 'hide': + setIsVisible(false); + return; + case 'toggle': + setIsVisible(prevValue => !prevValue); + return; + case 'open': + if(parts.length > 2) + { + if(parts.length === 4) + { + switch(parts[2]) + { + case 'offerId': + openPageByOfferId(parseInt(parts[3])); + return; + } + } + else + { + openPageByName(parts[2]); + } + } + else + { + setIsVisible(true); + } + + return; + } + }, + eventUrlPrefix: 'catalog/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, [ setIsVisible, openPageByOfferId, openPageByName ]); + + return ( + <> + { isVisible && + + setIsVisible(false) } /> + + { rootNode && (rootNode.children.length > 0) && rootNode.children.map(child => + { + if(!child.isVisible) return null; + + return ( + + { + if(searchResult) setSearchResult(null); + + activateNode(child); + } } > + + { GetConfigurationValue('catalog.tab.icons') && } + { child.localization } + + + ); + }) } + + + + { !navigationHidden && + + { activeNodes && (activeNodes.length > 0) && + } + } + + { GetCatalogLayout(currentPage, () => setNavigationHidden(true)) } + + + + } + + + + ); +} diff --git a/src/components/catalog/views/CatalogPurchaseConfirmView.tsx b/src/components/catalog/views/CatalogPurchaseConfirmView.tsx new file mode 100644 index 0000000..30dcfc3 --- /dev/null +++ b/src/components/catalog/views/CatalogPurchaseConfirmView.tsx @@ -0,0 +1,10 @@ +import { FC } from 'react'; + +export const CatalogPurchaseConfirmView: FC<{}> = props => +{ + const {} = props; + + return ( +
+ ); +} diff --git a/src/components/catalog/views/catalog-header/CatalogHeaderView.tsx b/src/components/catalog/views/catalog-header/CatalogHeaderView.tsx new file mode 100644 index 0000000..9c6bbe0 --- /dev/null +++ b/src/components/catalog/views/catalog-header/CatalogHeaderView.tsx @@ -0,0 +1,26 @@ +import { FC, useEffect, useState } from 'react'; +import { GetConfigurationValue } from '../../../../api'; +import { Flex } from '../../../../common'; + +export interface CatalogHeaderViewProps +{ + imageUrl?: string; +} + +export const CatalogHeaderView: FC = props => +{ + const { imageUrl = null } = props; + const [ displayImageUrl, setDisplayImageUrl ] = useState(''); + + useEffect(() => + { + setDisplayImageUrl(imageUrl ?? GetConfigurationValue('catalog.asset.image.url').replace('%name%', 'catalog_header_roombuilder')); + }, [ imageUrl ]); + + return + + { + currentTarget.src = GetConfigurationValue('catalog.asset.image.url').replace('%name%', 'catalog_header_roombuilder'); + } } /> + ; +} diff --git a/src/components/catalog/views/catalog-icon/CatalogIconView.tsx b/src/components/catalog/views/catalog-icon/CatalogIconView.tsx new file mode 100644 index 0000000..2376047 --- /dev/null +++ b/src/components/catalog/views/catalog-icon/CatalogIconView.tsx @@ -0,0 +1,20 @@ +import { FC, useMemo } from 'react'; +import { GetConfigurationValue } from '../../../../api'; +import { LayoutImage } from '../../../../common'; + +export interface CatalogIconViewProps +{ + icon: number; +} + +export const CatalogIconView: FC = props => +{ + const { icon = 0 } = props; + + const getIconUrl = useMemo(() => + { + return ((GetConfigurationValue('catalog.asset.icon.url')).replace('%name%', icon.toString())); + }, [ icon ]); + + return ; +} diff --git a/src/components/catalog/views/catalog-room-previewer/CatalogRoomPreviewerView.tsx b/src/components/catalog/views/catalog-room-previewer/CatalogRoomPreviewerView.tsx new file mode 100644 index 0000000..0e460e5 --- /dev/null +++ b/src/components/catalog/views/catalog-room-previewer/CatalogRoomPreviewerView.tsx @@ -0,0 +1,44 @@ +import { GetEventDispatcher, NitroToolbarAnimateIconEvent, TextureUtils, ToolbarIconEnum } from '@nitrots/nitro-renderer'; +import { FC, useRef } from 'react'; +import { LayoutRoomPreviewerView, LayoutRoomPreviewerViewProps } from '../../../../common'; +import { CatalogPurchasedEvent } from '../../../../events'; +import { useUiEvent } from '../../../../hooks'; + +export const CatalogRoomPreviewerView: FC = props => +{ + const { roomPreviewer = null } = props; + const elementRef = useRef(null); + + useUiEvent(CatalogPurchasedEvent.PURCHASE_SUCCESS, event => + { + if(!elementRef) return; + + const renderTexture = roomPreviewer.getRoomObjectCurrentImage(); + + if(!renderTexture) return; + + (async () => + { + const image = await TextureUtils.generateImage(renderTexture); + + if(!image) return; + + const bounds = elementRef.current.getBoundingClientRect(); + + const x = (bounds.x + (bounds.width / 2)); + const y = (bounds.y + (bounds.height / 2)); + + const animateEvent = new NitroToolbarAnimateIconEvent(image, x, y); + + animateEvent.iconName = ToolbarIconEnum.INVENTORY; + + GetEventDispatcher().dispatchEvent(animateEvent); + })(); + }); + + return ( +
+ +
+ ); +} diff --git a/src/components/catalog/views/gift/CatalogGiftView.tsx b/src/components/catalog/views/gift/CatalogGiftView.tsx new file mode 100644 index 0000000..3ac6f25 --- /dev/null +++ b/src/components/catalog/views/gift/CatalogGiftView.tsx @@ -0,0 +1,289 @@ +import { GetSessionDataManager, GiftReceiverNotFoundEvent, PurchaseFromCatalogAsGiftComposer } from '@nitrots/nitro-renderer'; +import { ChangeEvent, FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; +import { ColorUtils, LocalizeText, MessengerFriend, ProductTypeEnum, SendMessageComposer } from '../../../../api'; +import { Base, Button, ButtonGroup, Column, Flex, FormGroup, LayoutCurrencyIcon, LayoutFurniImageView, LayoutGiftTagView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text, classNames } from '../../../../common'; +import { CatalogEvent, CatalogInitGiftEvent, CatalogPurchasedEvent } from '../../../../events'; +import { useCatalog, useFriends, useMessageEvent, useUiEvent } from '../../../../hooks'; + +export const CatalogGiftView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ pageId, setPageId ] = useState(0); + const [ offerId, setOfferId ] = useState(0); + const [ extraData, setExtraData ] = useState(''); + const [ receiverName, setReceiverName ] = useState(''); + const [ showMyFace, setShowMyFace ] = useState(true); + const [ message, setMessage ] = useState(''); + const [ colors, setColors ] = useState<{ id: number, color: string }[]>([]); + const [ selectedBoxIndex, setSelectedBoxIndex ] = useState(0); + const [ selectedRibbonIndex, setSelectedRibbonIndex ] = useState(0); + const [ selectedColorId, setSelectedColorId ] = useState(0); + const [ maxBoxIndex, setMaxBoxIndex ] = useState(0); + const [ maxRibbonIndex, setMaxRibbonIndex ] = useState(0); + const [ receiverNotFound, setReceiverNotFound ] = useState(false); + const { catalogOptions = null } = useCatalog(); + const { friends } = useFriends(); + const { giftConfiguration = null } = catalogOptions; + const [ boxTypes, setBoxTypes ] = useState([]); + const [ suggestions, setSuggestions ] = useState([]); + const [ isAutocompleteVisible, setIsAutocompleteVisible ] = useState(true); + + const onClose = useCallback(() => + { + setIsVisible(false); + setPageId(0); + setOfferId(0); + setExtraData(''); + setReceiverName(''); + setShowMyFace(true); + setMessage(''); + setSelectedBoxIndex(0); + setSelectedRibbonIndex(0); + setIsAutocompleteVisible(false); + setSuggestions([]); + + if(colors.length) setSelectedColorId(colors[0].id); + }, [ colors ]); + + const isBoxDefault = useMemo(() => + { + return giftConfiguration ? (giftConfiguration.defaultStuffTypes.findIndex(s => (s === boxTypes[selectedBoxIndex])) > -1) : false; + }, [ boxTypes, giftConfiguration, selectedBoxIndex ]); + + const boxExtraData = useMemo(() => + { + if (!giftConfiguration) return ''; + + return ((boxTypes[selectedBoxIndex] * 1000) + giftConfiguration.ribbonTypes[selectedRibbonIndex]).toString(); + }, [ giftConfiguration, selectedBoxIndex, selectedRibbonIndex, boxTypes ]); + + const isColorable = useMemo(() => + { + if (!giftConfiguration) return false; + + if (isBoxDefault) return false; + + const boxType = boxTypes[selectedBoxIndex]; + + return (boxType === 8 || (boxType >= 3 && boxType <= 6)) ? false : true; + }, [ giftConfiguration, selectedBoxIndex, isBoxDefault, boxTypes ]); + + const colourId = useMemo(() => + { + return isBoxDefault ? boxTypes[selectedBoxIndex] : selectedColorId; + },[ isBoxDefault, boxTypes, selectedBoxIndex, selectedColorId ]) + + const allFriends = friends.filter( (friend: MessengerFriend) => friend.id !== -1 ); + + const onTextChanged = (e: ChangeEvent) => + { + const value = e.target.value; + + let suggestions = []; + + if (value.length > 0) + { + suggestions = allFriends.sort().filter((friend: MessengerFriend) => friend.name.includes(value)); + } + + setReceiverName(value); + setIsAutocompleteVisible(true); + setSuggestions(suggestions); + }; + + const selectedReceiverName = (friendName: string) => + { + setReceiverName(friendName); + setIsAutocompleteVisible(false); + } + + const handleAction = useCallback((action: string) => + { + switch(action) + { + case 'prev_box': + setSelectedBoxIndex(value => (value === 0 ? maxBoxIndex : value - 1)); + return; + case 'next_box': + setSelectedBoxIndex(value => (value === maxBoxIndex ? 0 : value + 1)); + return; + case 'prev_ribbon': + setSelectedRibbonIndex(value => (value === 0 ? maxRibbonIndex : value - 1)); + return; + case 'next_ribbon': + setSelectedRibbonIndex(value => (value === maxRibbonIndex ? 0 : value + 1)); + return; + case 'buy': + if(!receiverName || (receiverName.length === 0)) + { + setReceiverNotFound(true); + return; + } + + SendMessageComposer(new PurchaseFromCatalogAsGiftComposer(pageId, offerId, extraData, receiverName, message, colourId , selectedBoxIndex, selectedRibbonIndex, showMyFace)); + return; + } + }, [ colourId, extraData, maxBoxIndex, maxRibbonIndex, message, offerId, pageId, receiverName, selectedBoxIndex, selectedRibbonIndex, showMyFace ]); + + useMessageEvent(GiftReceiverNotFoundEvent, event => setReceiverNotFound(true)); + + useUiEvent([ + CatalogPurchasedEvent.PURCHASE_SUCCESS, + CatalogEvent.INIT_GIFT ], event => + { + switch(event.type) + { + case CatalogPurchasedEvent.PURCHASE_SUCCESS: + onClose(); + return; + case CatalogEvent.INIT_GIFT: + const castedEvent = (event as CatalogInitGiftEvent); + + onClose(); + + setPageId(castedEvent.pageId); + setOfferId(castedEvent.offerId); + setExtraData(castedEvent.extraData); + setIsVisible(true); + return; + } + }); + + useEffect(() => + { + setReceiverNotFound(false); + }, [ receiverName ]); + + const createBoxTypes = useCallback(() => + { + if (!giftConfiguration) return; + + setBoxTypes(prev => + { + let newPrev = [ ...giftConfiguration.boxTypes ]; + + newPrev.push(giftConfiguration.defaultStuffTypes[ Math.floor((Math.random() * (giftConfiguration.defaultStuffTypes.length - 1))) ]); + + setMaxBoxIndex(newPrev.length- 1); + setMaxRibbonIndex(newPrev.length - 1); + + return newPrev; + }) + },[ giftConfiguration ]) + + useEffect(() => + { + if(!giftConfiguration) return; + + const newColors: { id: number, color: string }[] = []; + + for(const colorId of giftConfiguration.stuffTypes) + { + const giftData = GetSessionDataManager().getFloorItemData(colorId); + + if(!giftData) continue; + + if(giftData.colors && giftData.colors.length > 0) newColors.push({ id: colorId, color: ColorUtils.makeColorNumberHex(giftData.colors[0]) }); + } + + createBoxTypes(); + + if(newColors.length) + { + setSelectedColorId(newColors[0].id); + setColors(newColors); + } + }, [ giftConfiguration, createBoxTypes ]); + + useEffect(() => + { + if (!isVisible) return; + + createBoxTypes(); + },[ createBoxTypes, isVisible ]) + + if(!giftConfiguration || !giftConfiguration.isEnabled || !isVisible) return null; + + const boxName = 'catalog.gift_wrapping_new.box.' + (isBoxDefault ? 'default' : boxTypes[selectedBoxIndex]); + const ribbonName = `catalog.gift_wrapping_new.ribbon.${ selectedRibbonIndex }`; + const priceText = 'catalog.gift_wrapping_new.' + (isBoxDefault ? 'freeprice' : 'price'); + + return ( + + + + + { LocalizeText('catalog.gift_wrapping.receiver') } + onTextChanged(e) } /> + { (suggestions.length > 0 && isAutocompleteVisible) && + + { suggestions.map((friend: MessengerFriend) => ( + selectedReceiverName(friend.name) }>{ friend.name } + )) } + + } + { receiverNotFound && + { LocalizeText('catalog.gift_wrapping.receiver_not_found.title') } } + + setMessage(value) } /> + + setShowMyFace(value => !value) } /> + + + + { selectedColorId && + + + } + + + + + + + + { LocalizeText(boxName) } + + { LocalizeText(priceText, [ 'price' ], [ giftConfiguration.price.toString() ]) } + + + + + + + + + + { LocalizeText(ribbonName) } + + + + + + { LocalizeText('catalog.gift_wrapping.pick_color') } + + + { colors.map(color => + + + + + ); +}; diff --git a/src/components/catalog/views/navigation/CatalogNavigationItemView.tsx b/src/components/catalog/views/navigation/CatalogNavigationItemView.tsx new file mode 100644 index 0000000..45f7f43 --- /dev/null +++ b/src/components/catalog/views/navigation/CatalogNavigationItemView.tsx @@ -0,0 +1,35 @@ +import { FC } from 'react'; +import { FaCaretDown, FaCaretUp } from 'react-icons/fa'; +import { ICatalogNode } from '../../../../api'; +import { Base, LayoutGridItem, Text } from '../../../../common'; +import { useCatalog } from '../../../../hooks'; +import { CatalogIconView } from '../catalog-icon/CatalogIconView'; +import { CatalogNavigationSetView } from './CatalogNavigationSetView'; + +export interface CatalogNavigationItemViewProps +{ + node: ICatalogNode; + child?: boolean; +} + +export const CatalogNavigationItemView: FC = props => +{ + const { node = null, child = false } = props; + const { activateNode = null } = useCatalog(); + + return ( + + activateNode(node) } className={ child ? 'inset' : '' }> + + { node.localization } + { node.isBranch && + <> + { node.isOpen && } + { !node.isOpen && } + } + + { node.isOpen && node.isBranch && + } + + ); +} diff --git a/src/components/catalog/views/navigation/CatalogNavigationSetView.tsx b/src/components/catalog/views/navigation/CatalogNavigationSetView.tsx new file mode 100644 index 0000000..8bfdd48 --- /dev/null +++ b/src/components/catalog/views/navigation/CatalogNavigationSetView.tsx @@ -0,0 +1,25 @@ +import { FC } from 'react'; +import { ICatalogNode } from '../../../../api'; +import { CatalogNavigationItemView } from './CatalogNavigationItemView'; + +export interface CatalogNavigationSetViewProps +{ + node: ICatalogNode; + child?: boolean; +} + +export const CatalogNavigationSetView: FC = props => +{ + const { node = null, child = false } = props; + + return ( + <> + { node && (node.children.length > 0) && node.children.map((n, index) => + { + if(!n.isVisible) return null; + + return + }) } + + ); +} diff --git a/src/components/catalog/views/navigation/CatalogNavigationView.tsx b/src/components/catalog/views/navigation/CatalogNavigationView.tsx new file mode 100644 index 0000000..2d03f06 --- /dev/null +++ b/src/components/catalog/views/navigation/CatalogNavigationView.tsx @@ -0,0 +1,34 @@ +import { FC } from 'react'; +import { ICatalogNode } from '../../../../api'; +import { AutoGrid, Column } from '../../../../common'; +import { useCatalog } from '../../../../hooks'; +import { CatalogSearchView } from '../page/common/CatalogSearchView'; +import { CatalogNavigationItemView } from './CatalogNavigationItemView'; +import { CatalogNavigationSetView } from './CatalogNavigationSetView'; + +export interface CatalogNavigationViewProps +{ + node: ICatalogNode; +} + +export const CatalogNavigationView: FC = props => +{ + const { node = null } = props; + const { searchResult = null } = useCatalog(); + + return ( + <> + + + + { searchResult && (searchResult.filteredNodes.length > 0) && searchResult.filteredNodes.map((n, index) => + { + return ; + }) } + { !searchResult && + } + + + + ); +} diff --git a/src/components/catalog/views/page/common/CatalogGridOfferView.tsx b/src/components/catalog/views/page/common/CatalogGridOfferView.tsx new file mode 100644 index 0000000..507bb6c --- /dev/null +++ b/src/components/catalog/views/page/common/CatalogGridOfferView.tsx @@ -0,0 +1,59 @@ +import { MouseEventType } from '@nitrots/nitro-renderer'; +import { FC, MouseEvent, useMemo, useState } from 'react'; +import { IPurchasableOffer, Offer, ProductTypeEnum } from '../../../../../api'; +import { LayoutAvatarImageView, LayoutGridItem, LayoutGridItemProps } from '../../../../../common'; +import { useCatalog, useInventoryFurni } from '../../../../../hooks'; + +interface CatalogGridOfferViewProps extends LayoutGridItemProps +{ + offer: IPurchasableOffer; + selectOffer: (offer: IPurchasableOffer) => void; +} + +export const CatalogGridOfferView: FC = props => +{ + const { offer = null, selectOffer = null, itemActive = false, ...rest } = props; + const [ isMouseDown, setMouseDown ] = useState(false); + const { requestOfferToMover = null } = useCatalog(); + const { isVisible = false } = useInventoryFurni(); + + const iconUrl = useMemo(() => + { + if(offer.pricingModel === Offer.PRICING_MODEL_BUNDLE) + { + return null; + } + + return offer.product.getIconUrl(offer); + }, [ offer ]); + + const onMouseEvent = (event: MouseEvent) => + { + switch(event.type) + { + case MouseEventType.MOUSE_DOWN: + selectOffer(offer); + setMouseDown(true); + return; + case MouseEventType.MOUSE_UP: + setMouseDown(false); + return; + case MouseEventType.ROLL_OUT: + if(!isMouseDown || !itemActive || !isVisible) return; + + requestOfferToMover(offer); + return; + } + } + + const product = offer.product; + + if(!product) return null; + + return ( + + { (offer.product.productType === ProductTypeEnum.ROBOT) && + } + + ); +} diff --git a/src/components/catalog/views/page/common/CatalogRedeemVoucherView.tsx b/src/components/catalog/views/page/common/CatalogRedeemVoucherView.tsx new file mode 100644 index 0000000..504bd4b --- /dev/null +++ b/src/components/catalog/views/page/common/CatalogRedeemVoucherView.tsx @@ -0,0 +1,60 @@ +import { RedeemVoucherMessageComposer, VoucherRedeemErrorMessageEvent, VoucherRedeemOkMessageEvent } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { FaTag } from 'react-icons/fa'; +import { LocalizeText, SendMessageComposer } from '../../../../../api'; +import { Button, Flex } from '../../../../../common'; +import { useMessageEvent, useNotification } from '../../../../../hooks'; + +export interface CatalogRedeemVoucherViewProps +{ + text: string; +} + +export const CatalogRedeemVoucherView: FC = props => +{ + const { text = null } = props; + const [ voucher, setVoucher ] = useState(''); + const [ isWaiting, setIsWaiting ] = useState(false); + const { simpleAlert = null } = useNotification(); + + const redeemVoucher = () => + { + if(!voucher || !voucher.length || isWaiting) return; + + SendMessageComposer(new RedeemVoucherMessageComposer(voucher)); + + setIsWaiting(true); + } + + useMessageEvent(VoucherRedeemOkMessageEvent, event => + { + const parser = event.getParser(); + + let message = LocalizeText('catalog.alert.voucherredeem.ok.description'); + + if(parser.productName) message = LocalizeText('catalog.alert.voucherredeem.ok.description.furni', [ 'productName', 'productDescription' ], [ parser.productName, parser.productDescription ]); + + simpleAlert(message, null, null, null, LocalizeText('catalog.alert.voucherredeem.ok.title')); + + setIsWaiting(false); + setVoucher(''); + }); + + useMessageEvent(VoucherRedeemErrorMessageEvent, event => + { + const parser = event.getParser(); + + simpleAlert(LocalizeText(`catalog.alert.voucherredeem.error.description.${ parser.errorCode }`), null, null, null, LocalizeText('catalog.alert.voucherredeem.error.title')); + + setIsWaiting(false); + }); + + return ( + + setVoucher(event.target.value) } /> + + + ); +} diff --git a/src/components/catalog/views/page/common/CatalogSearchView.tsx b/src/components/catalog/views/page/common/CatalogSearchView.tsx new file mode 100644 index 0000000..e117f90 --- /dev/null +++ b/src/components/catalog/views/page/common/CatalogSearchView.tsx @@ -0,0 +1,94 @@ +import { GetSessionDataManager, IFurnitureData } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { FaSearch, FaTimes } from 'react-icons/fa'; +import { CatalogPage, CatalogType, FilterCatalogNode, FurnitureOffer, GetOfferNodes, ICatalogNode, ICatalogPage, IPurchasableOffer, LocalizeText, PageLocalization, SearchResult } from '../../../../../api'; +import { Button, Flex } from '../../../../../common'; +import { useCatalog } from '../../../../../hooks'; + +export const CatalogSearchView: FC<{}> = props => +{ + const [ searchValue, setSearchValue ] = useState(''); + const { currentType = null, rootNode = null, offersToNodes = null, searchResult = null, setSearchResult = null, setCurrentPage = null } = useCatalog(); + + useEffect(() => + { + let search = searchValue?.toLocaleLowerCase().replace(' ', ''); + + if(!search || !search.length) + { + setSearchResult(null); + + return; + } + + const timeout = setTimeout(() => + { + const furnitureDatas = GetSessionDataManager().getAllFurnitureData(); + + if(!furnitureDatas || !furnitureDatas.length) return; + + const foundFurniture: IFurnitureData[] = []; + const foundFurniLines: string[] = []; + + for(const furniture of furnitureDatas) + { + if((currentType === CatalogType.BUILDER) && !furniture.availableForBuildersClub) continue; + + if((currentType === CatalogType.NORMAL) && furniture.excludeDynamic) continue; + + const searchValues = [ furniture.className, furniture.name, furniture.description ].join(' ').replace(/ /gi, '').toLowerCase(); + + if((currentType === CatalogType.BUILDER) && (furniture.purchaseOfferId === -1) && (furniture.rentOfferId === -1)) + { + if((furniture.furniLine !== '') && (foundFurniLines.indexOf(furniture.furniLine) < 0)) + { + if(searchValues.indexOf(search) >= 0) foundFurniLines.push(furniture.furniLine); + } + } + else + { + const foundNodes = [ + ...GetOfferNodes(offersToNodes, furniture.purchaseOfferId), + ...GetOfferNodes(offersToNodes, furniture.rentOfferId) + ]; + + if(foundNodes.length) + { + if(searchValues.indexOf(search) >= 0) foundFurniture.push(furniture); + + if(foundFurniture.length === 250) break; + } + } + } + + const offers: IPurchasableOffer[] = []; + + for(const furniture of foundFurniture) offers.push(new FurnitureOffer(furniture)); + + let nodes: ICatalogNode[] = []; + + FilterCatalogNode(search, foundFurniLines, rootNode, nodes); + + setSearchResult(new SearchResult(search, offers, nodes.filter(node => (node.isVisible)))); + setCurrentPage((new CatalogPage(-1, 'default_3x3', new PageLocalization([], []), offers, false, 1) as ICatalogPage)); + }, 300); + + return () => clearTimeout(timeout); + }, [ offersToNodes, currentType, rootNode, searchValue, setCurrentPage, setSearchResult ]); + + return ( + + + setSearchValue(event.target.value) } /> + + { (!searchValue || !searchValue.length) && + } + { searchValue && !!searchValue.length && + } + + ); +} diff --git a/src/components/catalog/views/page/layout/CatalogLayout.types.ts b/src/components/catalog/views/page/layout/CatalogLayout.types.ts new file mode 100644 index 0000000..b05bccf --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayout.types.ts @@ -0,0 +1,7 @@ +import { ICatalogPage } from '../../../../../api'; + +export interface CatalogLayoutProps +{ + page: ICatalogPage; + hideNavigation: () => void; +} diff --git a/src/components/catalog/views/page/layout/CatalogLayoutBadgeDisplayView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutBadgeDisplayView.tsx new file mode 100644 index 0000000..9b6ec6a --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutBadgeDisplayView.tsx @@ -0,0 +1,54 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../../../api'; +import { Base, Column, Flex, Grid, Text } from '../../../../../common'; +import { useCatalog } from '../../../../../hooks'; +import { CatalogBadgeSelectorWidgetView } from '../widgets/CatalogBadgeSelectorWidgetView'; +import { CatalogFirstProductSelectorWidgetView } from '../widgets/CatalogFirstProductSelectorWidgetView'; +import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView'; +import { CatalogLimitedItemWidgetView } from '../widgets/CatalogLimitedItemWidgetView'; +import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView'; +import { CatalogTotalPriceWidget } from '../widgets/CatalogTotalPriceWidget'; +import { CatalogViewProductWidgetView } from '../widgets/CatalogViewProductWidgetView'; +import { CatalogLayoutProps } from './CatalogLayout.types'; + +export const CatalogLayoutBadgeDisplayView: FC = props => +{ + const { page = null } = props; + const { currentOffer = null } = useCatalog(); + + return ( + <> + + + + + + { LocalizeText('catalog_selectbadge') } + + + + + { !currentOffer && + <> + { !!page.localization.getImage(1) && } + + } + { currentOffer && + <> + + + + + + { currentOffer.localizationName } + + + + + + } + + + + ); +} diff --git a/src/components/catalog/views/page/layout/CatalogLayoutColorGroupingView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutColorGroupingView.tsx new file mode 100644 index 0000000..60ccdba --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutColorGroupingView.tsx @@ -0,0 +1,176 @@ +import { ColorConverter } from '@nitrots/nitro-renderer'; +import { FC, useMemo, useState } from 'react'; +import { FaFillDrip } from 'react-icons/fa'; +import { IPurchasableOffer } from '../../../../../api'; +import { AutoGrid, Base, Button, Column, Flex, Grid, LayoutGridItem, Text } from '../../../../../common'; +import { useCatalog } from '../../../../../hooks'; +import { CatalogGridOfferView } from '../common/CatalogGridOfferView'; +import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView'; +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 interface CatalogLayoutColorGroupViewProps extends CatalogLayoutProps +{ + +} + +export const CatalogLayoutColorGroupingView : FC = props => +{ + const { page = null } = props; + const [ colorableItems, setColorableItems ] = useState>(new Map()); + const { currentOffer = null, setCurrentOffer = null } = useCatalog(); + const [ colorsShowing, setColorsShowing ] = useState(false); + + const sortByColorIndex = (a: IPurchasableOffer, b: IPurchasableOffer) => + { + if (((!(a.product.furnitureData.colorIndex)) || (!(b.product.furnitureData.colorIndex)))) + { + return 1; + } + if (a.product.furnitureData.colorIndex > b.product.furnitureData.colorIndex) + { + return 1; + } + if (a == b) + { + return 0; + } + return -1; + } + + const sortyByFurnitureClassName = (a: IPurchasableOffer, b: IPurchasableOffer) => + { + if (a.product.furnitureData.className > b.product.furnitureData.className) + { + return 1; + } + if (a == b) + { + return 0; + } + return -1; + } + + const selectOffer = (offer: IPurchasableOffer) => + { + offer.activate(); + setCurrentOffer(offer); + } + + const selectColor = (colorIndex: number, productName: string) => + { + const fullName = `${ productName }*${ colorIndex }`; + const index = page.offers.findIndex(offer => offer.product.furnitureData.fullName === fullName); + if (index > -1) + { + selectOffer(page.offers[index]); + } + } + + const offers = useMemo(() => + { + const offers: IPurchasableOffer[] = []; + const addedColorableItems = new Map(); + const updatedColorableItems = new Map(); + + page.offers.sort(sortByColorIndex); + + page.offers.forEach(offer => + { + if(!offer.product) return; + + const furniData = offer.product.furnitureData; + + if(!furniData || !furniData.hasIndexedColor) + { + offers.push(offer); + } + else + { + const name = furniData.className; + const colorIndex = furniData.colorIndex; + + if(!updatedColorableItems.has(name)) + { + updatedColorableItems.set(name, []); + } + + let selectedColor = 0xFFFFFF; + + if(furniData.colors) + { + for(let color of furniData.colors) + { + if(color !== 0xFFFFFF) // skip the white colors + { + selectedColor = color; + } + } + + if(updatedColorableItems.get(name).indexOf(selectedColor) === -1) + { + updatedColorableItems.get(name)[colorIndex] = selectedColor; + } + + } + + if(!addedColorableItems.has(name)) + { + offers.push(offer); + addedColorableItems.set(name, true); + } + } + }); + offers.sort(sortyByFurnitureClassName); + setColorableItems(updatedColorableItems); + return offers; + }, [ page.offers ]); + + return ( + + + + { (!colorsShowing || !currentOffer || !colorableItems.has(currentOffer.product.furnitureData.className)) && + offers.map((offer, index) => ) + } + { (colorsShowing && currentOffer && colorableItems.has(currentOffer.product.furnitureData.className)) && + colorableItems.get(currentOffer.product.furnitureData.className).map((color, index) => selectColor(index, currentOffer.product.furnitureData.className) } />) + } + + + + { !currentOffer && + <> + { !!page.localization.getImage(1) && } + + } + { currentOffer && + <> + + + + { currentOffer.product.furnitureData.hasIndexedColor && + } + + + + { currentOffer.localizationName } + + + + + + + + + } + + + ); +} diff --git a/src/components/catalog/views/page/layout/CatalogLayoutDefaultView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutDefaultView.tsx new file mode 100644 index 0000000..f1cfab3 --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutDefaultView.tsx @@ -0,0 +1,61 @@ +import { FC } from 'react'; +import { GetConfigurationValue, ProductTypeEnum } from '../../../../../api'; +import { Column, Flex, Grid, LayoutImage, Text } from '../../../../../common'; +import { useCatalog } 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 CatalogLayoutDefaultView: FC = props => +{ + const { page = null } = props; + const { currentOffer = null, currentPage = null } = useCatalog(); + + return ( + <> + + + { GetConfigurationValue('catalog.headers') && + } + + + + { !currentOffer && + <> + { !!page.localization.getImage(1) && + } + + } + { currentOffer && + <> + + { (currentOffer.product.productType !== ProductTypeEnum.BADGE) && + <> + + + } + { (currentOffer.product.productType === ProductTypeEnum.BADGE) && } + + + + { currentOffer.localizationName } + + + + + + + + + } + + + + ); +} diff --git a/src/components/catalog/views/page/layout/CatalogLayoutGuildCustomFurniView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutGuildCustomFurniView.tsx new file mode 100644 index 0000000..cf12951 --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutGuildCustomFurniView.tsx @@ -0,0 +1,48 @@ +import { FC } from 'react'; +import { Base, Column, Flex, Grid, Text } from '../../../../../common'; +import { useCatalog } from '../../../../../hooks'; +import { CatalogGuildBadgeWidgetView } from '../widgets/CatalogGuildBadgeWidgetView'; +import { CatalogGuildSelectorWidgetView } from '../widgets/CatalogGuildSelectorWidgetView'; +import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView'; +import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView'; +import { CatalogTotalPriceWidget } from '../widgets/CatalogTotalPriceWidget'; +import { CatalogViewProductWidgetView } from '../widgets/CatalogViewProductWidgetView'; +import { CatalogLayoutProps } from './CatalogLayout.types'; + +export const CatalogLayouGuildCustomFurniView: FC = props => +{ + const { page = null } = props; + const { currentOffer = null } = useCatalog(); + + return ( + + + + + + { !currentOffer && + <> + { !!page.localization.getImage(1) && } + + } + { currentOffer && + <> + + + + + + { currentOffer.localizationName } + + + + + + + + + } + + + ); +} diff --git a/src/components/catalog/views/page/layout/CatalogLayoutGuildForumView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutGuildForumView.tsx new file mode 100644 index 0000000..b5a89ca --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutGuildForumView.tsx @@ -0,0 +1,49 @@ +import { CatalogGroupsComposer } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { SendMessageComposer } from '../../../../../api'; +import { Base, Column, Flex, Grid, Text } from '../../../../../common'; +import { useCatalog } from '../../../../../hooks'; +import { CatalogFirstProductSelectorWidgetView } from '../widgets/CatalogFirstProductSelectorWidgetView'; +import { CatalogGuildSelectorWidgetView } from '../widgets/CatalogGuildSelectorWidgetView'; +import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView'; +import { CatalogTotalPriceWidget } from '../widgets/CatalogTotalPriceWidget'; +import { CatalogLayoutProps } from './CatalogLayout.types'; + +export const CatalogLayouGuildForumView: FC = props => +{ + const { page = null } = props; + const [ selectedGroupIndex, setSelectedGroupIndex ] = useState(0); + const { currentOffer = null, setCurrentOffer = null, catalogOptions = null } = useCatalog(); + const { groups = null } = catalogOptions; + + useEffect(() => + { + SendMessageComposer(new CatalogGroupsComposer()); + }, [ page ]); + + return ( + <> + + + + + + + { !!currentOffer && + <> + + { currentOffer.localizationName } + + + + + + + + + } + + + + ); +} diff --git a/src/components/catalog/views/page/layout/CatalogLayoutGuildFrontpageView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutGuildFrontpageView.tsx new file mode 100644 index 0000000..01cbcf1 --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutGuildFrontpageView.tsx @@ -0,0 +1,30 @@ +import { CreateLinkEvent } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { LocalizeText } from '../../../../../api'; +import { Base } from '../../../../../common/Base'; +import { Button } from '../../../../../common/Button'; +import { Column } from '../../../../../common/Column'; +import { Grid } from '../../../../../common/Grid'; +import { LayoutImage } from '../../../../../common/layout/LayoutImage'; +import { CatalogLayoutProps } from './CatalogLayout.types'; + +export const CatalogLayouGuildFrontpageView: FC = props => +{ + const { page = null } = props; + + return ( + + + + + + + + + + + + ); +} diff --git a/src/components/catalog/views/page/layout/CatalogLayoutInfoLoyaltyView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutInfoLoyaltyView.tsx new file mode 100644 index 0000000..06c4c03 --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutInfoLoyaltyView.tsx @@ -0,0 +1,15 @@ +import { FC } from 'react'; +import { CatalogLayoutProps } from './CatalogLayout.types'; + +export const CatalogLayoutInfoLoyaltyView: FC = props => +{ + const { page = null } = props; + + return ( +
+
+
+
+
+ ); +} diff --git a/src/components/catalog/views/page/layout/CatalogLayoutPets2View.tsx b/src/components/catalog/views/page/layout/CatalogLayoutPets2View.tsx new file mode 100644 index 0000000..38ad284 --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutPets2View.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { CatalogLayoutProps } from './CatalogLayout.types'; +import { CatalogLayoutPets3View } from './CatalogLayoutPets3View'; + +export const CatalogLayoutPets2View: FC = props => +{ + return +} diff --git a/src/components/catalog/views/page/layout/CatalogLayoutPets3View.tsx b/src/components/catalog/views/page/layout/CatalogLayoutPets3View.tsx new file mode 100644 index 0000000..7aec846 --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutPets3View.tsx @@ -0,0 +1,25 @@ +import { FC } from 'react'; +import { Base, Column, Flex } from '../../../../../common'; +import { CatalogLayoutProps } from './CatalogLayout.types'; + +export const CatalogLayoutPets3View: FC = props => +{ + const { page = null } = props; + + const imageUrl = page.localization.getImage(1); + + return ( + + + { imageUrl && } + + + + + + + + + + ); +} diff --git a/src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx new file mode 100644 index 0000000..92dd4bf --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx @@ -0,0 +1,113 @@ +import { GetRoomAdPurchaseInfoComposer, GetUserEventCatsMessageComposer, PurchaseRoomAdMessageComposer, RoomAdPurchaseInfoEvent, RoomEntryData } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, SendMessageComposer } from '../../../../../api'; +import { Base, Button, Column, Text } from '../../../../../common'; +import { useCatalog, useMessageEvent, useNavigator, useRoomPromote } from '../../../../../hooks'; +import { CatalogLayoutProps } from './CatalogLayout.types'; + +export const CatalogLayoutRoomAdsView: FC = props => +{ + const { page = null } = props; + const [ eventName, setEventName ] = useState(''); + const [ eventDesc, setEventDesc ] = useState(''); + const [ roomId, setRoomId ] = useState(-1); + const [ availableRooms, setAvailableRooms ] = useState([]); + const [ extended, setExtended ] = useState(false); + const [ categoryId, setCategoryId ] = useState(1); + const { categories = null } = useNavigator(); + const { setIsVisible = null } = useCatalog(); + const { promoteInformation, isExtended, setIsExtended } = useRoomPromote(); + + useEffect(() => + { + if(isExtended) + { + setRoomId(promoteInformation.data.flatId); + setEventName(promoteInformation.data.eventName); + setEventDesc(promoteInformation.data.eventDescription); + setCategoryId(promoteInformation.data.categoryId); + setExtended(isExtended); // This is for sending to packet + setIsExtended(false); // This is from hook useRoomPromotte + } + + }, [ isExtended, eventName, eventDesc, categoryId ]); + + const resetData = () => + { + setRoomId(-1); + setEventName(''); + setEventDesc(''); + setCategoryId(1); + setIsExtended(false); + setIsVisible(false); + } + + const purchaseAd = () => + { + const pageId = page.pageId; + const offerId = page.offers.length >= 1 ? page.offers[0].offerId : -1; + const flatId = roomId; + const name = eventName; + const desc = eventDesc; + const catId = categoryId; + + SendMessageComposer(new PurchaseRoomAdMessageComposer(pageId, offerId, flatId, name, extended, desc, catId)); + resetData(); + } + + useMessageEvent(RoomAdPurchaseInfoEvent, event => + { + const parser = event.getParser(); + + if(!parser) return; + + setAvailableRooms(parser.rooms); + }); + + useEffect(() => + { + SendMessageComposer(new GetRoomAdPurchaseInfoComposer()); + // TODO: someone needs to fix this for morningstar + SendMessageComposer(new GetUserEventCatsMessageComposer()); + }, []); + + return (<> + { LocalizeText('roomad.catalog_header') } + + { LocalizeText('roomad.catalog_text', [ 'duration' ], [ '120' ]) } + + + { LocalizeText('navigator.category') } + + + + { LocalizeText('roomad.catalog_name') } + setEventName(event.target.value) } readOnly={ extended } /> + + + { LocalizeText('roomad.catalog_description') } + + { LocalizeText('friendlist.invite.note') } + + + + + + + ); +}; diff --git a/src/components/friends/views/friends-list/FriendsListSearchView.tsx b/src/components/friends/views/friends-list/FriendsListSearchView.tsx new file mode 100644 index 0000000..69073a2 --- /dev/null +++ b/src/components/friends/views/friends-list/FriendsListSearchView.tsx @@ -0,0 +1,103 @@ +import { HabboSearchComposer, HabboSearchResultData, HabboSearchResultEvent } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, OpenMessengerChat, SendMessageComposer } from '../../../../api'; +import { Base, Column, Flex, NitroCardAccordionItemView, NitroCardAccordionSetView, NitroCardAccordionSetViewProps, Text, UserProfileIconView } from '../../../../common'; +import { useFriends, useMessageEvent } from '../../../../hooks'; + +interface FriendsSearchViewProps extends NitroCardAccordionSetViewProps +{ + +} + +export const FriendsSearchView: FC = props => +{ + const { ...rest } = props; + const [ searchValue, setSearchValue ] = useState(''); + const [ friendResults, setFriendResults ] = useState(null); + const [ otherResults, setOtherResults ] = useState(null); + const { canRequestFriend = null, requestFriend = null } = useFriends(); + + useMessageEvent(HabboSearchResultEvent, event => + { + const parser = event.getParser(); + + setFriendResults(parser.friends); + setOtherResults(parser.others); + }); + + useEffect(() => + { + if(!searchValue || !searchValue.length) return; + + const timeout = setTimeout(() => + { + if(!searchValue || !searchValue.length) return; + + SendMessageComposer(new HabboSearchComposer(searchValue)); + }, 500); + + return () => clearTimeout(timeout); + }, [ searchValue ]); + + return ( + + setSearchValue(event.target.value) } /> + + { friendResults && + <> + { (friendResults.length === 0) && + { LocalizeText('friendlist.search.nofriendsfound') } } + { (friendResults.length > 0) && + + { LocalizeText('friendlist.search.friendscaption', [ 'cnt' ], [ friendResults.length.toString() ]) } +
+ + { friendResults.map(result => + { + return ( + + + +
{ result.avatarName }
+
+ + { result.isAvatarOnline && + OpenMessengerChat(result.avatarId) } title={ LocalizeText('friendlist.tip.im') } /> } + +
+ ) + }) } +
+
} + } + { otherResults && + <> + { (otherResults.length === 0) && + { LocalizeText('friendlist.search.noothersfound') } } + { (otherResults.length > 0) && + + { LocalizeText('friendlist.search.otherscaption', [ 'cnt' ], [ otherResults.length.toString() ]) } +
+ + { otherResults.map(result => + { + return ( + + + +
{ result.avatarName }
+
+ + { canRequestFriend(result.avatarId) && + requestFriend(result.avatarId, result.avatarName) } title={ LocalizeText('friendlist.tip.addfriend') } /> } + +
+ ) + }) } +
+
} + } +
+
+ ); +} diff --git a/src/components/friends/views/friends-list/FriendsListView.tsx b/src/components/friends/views/friends-list/FriendsListView.tsx new file mode 100644 index 0000000..9561d0f --- /dev/null +++ b/src/components/friends/views/friends-list/FriendsListView.tsx @@ -0,0 +1,150 @@ +import { AddLinkEventTracker, ILinkEventTracker, RemoveFriendComposer, RemoveLinkEventTracker, SendRoomInviteComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { LocalizeText, MessengerFriend, SendMessageComposer } from '../../../../api'; +import { Button, Flex, NitroCardAccordionSetView, NitroCardAccordionView, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; +import { useFriends } from '../../../../hooks'; +import { FriendsRemoveConfirmationView } from './FriendsListRemoveConfirmationView'; +import { FriendsRoomInviteView } from './FriendsListRoomInviteView'; +import { FriendsSearchView } from './FriendsListSearchView'; +import { FriendsListGroupView } from './friends-list-group/FriendsListGroupView'; +import { FriendsListRequestView } from './friends-list-request/FriendsListRequestView'; + +export const FriendsListView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ selectedFriendsIds, setSelectedFriendsIds ] = useState([]); + const [ showRoomInvite, setShowRoomInvite ] = useState(false); + const [ showRemoveFriendsConfirmation, setShowRemoveFriendsConfirmation ] = useState(false); + const { onlineFriends = [], offlineFriends = [], requests = [], requestFriend = null } = useFriends(); + + const removeFriendsText = useMemo(() => + { + if(!selectedFriendsIds || !selectedFriendsIds.length) return ''; + + const userNames: string[] = []; + + for(const userId of selectedFriendsIds) + { + let existingFriend: MessengerFriend = onlineFriends.find(f => f.id === userId); + + if(!existingFriend) existingFriend = offlineFriends.find(f => f.id === userId); + + if(!existingFriend) continue; + + userNames.push(existingFriend.name); + } + + return LocalizeText('friendlist.removefriendconfirm.userlist', [ 'user_names' ], [ userNames.join(', ') ]); + }, [ offlineFriends, onlineFriends, selectedFriendsIds ]); + + const selectFriend = useCallback((userId: number) => + { + if(userId < 0) return; + + setSelectedFriendsIds(prevValue => + { + const newValue = [ ...prevValue ]; + + const existingUserIdIndex: number = newValue.indexOf(userId); + + if(existingUserIdIndex > -1) + { + newValue.splice(existingUserIdIndex, 1) + } + else + { + newValue.push(userId); + } + + return newValue; + }); + }, [ setSelectedFriendsIds ]); + + const sendRoomInvite = (message: string) => + { + if(!selectedFriendsIds.length || !message || !message.length || (message.length > 255)) return; + + SendMessageComposer(new SendRoomInviteComposer(message, selectedFriendsIds)); + + setShowRoomInvite(false); + } + + const removeSelectedFriends = () => + { + if(selectedFriendsIds.length === 0) return; + + setSelectedFriendsIds(prevValue => + { + SendMessageComposer(new RemoveFriendComposer(...prevValue)); + + return []; + }); + + setShowRemoveFriendsConfirmation(false); + } + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'show': + setIsVisible(true); + return; + case 'hide': + setIsVisible(false); + return; + case 'toggle': + setIsVisible(prevValue => !prevValue); + return; + case 'request': + if(parts.length < 4) return; + + requestFriend(parseInt(parts[2]), parts[3]); + } + }, + eventUrlPrefix: 'friends/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, [ requestFriend ]); + + if(!isVisible) return null; + + return ( + <> + + setIsVisible(false) } /> + + + + + + + + + + + + { selectedFriendsIds && selectedFriendsIds.length > 0 && + + + + } + + + { showRoomInvite && + setShowRoomInvite(false) } sendRoomInvite={ sendRoomInvite } /> } + { showRemoveFriendsConfirmation && + setShowRemoveFriendsConfirmation(false) } removeSelectedFriends={ removeSelectedFriends } /> } + + ); +}; diff --git a/src/components/friends/views/friends-list/friends-list-group/FriendsListGroupItemView.tsx b/src/components/friends/views/friends-list/friends-list-group/FriendsListGroupItemView.tsx new file mode 100644 index 0000000..03cd7e9 --- /dev/null +++ b/src/components/friends/views/friends-list/friends-list-group/FriendsListGroupItemView.tsx @@ -0,0 +1,85 @@ +import { FC, MouseEvent, useState } from 'react'; +import { LocalizeText, MessengerFriend, OpenMessengerChat } from '../../../../../api'; +import { Base, Flex, NitroCardAccordionItemView, UserProfileIconView } from '../../../../../common'; +import { useFriends } from '../../../../../hooks'; + +export const FriendsListGroupItemView: FC<{ friend: MessengerFriend, selected: boolean, selectFriend: (userId: number) => void }> = props => +{ + const { friend = null, selected = false, selectFriend = null } = props; + const [ isRelationshipOpen, setIsRelationshipOpen ] = useState(false); + const { followFriend = null, updateRelationship = null } = useFriends(); + + const clickFollowFriend = (event: MouseEvent) => + { + event.stopPropagation(); + + followFriend(friend); + } + + const openMessengerChat = (event: MouseEvent) => + { + event.stopPropagation(); + + OpenMessengerChat(friend.id); + } + + const openRelationship = (event: MouseEvent) => + { + event.stopPropagation(); + + setIsRelationshipOpen(true); + } + + const clickUpdateRelationship = (event: MouseEvent, type: number) => + { + event.stopPropagation(); + + updateRelationship(friend, type); + + setIsRelationshipOpen(false); + } + + const getCurrentRelationshipName = () => + { + if(!friend) return 'none'; + + switch(friend.relationshipStatus) + { + case MessengerFriend.RELATIONSHIP_HEART: return 'heart'; + case MessengerFriend.RELATIONSHIP_SMILE: return 'smile'; + case MessengerFriend.RELATIONSHIP_BOBBA: return 'bobba'; + default: return 'none'; + } + } + + if(!friend) return null; + + return ( + selectFriend(friend.id) }> + + event.stopPropagation() }> + + +
{ friend.name }
+
+ + { !isRelationshipOpen && + <> + { friend.followingAllowed && + } + { friend.online && + } + { (friend.id > 0) && + } + } + { isRelationshipOpen && + <> + clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_HEART) } /> + clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_SMILE) } /> + clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_BOBBA) } /> + clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_NONE) } /> + } + +
+ ); +} diff --git a/src/components/friends/views/friends-list/friends-list-group/FriendsListGroupView.tsx b/src/components/friends/views/friends-list/friends-list-group/FriendsListGroupView.tsx new file mode 100644 index 0000000..c593003 --- /dev/null +++ b/src/components/friends/views/friends-list/friends-list-group/FriendsListGroupView.tsx @@ -0,0 +1,23 @@ +import { FC } from 'react'; +import { MessengerFriend } from '../../../../../api'; +import { FriendsListGroupItemView } from './FriendsListGroupItemView'; + +interface FriendsListGroupViewProps +{ + list: MessengerFriend[]; + selectedFriendsIds: number[]; + selectFriend: (userId: number) => void; +} + +export const FriendsListGroupView: FC = props => +{ + const { list = null, selectedFriendsIds = null, selectFriend = null } = props; + + if(!list || !list.length) return null; + + return ( + <> + { list.map((item, index) => = 0) } selectFriend={ selectFriend } />) } + + ); +} diff --git a/src/components/friends/views/friends-list/friends-list-request/FriendsListRequestItemView.tsx b/src/components/friends/views/friends-list/friends-list-request/FriendsListRequestItemView.tsx new file mode 100644 index 0000000..de5d3a3 --- /dev/null +++ b/src/components/friends/views/friends-list/friends-list-request/FriendsListRequestItemView.tsx @@ -0,0 +1,25 @@ +import { FC } from 'react'; +import { MessengerRequest } from '../../../../../api'; +import { Base, Flex, NitroCardAccordionItemView, UserProfileIconView } from '../../../../../common'; +import { useFriends } from '../../../../../hooks'; + +export const FriendsListRequestItemView: FC<{ request: MessengerRequest }> = props => +{ + const { request = null } = props; + const { requestResponse = null } = useFriends(); + + if(!request) return null; + + return ( + + + +
{ request.name }
+
+ + requestResponse(request.id, true) } /> + requestResponse(request.id, false) } /> + +
+ ); +} diff --git a/src/components/friends/views/friends-list/friends-list-request/FriendsListRequestView.tsx b/src/components/friends/views/friends-list/friends-list-request/FriendsListRequestView.tsx new file mode 100644 index 0000000..5f6e991 --- /dev/null +++ b/src/components/friends/views/friends-list/friends-list-request/FriendsListRequestView.tsx @@ -0,0 +1,29 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../../../api'; +import { Button, Column, Flex, NitroCardAccordionSetView, NitroCardAccordionSetViewProps } from '../../../../../common'; +import { useFriends } from '../../../../../hooks'; +import { FriendsListRequestItemView } from './FriendsListRequestItemView'; + +export const FriendsListRequestView: FC = props => +{ + const { children = null, ...rest } = props; + const { requests = [], requestResponse = null } = useFriends(); + + if(!requests.length) return null; + + return ( + + + + { requests.map((request, index) => ) } + + + + + + { children } + + ); +} diff --git a/src/components/friends/views/messenger/FriendsMessengerView.tsx b/src/components/friends/views/messenger/FriendsMessengerView.tsx new file mode 100644 index 0000000..932e71d --- /dev/null +++ b/src/components/friends/views/messenger/FriendsMessengerView.tsx @@ -0,0 +1,177 @@ +import { AddLinkEventTracker, FollowFriendMessageComposer, GetSessionDataManager, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; +import { FC, KeyboardEvent, useEffect, useRef, useState } from 'react'; +import { FaTimes } from 'react-icons/fa'; +import { GetUserProfile, LocalizeText, ReportType, SendMessageComposer } from '../../../../api'; +import { Base, Button, ButtonGroup, Column, Flex, Grid, LayoutAvatarImageView, LayoutBadgeImageView, LayoutGridItem, LayoutItemCountView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; +import { useHelp, useMessenger } from '../../../../hooks'; +import { FriendsMessengerThreadView } from './messenger-thread/FriendsMessengerThreadView'; + +export const FriendsMessengerView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ lastThreadId, setLastThreadId ] = useState(-1); + const [ messageText, setMessageText ] = useState(''); + const { visibleThreads = [], activeThread = null, getMessageThread = null, sendMessage = null, setActiveThreadId = null, closeThread = null } = useMessenger(); + const { report = null } = useHelp(); + const messagesBox = useRef(); + + const followFriend = () => (activeThread && activeThread.participant && SendMessageComposer(new FollowFriendMessageComposer(activeThread.participant.id))); + const openProfile = () => (activeThread && activeThread.participant && GetUserProfile(activeThread.participant.id)); + + const send = () => + { + if(!activeThread || !messageText.length) return; + + sendMessage(activeThread, GetSessionDataManager().userId, messageText); + + setMessageText(''); + } + + const onKeyDown = (event: KeyboardEvent) => + { + if(event.key !== 'Enter') return; + + send(); + } + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length === 2) + { + if(parts[1] === 'open') + { + setIsVisible(true); + + return; + } + + if(parts[1] === 'toggle') + { + setIsVisible(prevValue => !prevValue); + + return; + } + + const thread = getMessageThread(parseInt(parts[1])); + + if(!thread) return; + + setActiveThreadId(thread.threadId); + setIsVisible(true); + } + }, + eventUrlPrefix: 'friends-messenger/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, [ getMessageThread, setActiveThreadId ]); + + useEffect(() => + { + if(!isVisible || !activeThread) return; + + messagesBox.current.scrollTop = messagesBox.current.scrollHeight; + }, [ isVisible, activeThread ]); + + useEffect(() => + { + if(isVisible && !activeThread) + { + if(lastThreadId > 0) + { + setActiveThreadId(lastThreadId); + } + else + { + if(visibleThreads.length > 0) setActiveThreadId(visibleThreads[0].threadId); + } + + return; + } + + if(!isVisible && activeThread) + { + setLastThreadId(activeThread.threadId); + setActiveThreadId(-1); + } + }, [ isVisible, activeThread, lastThreadId, visibleThreads, setActiveThreadId ]); + + if(!isVisible) return null; + + return ( + + setIsVisible(false) } /> + + + + { LocalizeText('toolbar.icon.label.messenger') } + + + { visibleThreads && (visibleThreads.length > 0) && visibleThreads.map(thread => + { + return ( + setActiveThreadId(thread.threadId) }> + { thread.unread && + } + + + { (thread.participant.id > 0) && + } + { (thread.participant.id <= 0) && + } + + { thread.participant.name } + + + ); + }) } + + + + + { activeThread && + <> + { LocalizeText('messenger.window.separator', [ 'FRIEND_NAME' ], [ activeThread.participant.name ]) } + + + + + + + + + + + + + + + + + setMessageText(event.target.value) } onKeyDown={ onKeyDown } /> + + + } + + + + + ); +} diff --git a/src/components/friends/views/messenger/messenger-thread/FriendsMessengerThreadGroup.tsx b/src/components/friends/views/messenger/messenger-thread/FriendsMessengerThreadGroup.tsx new file mode 100644 index 0000000..4ed6c99 --- /dev/null +++ b/src/components/friends/views/messenger/messenger-thread/FriendsMessengerThreadGroup.tsx @@ -0,0 +1,73 @@ +import { GetSessionDataManager } from '@nitrots/nitro-renderer'; +import { FC, useMemo } from 'react'; +import { GetGroupChatData, LocalizeText, MessengerGroupType, MessengerThread, MessengerThreadChat, MessengerThreadChatGroup } from '../../../../../api'; +import { Base, Flex, LayoutAvatarImageView } from '../../../../../common'; + +export const FriendsMessengerThreadGroup: FC<{ thread: MessengerThread, group: MessengerThreadChatGroup }> = props => +{ + const { thread = null, group = null } = props; + + const groupChatData = useMemo(() => ((group.type === MessengerGroupType.GROUP_CHAT) && GetGroupChatData(group.chats[0].extraData)), [ group ]); + + const isOwnChat = useMemo(() => + { + if(!thread || !group) return false; + + if((group.type === MessengerGroupType.PRIVATE_CHAT) && (group.userId === GetSessionDataManager().userId)) return true; + + if(groupChatData && group.chats.length && (groupChatData.userId === GetSessionDataManager().userId)) return true; + + return false; + }, [ thread, group, groupChatData ]); + + if(!thread || !group) return null; + + if(!group.userId) + { + return ( + <> + { group.chats.map((chat, index) => + { + return ( + + + { (chat.type === MessengerThreadChat.SECURITY_NOTIFICATION) && + + + { chat.message } + } + { (chat.type === MessengerThreadChat.ROOM_INVITE) && + + + { (LocalizeText('messenger.invitation') + ' ') }{ chat.message } + } + + + ); + }) } + + ); + } + + return ( + + + { ((group.type === MessengerGroupType.PRIVATE_CHAT) && !isOwnChat) && + } + { (groupChatData && !isOwnChat) && + } + + + + { isOwnChat && GetSessionDataManager().userName } + { !isOwnChat && (groupChatData ? groupChatData.username : thread.participant.name) } + + { group.chats.map((chat, index) => { chat.message }) } + + { isOwnChat && + + + } + + ); +} diff --git a/src/components/friends/views/messenger/messenger-thread/FriendsMessengerThreadView.tsx b/src/components/friends/views/messenger/messenger-thread/FriendsMessengerThreadView.tsx new file mode 100644 index 0000000..962a668 --- /dev/null +++ b/src/components/friends/views/messenger/messenger-thread/FriendsMessengerThreadView.tsx @@ -0,0 +1,16 @@ +import { FC } from 'react'; +import { MessengerThread } from '../../../../../api'; +import { FriendsMessengerThreadGroup } from './FriendsMessengerThreadGroup'; + +export const FriendsMessengerThreadView: FC<{ thread: MessengerThread }> = props => +{ + const { thread = null } = props; + + thread.setRead(); + + return ( + <> + { (thread.groups.length > 0) && thread.groups.map((group, index) => ) } + + ); +} diff --git a/src/components/game-center/GameCenterView.scss b/src/components/game-center/GameCenterView.scss new file mode 100644 index 0000000..246278d --- /dev/null +++ b/src/components/game-center/GameCenterView.scss @@ -0,0 +1,44 @@ +.game-center-stage { + width: 100%; + height: calc(100% - 55px); + background-color: black; + position: absolute; + inset: 0; +} +.game-center-main { + width: 1280px; + height: calc(100% - 55px); + background: #93d4f3; + + .game-view { + background-position: bottom center; + background-repeat: no-repeat; + + > div { + color: inherit; + } + } + + .gameList-container { + min-height: 107px; + + .game-icon { + width: 83px; + height: 83px; + background-position: center; + background-repeat: no-repeat; + + &.selected { + position: relative; + &::after { + content: ''; + background-image: url('@/assets/images/gamecenter/selectedIcon.png'); + width: 83px; + height: 83px; + position: absolute; + inset: 0; + } + } + } + } +} diff --git a/src/components/game-center/GameCenterView.tsx b/src/components/game-center/GameCenterView.tsx new file mode 100644 index 0000000..885908e --- /dev/null +++ b/src/components/game-center/GameCenterView.tsx @@ -0,0 +1,49 @@ +import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; +import { useEffect } from 'react'; +import { Flex } from '../../common'; +import { useGameCenter } from '../../hooks'; +import { GameListView } from './views/GameListView'; +import { GameStageView } from './views/GameStageView'; +import { GameView } from './views/GameView'; + +export const GameCenterView = () => +{ + const{ isVisible, setIsVisible, games, accountStatus } = useGameCenter(); + + useEffect(() => + { + const toggleGameCenter = () => + { + setIsVisible(prev => !prev); + } + + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const value = url.split('/'); + + switch(value[1]) + { + case 'toggle': + toggleGameCenter(); + break; + } + }, + eventUrlPrefix: 'games/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, [ setIsVisible ]); + + if(!isVisible || !games || !accountStatus) return; + + return + + + + + + +} diff --git a/src/components/game-center/views/GameListView.tsx b/src/components/game-center/views/GameListView.tsx new file mode 100644 index 0000000..1211bc7 --- /dev/null +++ b/src/components/game-center/views/GameListView.tsx @@ -0,0 +1,32 @@ +import { GameConfigurationData } from '@nitrots/nitro-renderer'; +import { LocalizeText } from '../../../api'; +import { Base, Flex } from '../../../common'; +import { useGameCenter } from '../../../hooks'; + +export const GameListView = () => +{ + const { games,selectedGame, setSelectedGame } = useGameCenter(); + + const getClasses = (game: GameConfigurationData) => + { + let classes = [ 'game-icon' ]; + + if(selectedGame === game) classes.push('selected'); + + return classes.join(' '); + } + + const getIconImage = (game: GameConfigurationData): string => + { + return `url(${ game.assetUrl }${ game.gameNameId }_icon.png)` + } + + return + { LocalizeText('gamecenter.game_list_title') } + + { games && games.map((game,index) => + setSelectedGame(game) } style={ { backgroundImage: getIconImage(game) } }/> + ) } + + +} diff --git a/src/components/game-center/views/GameStageView.tsx b/src/components/game-center/views/GameStageView.tsx new file mode 100644 index 0000000..0a26fea --- /dev/null +++ b/src/components/game-center/views/GameStageView.tsx @@ -0,0 +1,47 @@ +import { Game2ExitGameMessageComposer } from '@nitrots/nitro-renderer'; +import { useEffect, useRef, useState } from 'react'; +import { SendMessageComposer } from '../../../api'; +import { Base } from '../../../common'; +import { useGameCenter } from '../../../hooks'; + +export const GameStageView = () => +{ + const { gameURL,setGameURL } = useGameCenter(); + const [ loadTimes, setLoadTimes ] = useState(0); + const ref = useRef(); + + useEffect(()=> + { + if(!ref || ref && !ref.current) return; + + setLoadTimes(0); + + let frame: HTMLIFrameElement = document.createElement('iframe'); + + frame.src = gameURL; + frame.classList.add('game-center-stage'); + frame.classList.add('h-100'); + + frame.onload = () => + { + setLoadTimes(prev => prev += 1) + } + + ref.current.innerHTML = ''; + ref.current.appendChild(frame); + + },[ ref, gameURL ]); + + useEffect(()=> + { + if(loadTimes > 1) + { + setGameURL(null); + SendMessageComposer(new Game2ExitGameMessageComposer()); + } + },[ loadTimes,setGameURL ]) + + if(!gameURL) return null; + + return +} diff --git a/src/components/game-center/views/GameView.tsx b/src/components/game-center/views/GameView.tsx new file mode 100644 index 0000000..a9d641f --- /dev/null +++ b/src/components/game-center/views/GameView.tsx @@ -0,0 +1,58 @@ +import { Game2GetAccountGameStatusMessageComposer, GetGameStatusMessageComposer, JoinQueueMessageComposer } from '@nitrots/nitro-renderer'; +import { useEffect } from 'react'; +import { ColorUtils, LocalizeText, SendMessageComposer } from '../../../api'; +import { Base, Button, Flex, LayoutItemCountView, Text } from '../../../common'; +import { useGameCenter } from '../../../hooks'; + +export const GameView = () => +{ + const { selectedGame, accountStatus } = useGameCenter(); + + useEffect(()=> + { + if(selectedGame) + { + SendMessageComposer(new GetGameStatusMessageComposer(selectedGame.gameId)); + SendMessageComposer(new Game2GetAccountGameStatusMessageComposer(selectedGame.gameId)); + } + },[ selectedGame ]) + + const getBgColour = (): string => + { + return ColorUtils.uintHexColor(selectedGame.bgColor) + } + + const getBgImage = (): string => + { + return `url(${ selectedGame.assetUrl }${ selectedGame.gameNameId }_theme.png)` + } + + const getColor = () => + { + return ColorUtils.uintHexColor(selectedGame.textColor); + } + + const onPlay = () => + { + SendMessageComposer(new JoinQueueMessageComposer(selectedGame.gameId)); + } + + return + + { LocalizeText(`gamecenter.${ selectedGame.gameNameId }.description_title`) } + + { (accountStatus.hasUnlimitedGames || accountStatus.freeGamesLeft > 0) && <> + + } + { LocalizeText(`gamecenter.${ selectedGame.gameNameId }.description_content`) } + + + + + + +} diff --git a/src/components/groups/GroupView.scss b/src/components/groups/GroupView.scss new file mode 100644 index 0000000..8882b93 --- /dev/null +++ b/src/components/groups/GroupView.scss @@ -0,0 +1,190 @@ +.nitro-group-tab-image { + width: 122px; + height: 68px; + background: url('@/assets/images/groups/creator_images.png') no-repeat; + + &.tab-1 { + background-position: 0px 0px; + width: 99px; + height: 50px; + } + + &.tab-2 { + background-position: -99px 0px; + width: 98px; + height: 62px; + } + + &.tab-3 { + background-position: 0px -50px; + width: 96px; + height: 45px; + } + + &.tab-4, + &.tab-5 { + background-position: 0px -95px; + width: 114px; + height: 61px; + } +} + +.group-information { + width: 100%; + height: 100%; + display: flex; + + .group-badge { + width: 78px; + height: 78px; + + .badge-image { + background-size: contain; + } + } + + .group-description { + height: 55px; + } +} + +.nitro-group-information-standalone { + width: 500px; +} + +.nitro-group-members { + width: 400px; + max-height: 380px; + + .nitro-group-members-list-grid { + + .member-list-item { + height: 50px; + max-height: 50px; + + .avatar-head { + position: relative; + overflow: hidden; + width: 40px; + height: 50px; + + .avatar-image { + position: absolute; + left: -25px; + top: -20px; + } + } + } + } +} + +.group-badge-preview { + width: 42px; + height: 42px; + background-color: $grid-bg-color; + + &.active { + border-color: $grid-active-border-color !important; + background-color: $grid-active-bg-color; + + &:before { + position: absolute; + content: ' '; + width: 0; + height: 0; + border-top: 10px solid white; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + bottom: -10px; + } + } +} + +.group-badge-color-swatch, +.group-badge-position-swatch { + position: relative; + border-radius: $border-radius; + width: 16px; + height: 16px; + background: $white; + border: 2px solid $white; + box-shadow: inset 3px 3px rgba(0, 0, 0, .1); + + &.active { + box-shadow: none; + } +} + +.group-badge-position-swatch { + box-shadow: inset 3px 3px rgba(0, 0, 0, .1); + + &.active { + background: $primary; + } +} + +.group-badge-color-swatch { + box-shadow: inset 2px 2px rgba(0, 0, 0, .2); +} + +.group-color-swatch { + width: 30px; + height: 40px; +} + +.nitro-group-manager { + height: $nitro-group-manager-height; + width: $nitro-group-manager-width; +} + +.nitro-group-creator { + height: $nitro-group-manager-height; + width: $nitro-group-manager-width; + + .creator-tabs { + + .tab { + position: relative; + margin-left: -6px; + background-image: url('@/assets/images/groups/creator_tabs.png'); + background-repeat: no-repeat; + + &:first-child { + margin-left: 0; + } + + &.tab-blue-flat { + width: 84px; + height: 24px; + background-position: 0px 0px; + + &.active { + height: 28px; + background-position: 0px -24px; + } + } + + &.tab-blue-arrow { + width: 83px; + height: 24px; + background-position: 0px -52px; + + &.active { + height: 28px; + background-position: 0px -76px; + } + } + + &.tab-yellow { + width: 133px; + height: 28px; + background-position: 0px -104px; + + &.active { + height: 33px; + background-position: 0px -132px; + } + } + } + } +} diff --git a/src/components/groups/GroupsView.tsx b/src/components/groups/GroupsView.tsx new file mode 100644 index 0000000..a8a000b --- /dev/null +++ b/src/components/groups/GroupsView.tsx @@ -0,0 +1,63 @@ +import { AddLinkEventTracker, GroupPurchasedEvent, GroupSettingsComposer, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { SendMessageComposer, TryVisitRoom } from '../../api'; +import { useGroup, useMessageEvent } from '../../hooks'; +import { GroupCreatorView } from './views/GroupCreatorView'; +import { GroupInformationStandaloneView } from './views/GroupInformationStandaloneView'; +import { GroupManagerView } from './views/GroupManagerView'; +import { GroupMembersView } from './views/GroupMembersView'; + +export const GroupsView: FC<{}> = props => +{ + const [ isCreatorVisible, setCreatorVisible ] = useState(false); + const {} = useGroup(); + + useMessageEvent(GroupPurchasedEvent, event => + { + const parser = event.getParser(); + + setCreatorVisible(false); + TryVisitRoom(parser.roomId); + }); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'create': + setCreatorVisible(true); + return; + case 'manage': + if(!parts[2]) return; + + setCreatorVisible(false); + SendMessageComposer(new GroupSettingsComposer(Number(parts[2]))); + return; + } + }, + eventUrlPrefix: 'groups/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + return ( + <> + { isCreatorVisible && + setCreatorVisible(false) } /> } + { !isCreatorVisible && + } + + + + ); +}; diff --git a/src/components/groups/views/GroupBadgeCreatorView.tsx b/src/components/groups/views/GroupBadgeCreatorView.tsx new file mode 100644 index 0000000..8e32c92 --- /dev/null +++ b/src/components/groups/views/GroupBadgeCreatorView.tsx @@ -0,0 +1,83 @@ +import { Dispatch, FC, SetStateAction, useState } from 'react'; +import { FaPlus, FaTimes } from 'react-icons/fa'; +import { GroupBadgePart } from '../../../api'; +import { Base, Column, Flex, Grid, LayoutBadgeImageView } from '../../../common'; +import { useGroup } from '../../../hooks'; + +interface GroupBadgeCreatorViewProps +{ + badgeParts: GroupBadgePart[]; + setBadgeParts: Dispatch>; +} + +const POSITIONS: number[] = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]; + +export const GroupBadgeCreatorView: FC = props => +{ + const { badgeParts = [], setBadgeParts = null } = props; + const [ selectedIndex, setSelectedIndex ] = useState(-1); + const { groupCustomize = null } = useGroup(); + + const setPartProperty = (partIndex: number, property: string, value: number) => + { + const newBadgeParts = [ ...badgeParts ]; + + newBadgeParts[partIndex][property] = value; + + setBadgeParts(newBadgeParts); + + if(property === 'key') setSelectedIndex(-1); + } + + if(!badgeParts || !badgeParts.length) return null; + + return ( + <> + { ((selectedIndex < 0) && badgeParts && (badgeParts.length > 0)) && badgeParts.map((part, index) => + { + return ( + + setSelectedIndex(index) }> + { (badgeParts[index].code && (badgeParts[index].code.length > 0)) && + } + { (!badgeParts[index].code || !badgeParts[index].code.length) && + + + } + + { (part.type !== GroupBadgePart.BASE) && + + { POSITIONS.map((position, posIndex) => + { + return setPartProperty(index, 'position', position) }> + }) } + } + + { (groupCustomize.badgePartColors.length > 0) && groupCustomize.badgePartColors.map((item, colorIndex) => + { + return setPartProperty(index, 'color', (colorIndex + 1)) }> + }) } + + + ); + }) } + { (selectedIndex >= 0) && + + { (badgeParts[selectedIndex].type === GroupBadgePart.SYMBOL) && + setPartProperty(selectedIndex, 'key', 0) }> + + + + } + { ((badgeParts[selectedIndex].type === GroupBadgePart.BASE) ? groupCustomize.badgeBases : groupCustomize.badgeSymbols).map((item, index) => + { + return ( + setPartProperty(selectedIndex, 'key', item.id) }> + + + ); + }) } + } + + ); +} diff --git a/src/components/groups/views/GroupCreatorView.tsx b/src/components/groups/views/GroupCreatorView.tsx new file mode 100644 index 0000000..cd64ef3 --- /dev/null +++ b/src/components/groups/views/GroupCreatorView.tsx @@ -0,0 +1,164 @@ +import { GroupBuyComposer, GroupBuyDataComposer, GroupBuyDataEvent } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { HasHabboClub, IGroupData, LocalizeText, SendMessageComposer } from '../../../api'; +import { Base, Button, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; +import { useMessageEvent } from '../../../hooks'; +import { GroupTabBadgeView } from './tabs/GroupTabBadgeView'; +import { GroupTabColorsView } from './tabs/GroupTabColorsView'; +import { GroupTabCreatorConfirmationView } from './tabs/GroupTabCreatorConfirmationView'; +import { GroupTabIdentityView } from './tabs/GroupTabIdentityView'; + +interface GroupCreatorViewProps +{ + onClose: () => void; +} + +const TABS: number[] = [ 1, 2, 3, 4 ]; + +export const GroupCreatorView: FC = props => +{ + const { onClose = null } = props; + const [ currentTab, setCurrentTab ] = useState(1); + const [ closeAction, setCloseAction ] = useState<{ action: () => boolean }>(null); + const [ groupData, setGroupData ] = useState(null); + const [ availableRooms, setAvailableRooms ] = useState<{ id: number, name: string }[]>(null); + const [ purchaseCost, setPurchaseCost ] = useState(0); + + const onCloseClose = () => + { + setCloseAction(null); + setGroupData(null); + + if(onClose) onClose(); + } + + const buyGroup = () => + { + if(!groupData) return; + + const badge = []; + + groupData.groupBadgeParts.forEach(part => + { + if(part.code) + { + badge.push(part.key); + badge.push(part.color); + badge.push(part.position); + } + }); + + SendMessageComposer(new GroupBuyComposer(groupData.groupName, groupData.groupDescription, groupData.groupHomeroomId, groupData.groupColors[0], groupData.groupColors[1], badge)); + } + + const previousStep = () => + { + if(closeAction && closeAction.action) + { + if(!closeAction.action()) return; + } + + if(currentTab === 1) + { + onClose(); + + return; + } + + setCurrentTab(value => value - 1); + } + + const nextStep = () => + { + if(closeAction && closeAction.action) + { + if(!closeAction.action()) return; + } + + if(currentTab === 4) + { + buyGroup(); + + return; + } + + setCurrentTab(value => (value === 4 ? value : value + 1)); + } + + useMessageEvent(GroupBuyDataEvent, event => + { + const parser = event.getParser(); + + const rooms: { id: number, name: string }[] = []; + + parser.availableRooms.forEach((name, id) => rooms.push({ id, name })); + + setAvailableRooms(rooms); + setPurchaseCost(parser.groupCost); + }); + + useEffect(() => + { + setCurrentTab(1); + + setGroupData({ + groupId: -1, + groupName: null, + groupDescription: null, + groupHomeroomId: -1, + groupState: 1, + groupCanMembersDecorate: true, + groupColors: null, + groupBadgeParts: null + }); + + SendMessageComposer(new GroupBuyDataComposer()); + }, [ setGroupData ]); + + if(!groupData) return null; + + return ( + + + + + { TABS.map((tab, index) => + { + return ( + + { LocalizeText(`group.create.steplabel.${ tab }`) } + + ); + }) } + + + + + + { LocalizeText(`group.create.stepcaption.${ currentTab }`) } + { LocalizeText(`group.create.stepdesc.${ currentTab }`) } + + + + { (currentTab === 1) && + } + { (currentTab === 2) && + } + { (currentTab === 3) && + } + { (currentTab === 4) && + } + + + + + + + + + ); +}; diff --git a/src/components/groups/views/GroupInformationStandaloneView.tsx b/src/components/groups/views/GroupInformationStandaloneView.tsx new file mode 100644 index 0000000..d4206d7 --- /dev/null +++ b/src/components/groups/views/GroupInformationStandaloneView.tsx @@ -0,0 +1,29 @@ +import { GroupInformationEvent, GroupInformationParser } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { LocalizeText } from '../../../api'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../common'; +import { useMessageEvent } from '../../../hooks'; +import { GroupInformationView } from './GroupInformationView'; + +export const GroupInformationStandaloneView: FC<{}> = props => +{ + const [ groupInformation, setGroupInformation ] = useState(null); + + useMessageEvent(GroupInformationEvent, event => + { + const parser = event.getParser(); + + if((groupInformation && (groupInformation.id === parser.id)) || parser.flag) setGroupInformation(parser); + }); + + if(!groupInformation) return null; + + return ( + + setGroupInformation(null) } /> + + setGroupInformation(null) } /> + + + ); +}; diff --git a/src/components/groups/views/GroupInformationView.tsx b/src/components/groups/views/GroupInformationView.tsx new file mode 100644 index 0000000..a13408b --- /dev/null +++ b/src/components/groups/views/GroupInformationView.tsx @@ -0,0 +1,146 @@ +import { CreateLinkEvent, GetSessionDataManager, GroupInformationParser, GroupRemoveMemberComposer } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { CatalogPageName, GetGroupManager, GetGroupMembers, GroupMembershipType, GroupType, LocalizeText, SendMessageComposer, TryJoinGroup, TryVisitRoom } from '../../../api'; +import { Button, Column, Flex, Grid, GridProps, LayoutBadgeImageView, Text } from '../../../common'; +import { useNotification } from '../../../hooks'; + +const STATES: string[] = [ 'regular', 'exclusive', 'private' ]; + +interface GroupInformationViewProps extends GridProps +{ + groupInformation: GroupInformationParser; + onJoin?: () => void; + onClose?: () => void; +} + +export const GroupInformationView: FC = props => +{ + const { groupInformation = null, onClose = null, overflow = 'hidden', ...rest } = props; + const { showConfirm = null } = useNotification(); + + const isRealOwner = (groupInformation && (groupInformation.ownerName === GetSessionDataManager().userName)); + + const joinGroup = () => (groupInformation && TryJoinGroup(groupInformation.id)); + + const leaveGroup = () => + { + showConfirm(LocalizeText('group.leaveconfirm.desc'), () => + { + SendMessageComposer(new GroupRemoveMemberComposer(groupInformation.id, GetSessionDataManager().userId)); + + if(onClose) onClose(); + }, null); + } + + const getRoleIcon = () => + { + if(groupInformation.membershipType === GroupMembershipType.NOT_MEMBER || groupInformation.membershipType === GroupMembershipType.REQUEST_PENDING) return null; + + if(isRealOwner) return ; + + if(groupInformation.isAdmin) return ; + + return ; + } + + const getButtonText = () => + { + if(isRealOwner) return 'group.youareowner'; + + if(groupInformation.type === GroupType.PRIVATE && groupInformation.membershipType !== GroupMembershipType.MEMBER) return ''; + + if(groupInformation.membershipType === GroupMembershipType.MEMBER) return 'group.leave'; + + if((groupInformation.membershipType === GroupMembershipType.NOT_MEMBER) && groupInformation.type === GroupType.REGULAR) return 'group.join'; + + if(groupInformation.membershipType === GroupMembershipType.REQUEST_PENDING) return 'group.membershippending'; + + if((groupInformation.membershipType === GroupMembershipType.NOT_MEMBER) && groupInformation.type === GroupType.EXCLUSIVE) return 'group.requestmembership'; + } + + const handleButtonClick = () => + { + if((groupInformation.type === GroupType.PRIVATE) && (groupInformation.membershipType === GroupMembershipType.NOT_MEMBER)) return; + + if(groupInformation.membershipType === GroupMembershipType.MEMBER) + { + leaveGroup(); + + return; + } + + joinGroup(); + } + + const handleAction = (action: string) => + { + switch(action) + { + case 'members': + GetGroupMembers(groupInformation.id); + break; + case 'members_pending': + GetGroupMembers(groupInformation.id, 2); + break; + case 'manage': + GetGroupManager(groupInformation.id); + break; + case 'homeroom': + TryVisitRoom(groupInformation.roomId); + break; + case 'furniture': + CreateLinkEvent('catalog/open/' + CatalogPageName.GUILD_CUSTOM_FURNI); + break; + case 'popular_groups': + CreateLinkEvent('navigator/search/groups'); + break; + } + } + + if(!groupInformation) return null; + + return ( + + + + + + + handleAction('members') }>{ LocalizeText('group.membercount', [ 'totalMembers' ], [ groupInformation.membersCount.toString() ]) } + { (groupInformation.pendingRequestsCount > 0) && + handleAction('members_pending') }>{ LocalizeText('group.pendingmembercount', [ 'amount' ], [ groupInformation.pendingRequestsCount.toString() ]) } } + { groupInformation.isOwner && + handleAction('manage') }>{ LocalizeText('group.manage') } } + + { getRoleIcon() } + + + + + + { groupInformation.title } + + + { groupInformation.canMembersDecorate && + } + + + { LocalizeText('group.created', [ 'date', 'owner' ], [ groupInformation.createdAt, groupInformation.ownerName ]) } + + { groupInformation.description } + + + + handleAction('homeroom') }>{ LocalizeText('group.linktobase') } + handleAction('furniture') }>{ LocalizeText('group.buyfurni') } + handleAction('popular_groups') }>{ LocalizeText('group.showgroups') } + + { (groupInformation.type !== GroupType.PRIVATE || groupInformation.type === GroupType.PRIVATE && groupInformation.membershipType === GroupMembershipType.MEMBER) && + } + + + + ); +}; diff --git a/src/components/groups/views/GroupManagerView.tsx b/src/components/groups/views/GroupManagerView.tsx new file mode 100644 index 0000000..9e1d625 --- /dev/null +++ b/src/components/groups/views/GroupManagerView.tsx @@ -0,0 +1,119 @@ +import { GroupBadgePart, GroupInformationEvent, GroupSettingsEvent } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { IGroupData, LocalizeText } from '../../../api'; +import { Base, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text } from '../../../common'; +import { useMessageEvent } from '../../../hooks'; +import { GroupTabBadgeView } from './tabs/GroupTabBadgeView'; +import { GroupTabColorsView } from './tabs/GroupTabColorsView'; +import { GroupTabIdentityView } from './tabs/GroupTabIdentityView'; +import { GroupTabSettingsView } from './tabs/GroupTabSettingsView'; + +const TABS: number[] = [ 1, 2, 3, 5 ]; + +export const GroupManagerView: FC<{}> = props => +{ + const [ currentTab, setCurrentTab ] = useState(1); + const [ closeAction, setCloseAction ] = useState<{ action: () => boolean }>(null); + const [ groupData, setGroupData ] = useState(null); + + const onClose = () => + { + setCloseAction(prevValue => + { + if(prevValue && prevValue.action) prevValue.action(); + + return null; + }); + + setGroupData(null); + } + + const changeTab = (tab: number) => + { + if(closeAction && closeAction.action) closeAction.action(); + + setCurrentTab(tab); + } + + useMessageEvent(GroupInformationEvent, event => + { + const parser = event.getParser(); + + if(!groupData || (groupData.groupId !== parser.id)) return; + + setGroupData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.groupName = parser.title; + newValue.groupDescription = parser.description; + newValue.groupState = parser.type; + newValue.groupCanMembersDecorate = parser.canMembersDecorate; + + return newValue; + }); + }); + + useMessageEvent(GroupSettingsEvent, event => + { + const parser = event.getParser(); + + const groupBadgeParts: GroupBadgePart[] = []; + + parser.badgeParts.forEach((part, id) => + { + groupBadgeParts.push(new GroupBadgePart( + part.isBase ? GroupBadgePart.BASE : GroupBadgePart.SYMBOL, + part.key, + part.color, + part.position + )); + }); + + setGroupData({ + groupId: parser.id, + groupName: parser.title, + groupDescription: parser.description, + groupHomeroomId: parser.roomId, + groupState: parser.state, + groupCanMembersDecorate: parser.canMembersDecorate, + groupColors: [ parser.colorA, parser.colorB ], + groupBadgeParts + }); + }); + + if(!groupData || (groupData.groupId <= 0)) return null; + + return ( + + + + { TABS.map(tab => + { + return ( changeTab(tab) }> + { LocalizeText(`group.edit.tab.${ tab }`) } + ); + }) } + + + + + + { LocalizeText(`group.edit.tabcaption.${ currentTab }`) } + { LocalizeText(`group.edit.tabdesc.${ currentTab }`) } + + + + { (currentTab === 1) && + } + { (currentTab === 2) && + } + { (currentTab === 3) && + } + { (currentTab === 5) && + } + + + + ); +}; diff --git a/src/components/groups/views/GroupMembersView.tsx b/src/components/groups/views/GroupMembersView.tsx new file mode 100644 index 0000000..5cf1be8 --- /dev/null +++ b/src/components/groups/views/GroupMembersView.tsx @@ -0,0 +1,210 @@ +import { AddLinkEventTracker, GetSessionDataManager, GroupAdminGiveComposer, GroupAdminTakeComposer, GroupConfirmMemberRemoveEvent, GroupConfirmRemoveMemberComposer, GroupMemberParser, GroupMembersComposer, GroupMembersEvent, GroupMembershipAcceptComposer, GroupMembershipDeclineComposer, GroupMembersParser, GroupRank, GroupRemoveMemberComposer, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useState } from 'react'; +import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; +import { GetUserProfile, LocalizeText, SendMessageComposer } from '../../../api'; +import { Base, Button, Column, Flex, Grid, LayoutAvatarImageView, LayoutBadgeImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; +import { useMessageEvent, useNotification } from '../../../hooks'; + +export const GroupMembersView: FC<{}> = props => +{ + const [ groupId, setGroupId ] = useState(-1); + const [ levelId, setLevelId ] = useState(-1); + const [ membersData, setMembersData ] = useState(null); + const [ pageId, setPageId ] = useState(-1); + const [ totalPages, setTotalPages ] = useState(0); + const [ searchQuery, setSearchQuery ] = useState(''); + const [ removingMemberName, setRemovingMemberName ] = useState(null); + const { showConfirm = null } = useNotification(); + + const getRankDescription = (member: GroupMemberParser) => + { + if(member.rank === GroupRank.OWNER) return 'group.members.owner'; + + if(membersData.admin) + { + if(member.rank === GroupRank.ADMIN) return 'group.members.removerights'; + + if(member.rank === GroupRank.MEMBER) return 'group.members.giverights'; + } + + return ''; + } + + const refreshMembers = useCallback(() => + { + if((groupId === -1) || (levelId === -1) || (pageId === -1)) return; + + SendMessageComposer(new GroupMembersComposer(groupId, pageId, searchQuery, levelId)); + }, [ groupId, levelId, pageId, searchQuery ]); + + const toggleAdmin = (member: GroupMemberParser) => + { + if(!membersData.admin || (member.rank === GroupRank.OWNER)) return; + + if(member.rank !== GroupRank.ADMIN) SendMessageComposer(new GroupAdminGiveComposer(membersData.groupId, member.id)); + else SendMessageComposer(new GroupAdminTakeComposer(membersData.groupId, member.id)); + + refreshMembers(); + } + + const acceptMembership = (member: GroupMemberParser) => + { + if(!membersData.admin || (member.rank !== GroupRank.REQUESTED)) return; + + SendMessageComposer(new GroupMembershipAcceptComposer(membersData.groupId, member.id)); + + refreshMembers(); + } + + const removeMemberOrDeclineMembership = (member: GroupMemberParser) => + { + if(!membersData.admin) return; + + if(member.rank === GroupRank.REQUESTED) + { + SendMessageComposer(new GroupMembershipDeclineComposer(membersData.groupId, member.id)); + + refreshMembers(); + + return; + } + + setRemovingMemberName(member.name); + SendMessageComposer(new GroupConfirmRemoveMemberComposer(membersData.groupId, member.id)); + } + + useMessageEvent(GroupMembersEvent, event => + { + const parser = event.getParser(); + + setMembersData(parser); + setLevelId(parser.level); + setTotalPages(Math.ceil(parser.totalMembersCount / parser.pageSize)); + }); + + useMessageEvent(GroupConfirmMemberRemoveEvent, event => + { + const parser = event.getParser(); + + showConfirm(LocalizeText(((parser.furnitureCount > 0) ? 'group.kickconfirm.desc' : 'group.kickconfirm_nofurni.desc'), [ 'user', 'amount' ], [ removingMemberName, parser.furnitureCount.toString() ]), () => + { + SendMessageComposer(new GroupRemoveMemberComposer(membersData.groupId, parser.userId)); + + refreshMembers(); + }, null); + + setRemovingMemberName(null); + }); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + const groupId = (parseInt(parts[1]) || -1); + const levelId = (parseInt(parts[2]) || 3); + + setGroupId(groupId); + setLevelId(levelId); + setPageId(0); + }, + eventUrlPrefix: 'group-members/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + useEffect(() => + { + setPageId(0); + }, [ groupId, levelId, searchQuery ]); + + useEffect(() => + { + if((groupId === -1) || (levelId === -1) || (pageId === -1)) return; + + SendMessageComposer(new GroupMembersComposer(groupId, pageId, searchQuery, levelId)); + }, [ groupId, levelId, pageId, searchQuery ]); + + useEffect(() => + { + if(groupId === -1) return; + + setLevelId(-1); + setMembersData(null); + setTotalPages(0); + setSearchQuery(''); + setRemovingMemberName(null); + }, [ groupId ]); + + if((groupId === -1) || !membersData) return null; + + return ( + + setGroupId(-1) } /> + + + + + + + setSearchQuery(event.target.value) } /> + + + + + { membersData.result.map((member, index) => + { + return ( + +
GetUserProfile(member.id) }> + +
+ + GetUserProfile(member.id) }>{ member.name } + { (member.rank !== GroupRank.REQUESTED) && + { LocalizeText('group.members.since', [ 'date' ], [ member.joinedAt ]) } } + + + { (member.rank !== GroupRank.REQUESTED) && + + toggleAdmin(member) } /> + } + { membersData.admin && (member.rank === GroupRank.REQUESTED) && + + acceptMembership(member) }> + } + { membersData.admin && (member.rank !== GroupRank.OWNER) && (member.id !== GetSessionDataManager().userId) && + + removeMemberOrDeclineMembership(member) }> + } + +
+ ); + }) } +
+ + + + { LocalizeText('group.members.pageinfo', [ 'amount', 'page', 'totalPages' ], [ membersData.totalMembersCount.toString(), (membersData.pageIndex + 1).toString(), totalPages.toString() ]) } + + + +
+
+ ); +}; diff --git a/src/components/groups/views/GroupRoomInformationView.tsx b/src/components/groups/views/GroupRoomInformationView.tsx new file mode 100644 index 0000000..c8c8d99 --- /dev/null +++ b/src/components/groups/views/GroupRoomInformationView.tsx @@ -0,0 +1,132 @@ +import { DesktopViewEvent, GetGuestRoomResultEvent, GetSessionDataManager, GroupInformationComposer, GroupInformationEvent, GroupInformationParser, GroupRemoveMemberComposer, HabboGroupDeactivatedMessageEvent, RoomEntryInfoMessageEvent } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { FaChevronDown, FaChevronUp } from 'react-icons/fa'; +import { GetGroupInformation, GetGroupManager, GroupMembershipType, GroupType, LocalizeText, SendMessageComposer, TryJoinGroup } from '../../../api'; +import { Base, Button, Column, Flex, LayoutBadgeImageView, Text } from '../../../common'; +import { useMessageEvent, useNotification } from '../../../hooks'; + +export const GroupRoomInformationView: FC<{}> = props => +{ + const [ expectedGroupId, setExpectedGroupId ] = useState(0); + const [ groupInformation, setGroupInformation ] = useState(null); + const [ isOpen, setIsOpen ] = useState(true); + const { showConfirm = null } = useNotification(); + + useMessageEvent(DesktopViewEvent, event => + { + setExpectedGroupId(0); + setGroupInformation(null); + }); + + useMessageEvent(RoomEntryInfoMessageEvent, event => + { + setExpectedGroupId(0); + setGroupInformation(null); + }); + + useMessageEvent(GetGuestRoomResultEvent, event => + { + const parser = event.getParser(); + + if(!parser.roomEnter) return; + + if(parser.data.habboGroupId > 0) + { + setExpectedGroupId(parser.data.habboGroupId); + SendMessageComposer(new GroupInformationComposer(parser.data.habboGroupId, false)); + } + else + { + setExpectedGroupId(0); + setGroupInformation(null); + } + }); + + useMessageEvent(HabboGroupDeactivatedMessageEvent, event => + { + const parser = event.getParser(); + + if(!groupInformation || ((parser.groupId !== groupInformation.id) && (parser.groupId !== expectedGroupId))) return; + + setExpectedGroupId(0); + setGroupInformation(null); + }); + + useMessageEvent(GroupInformationEvent, event => + { + const parser = event.getParser(); + + if(parser.id !== expectedGroupId) return; + + setGroupInformation(parser); + }); + + const leaveGroup = () => + { + showConfirm(LocalizeText('group.leaveconfirm.desc'), () => + { + SendMessageComposer(new GroupRemoveMemberComposer(groupInformation.id, GetSessionDataManager().userId)); + }, null); + } + + const isRealOwner = (groupInformation && (groupInformation.ownerName === GetSessionDataManager().userName)); + + const getButtonText = () => + { + if(isRealOwner) return 'group.manage'; + + if(groupInformation.type === GroupType.PRIVATE) return ''; + + if(groupInformation.membershipType === GroupMembershipType.MEMBER) return 'group.leave'; + + if((groupInformation.membershipType === GroupMembershipType.NOT_MEMBER) && groupInformation.type === GroupType.REGULAR) return 'group.join'; + + if(groupInformation.membershipType === GroupMembershipType.REQUEST_PENDING) return 'group.membershippending'; + + if((groupInformation.membershipType === GroupMembershipType.NOT_MEMBER) && groupInformation.type === GroupType.EXCLUSIVE) return 'group.requestmembership'; + } + + const handleButtonClick = () => + { + if(isRealOwner) return GetGroupManager(groupInformation.id); + + if((groupInformation.type === GroupType.PRIVATE) && (groupInformation.membershipType === GroupMembershipType.NOT_MEMBER)) return; + + if(groupInformation.membershipType === GroupMembershipType.MEMBER) + { + leaveGroup(); + + return; + } + + TryJoinGroup(groupInformation.id); + } + + if(!groupInformation) return null; + + return ( + + + setIsOpen(value => !value) }> + { LocalizeText('group.homeroominfo.title') } + { isOpen && } + { !isOpen && } + + { isOpen && + <> + GetGroupInformation(groupInformation.id) }> + + + + { groupInformation.title } + + { (groupInformation.type !== GroupType.PRIVATE || isRealOwner) && + + } + } + + + ); +}; diff --git a/src/components/groups/views/tabs/GroupTabBadgeView.tsx b/src/components/groups/views/tabs/GroupTabBadgeView.tsx new file mode 100644 index 0000000..73d65c0 --- /dev/null +++ b/src/components/groups/views/tabs/GroupTabBadgeView.tsx @@ -0,0 +1,120 @@ +import { GroupSaveBadgeComposer } from '@nitrots/nitro-renderer'; +import { Dispatch, FC, SetStateAction, useCallback, useEffect, useState } from 'react'; +import { GroupBadgePart, IGroupData, SendMessageComposer } from '../../../../api'; +import { Column, Flex, Grid, LayoutBadgeImageView } from '../../../../common'; +import { useGroup } from '../../../../hooks'; +import { GroupBadgeCreatorView } from '../GroupBadgeCreatorView'; + +interface GroupTabBadgeViewProps +{ + skipDefault?: boolean; + setCloseAction: Dispatch boolean }>>; + groupData: IGroupData; + setGroupData: Dispatch>; +} + +export const GroupTabBadgeView: FC = props => +{ + const { groupData = null, setGroupData = null, setCloseAction = null, skipDefault = null } = props; + const [ badgeParts, setBadgeParts ] = useState(null); + const { groupCustomize = null } = useGroup(); + + const getModifiedBadgeCode = () => + { + if(!badgeParts || !badgeParts.length) return ''; + + let badgeCode = ''; + + badgeParts.forEach(part => (part.code && (badgeCode += part.code))); + + return badgeCode; + } + + const saveBadge = useCallback(() => + { + if(!groupData || !badgeParts || !badgeParts.length) return false; + + if((groupData.groupBadgeParts === badgeParts)) return true; + + if(groupData.groupId <= 0) + { + setGroupData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.groupBadgeParts = badgeParts; + + return newValue; + }); + + return true; + } + + const badge = []; + + badgeParts.forEach(part => + { + if(!part.code) return; + + badge.push(part.key); + badge.push(part.color); + badge.push(part.position); + }); + + SendMessageComposer(new GroupSaveBadgeComposer(groupData.groupId, badge)); + + return true; + }, [ groupData, badgeParts, setGroupData ]); + + useEffect(() => + { + if(groupData.groupBadgeParts) return; + + const badgeParts = [ + new GroupBadgePart(GroupBadgePart.BASE, groupCustomize.badgeBases[0].id, groupCustomize.badgePartColors[0].id), + new GroupBadgePart(GroupBadgePart.SYMBOL, 0, groupCustomize.badgePartColors[0].id), + new GroupBadgePart(GroupBadgePart.SYMBOL, 0, groupCustomize.badgePartColors[0].id), + new GroupBadgePart(GroupBadgePart.SYMBOL, 0, groupCustomize.badgePartColors[0].id), + new GroupBadgePart(GroupBadgePart.SYMBOL, 0, groupCustomize.badgePartColors[0].id) + ]; + + setGroupData(prevValue => + { + const groupBadgeParts = badgeParts; + + return { ...prevValue, groupBadgeParts }; + }); + }, [ groupData.groupBadgeParts, groupCustomize, setGroupData ]); + + useEffect(() => + { + if(groupData.groupId <= 0) + { + setBadgeParts(groupData.groupBadgeParts ? [ ...groupData.groupBadgeParts ] : null); + + return; + } + + setBadgeParts(groupData.groupBadgeParts); + }, [ groupData ]); + + useEffect(() => + { + setCloseAction({ action: saveBadge }); + + return () => setCloseAction(null); + }, [ setCloseAction, saveBadge ]); + + return ( + + + + + + + + + + + ); +}; diff --git a/src/components/groups/views/tabs/GroupTabColorsView.tsx b/src/components/groups/views/tabs/GroupTabColorsView.tsx new file mode 100644 index 0000000..61f3c86 --- /dev/null +++ b/src/components/groups/views/tabs/GroupTabColorsView.tsx @@ -0,0 +1,127 @@ +import { GroupSaveColorsComposer } from '@nitrots/nitro-renderer'; +import { Dispatch, FC, SetStateAction, useCallback, useEffect, useState } from 'react'; +import { IGroupData, LocalizeText, SendMessageComposer } from '../../../../api'; +import { AutoGrid, Base, classNames, Column, Flex, Grid, Text } from '../../../../common'; +import { useGroup } from '../../../../hooks'; + +interface GroupTabColorsViewProps +{ + groupData: IGroupData; + setGroupData: Dispatch>; + setCloseAction: Dispatch boolean }>>; +} + +export const GroupTabColorsView: FC = props => +{ + const { groupData = null, setGroupData = null, setCloseAction = null } = props; + const [ colors, setColors ] = useState(null); + const { groupCustomize = null } = useGroup(); + + const getGroupColor = (colorIndex: number) => + { + if(colorIndex === 0) return groupCustomize.groupColorsA.find(color => (color.id === colors[colorIndex])).color; + + return groupCustomize.groupColorsB.find(color => (color.id === colors[colorIndex])).color; + } + + const selectColor = (colorIndex: number, colorId: number) => + { + setColors(prevValue => + { + const newColors = [ ...prevValue ]; + + newColors[colorIndex] = colorId; + + return newColors; + }); + } + + const saveColors = useCallback(() => + { + if(!groupData || !colors || !colors.length) return false; + + if(groupData.groupColors === colors) return true; + + if(groupData.groupId <= 0) + { + setGroupData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.groupColors = [ ...colors ]; + + return newValue; + }); + + return true; + } + + SendMessageComposer(new GroupSaveColorsComposer(groupData.groupId, colors[0], colors[1])); + + return true; + }, [ groupData, colors, setGroupData ]); + + useEffect(() => + { + if(!groupCustomize.groupColorsA || !groupCustomize.groupColorsB || groupData.groupColors) return; + + const groupColors = [ groupCustomize.groupColorsA[0].id, groupCustomize.groupColorsB[0].id ]; + + setGroupData(prevValue => + { + return { ...prevValue, groupColors }; + }); + }, [ groupCustomize, groupData.groupColors, setGroupData ]); + + useEffect(() => + { + if(groupData.groupId <= 0) + { + setColors(groupData.groupColors ? [ ...groupData.groupColors ] : null); + + return; + } + + setColors(groupData.groupColors); + }, [ groupData ]); + + useEffect(() => + { + setCloseAction({ action: saveColors }); + + return () => setCloseAction(null); + }, [ setCloseAction, saveColors ]); + + if(!colors) return null; + + return ( + + + { LocalizeText('group.edit.color.guild.color') } + { groupData.groupColors && (groupData.groupColors.length > 0) && + + + + } + + + { LocalizeText('group.edit.color.primary.color') } + + { groupData.groupColors && groupCustomize.groupColorsA && groupCustomize.groupColorsA.map((item, index) => + { + return
selectColor(0, item.id) }>
+ }) } +
+
+ + { LocalizeText('group.edit.color.secondary.color') } + + { groupData.groupColors && groupCustomize.groupColorsB && groupCustomize.groupColorsB.map((item, index) => + { + return
selectColor(1, item.id) }>
+ }) } +
+
+
+ ); +}; diff --git a/src/components/groups/views/tabs/GroupTabCreatorConfirmationView.tsx b/src/components/groups/views/tabs/GroupTabCreatorConfirmationView.tsx new file mode 100644 index 0000000..3046038 --- /dev/null +++ b/src/components/groups/views/tabs/GroupTabCreatorConfirmationView.tsx @@ -0,0 +1,67 @@ +import { Dispatch, FC, SetStateAction } from 'react'; +import { IGroupData, LocalizeText } from '../../../../api'; +import { Base, Column, Flex, Grid, LayoutBadgeImageView, Text } from '../../../../common'; +import { useGroup } from '../../../../hooks'; + +interface GroupTabCreatorConfirmationViewProps +{ + groupData: IGroupData; + setGroupData: Dispatch>; + purchaseCost: number; +} + +export const GroupTabCreatorConfirmationView: FC = props => +{ + const { groupData = null, setGroupData = null, purchaseCost = 0 } = props; + const { groupCustomize = null } = useGroup(); + + const getCompleteBadgeCode = () => + { + if(!groupData || !groupData.groupBadgeParts || !groupData.groupBadgeParts.length) return ''; + + let badgeCode = ''; + + groupData.groupBadgeParts.forEach(part => (part.code && (badgeCode += part.code))); + + return badgeCode; + } + + const getGroupColor = (colorIndex: number) => + { + if(colorIndex === 0) return groupCustomize.groupColorsA.find(c => c.id === groupData.groupColors[colorIndex]).color; + + return groupCustomize.groupColorsB.find(c => c.id === groupData.groupColors[colorIndex]).color; + } + + if(!groupData) return null; + + return ( + + + + { LocalizeText('group.create.confirm.guildbadge') } + + + + { LocalizeText('group.edit.color.guild.color') } + + + + + + + + + + { groupData.groupName } + { groupData.groupDescription } + + { LocalizeText('group.create.confirm.info') } + + + { LocalizeText('group.create.confirm.buyinfo', [ 'amount' ], [ purchaseCost.toString() ]) } + + + + ); +}; diff --git a/src/components/groups/views/tabs/GroupTabIdentityView.tsx b/src/components/groups/views/tabs/GroupTabIdentityView.tsx new file mode 100644 index 0000000..3a35d47 --- /dev/null +++ b/src/components/groups/views/tabs/GroupTabIdentityView.tsx @@ -0,0 +1,116 @@ +import { CreateLinkEvent, GroupDeleteComposer, GroupSaveInformationComposer } from '@nitrots/nitro-renderer'; +import { Dispatch, FC, SetStateAction, useCallback, useEffect, useState } from 'react'; +import { IGroupData, LocalizeText, SendMessageComposer } from '../../../../api'; +import { Base, Button, Column, Flex, Text } from '../../../../common'; +import { useNotification } from '../../../../hooks'; + +interface GroupTabIdentityViewProps +{ + groupData: IGroupData; + setGroupData: Dispatch>; + setCloseAction: Dispatch boolean }>>; + onClose: () => void; + isCreator?: boolean; + availableRooms?: { id: number, name: string }[]; +} + +export const GroupTabIdentityView: FC = props => +{ + const { groupData = null, setGroupData = null, setCloseAction = null, onClose = null, isCreator = false, availableRooms = [] } = props; + const [ groupName, setGroupName ] = useState(''); + const [ groupDescription, setGroupDescription ] = useState(''); + const [ groupHomeroomId, setGroupHomeroomId ] = useState(-1); + const { showConfirm = null } = useNotification(); + + const deleteGroup = () => + { + if(!groupData || (groupData.groupId <= 0)) return; + + showConfirm(LocalizeText('group.deleteconfirm.desc'), () => + { + SendMessageComposer(new GroupDeleteComposer(groupData.groupId)); + + if(onClose) onClose(); + }, null, null, null, LocalizeText('group.deleteconfirm.title')); + } + + const saveIdentity = useCallback(() => + { + if(!groupData || !groupName || !groupName.length) return false; + + if((groupName === groupData.groupName) && (groupDescription === groupData.groupDescription)) return true; + + if(groupData.groupId <= 0) + { + if(groupHomeroomId <= 0) return false; + + setGroupData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.groupName = groupName; + newValue.groupDescription = groupDescription; + newValue.groupHomeroomId = groupHomeroomId; + + return newValue; + }); + + return true; + } + + SendMessageComposer(new GroupSaveInformationComposer(groupData.groupId, groupName, (groupDescription || ''))); + + return true; + }, [ groupData, groupName, groupDescription, groupHomeroomId, setGroupData ]); + + useEffect(() => + { + setGroupName(groupData.groupName || ''); + setGroupDescription(groupData.groupDescription || ''); + setGroupHomeroomId(groupData.groupHomeroomId); + }, [ groupData ]); + + useEffect(() => + { + setCloseAction({ action: saveIdentity }); + + return () => setCloseAction(null); + }, [ setCloseAction, saveIdentity ]); + + if(!groupData) return null; + + return ( + + + + { LocalizeText('group.edit.name') } + setGroupName(event.target.value) } /> + + + { LocalizeText('group.edit.desc') } + + + + ); +}; diff --git a/src/components/guide-tool/views/GuideToolUserFeedbackView.tsx b/src/components/guide-tool/views/GuideToolUserFeedbackView.tsx new file mode 100644 index 0000000..9ec7280 --- /dev/null +++ b/src/components/guide-tool/views/GuideToolUserFeedbackView.tsx @@ -0,0 +1,43 @@ +import { GuideSessionFeedbackMessageComposer } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { LocalizeText, SendMessageComposer } from '../../../api'; +import { Button, Column, Flex, Text } from '../../../common'; + +interface GuideToolUserFeedbackViewProps +{ + userName: string; +} + +export const GuideToolUserFeedbackView: FC = props => +{ + const { userName = null } = props; + + const giveFeedback = (recommend: boolean) => SendMessageComposer(new GuideSessionFeedbackMessageComposer(recommend)); + + return ( + + + + { userName } + { LocalizeText('guide.help.request.user.feedback.guide.desc') } + + + + + { LocalizeText('guide.help.request.user.feedback.closed.title') } + { LocalizeText('guide.help.request.user.feedback.closed.desc') } + + { userName && (userName.length > 0) && + <> +
+ + { LocalizeText('guide.help.request.user.feedback.question') } + + + + + + } +
+ ); +}; diff --git a/src/components/guide-tool/views/GuideToolUserNoHelpersView.tsx b/src/components/guide-tool/views/GuideToolUserNoHelpersView.tsx new file mode 100644 index 0000000..6fcbfd5 --- /dev/null +++ b/src/components/guide-tool/views/GuideToolUserNoHelpersView.tsx @@ -0,0 +1,13 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../api'; +import { Column, Text } from '../../../common'; + +export const GuideToolUserNoHelpersView: FC<{}> = props => +{ + return ( + + { LocalizeText('guide.help.request.no_tour_guides.title') } + { LocalizeText('guide.help.request.no_tour_guides.message') } + + ); +}; \ No newline at end of file diff --git a/src/components/guide-tool/views/GuideToolUserPendingView.tsx b/src/components/guide-tool/views/GuideToolUserPendingView.tsx new file mode 100644 index 0000000..81faaff --- /dev/null +++ b/src/components/guide-tool/views/GuideToolUserPendingView.tsx @@ -0,0 +1,33 @@ +import { GuideSessionRequesterCancelsMessageComposer } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { LocalizeText, SendMessageComposer } from '../../../api'; +import { Button, Column, Text } from '../../../common'; + +interface GuideToolUserPendingViewProps +{ + helpRequestDescription: string; + helpRequestAverageTime: number; +} + +export const GuideToolUserPendingView: FC = props => +{ + const { helpRequestDescription = null, helpRequestAverageTime = 0 } = props; + + const cancelRequest = () => SendMessageComposer(new GuideSessionRequesterCancelsMessageComposer()); + + return ( + + + { LocalizeText('guide.help.request.guide.accept.request.title') } + { LocalizeText('guide.help.request.type.1') } + { helpRequestDescription } + + + { LocalizeText('guide.help.request.user.pending.info.title') } + { LocalizeText('guide.help.request.user.pending.info.message') } + { LocalizeText('guide.help.request.user.pending.info.waiting', [ 'waitingtime' ], [ helpRequestAverageTime.toString() ]) } + + + + ); +}; diff --git a/src/components/guide-tool/views/GuideToolUserSomethingWrogView.tsx b/src/components/guide-tool/views/GuideToolUserSomethingWrogView.tsx new file mode 100644 index 0000000..1e8ec5e --- /dev/null +++ b/src/components/guide-tool/views/GuideToolUserSomethingWrogView.tsx @@ -0,0 +1,12 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../api'; +import { Column, Text } from '../../../common'; + +export const GuideToolUserSomethingWrogView: FC<{}> = props => +{ + return ( + + { LocalizeText('guide.help.request.user.guide.disconnected.error.desc') } + + ); +}; \ No newline at end of file diff --git a/src/components/guide-tool/views/GuideToolUserThanksView.tsx b/src/components/guide-tool/views/GuideToolUserThanksView.tsx new file mode 100644 index 0000000..4953b0f --- /dev/null +++ b/src/components/guide-tool/views/GuideToolUserThanksView.tsx @@ -0,0 +1,13 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../api'; +import { Column, Text } from '../../../common'; + +export const GuideToolUserThanksView: FC<{}> = props => +{ + return ( + + { LocalizeText('guide.help.request.user.thanks.info.title') } + { LocalizeText('guide.help.request.user.thanks.info.desc') } + + ); +}; diff --git a/src/components/hc-center/HcCenterView.scss b/src/components/hc-center/HcCenterView.scss new file mode 100644 index 0000000..a6af9fa --- /dev/null +++ b/src/components/hc-center/HcCenterView.scss @@ -0,0 +1,44 @@ +.nitro-hc-center { + width: 430px; + resize: none; + + .hc-logo { + width: 213px; + height: 37px; + background-image: url('@/assets/images/hc-center/hc_logo.gif'); + } + + .payday-special { + height: 128px; + } + + .payday { + width: 222px; + height: 150px; + background-image: url('@/assets/images/hc-center/payday.png'); + z-index: 3; + color: #6b3502; + } + + .clock { + width: 24px; + height: 24px; + background-image: url('@/assets/images/hc-center/clock.png'); + } + + .streak-info { + min-height: 64px; + line-height: 16px; + } + + .habbo-avatar { + z-index: 4; + } + + .benefits { + background-image: url('@/assets/images/hc-center/benefits.png'); + background-position: right top; + background-repeat: no-repeat; + height: 100%; + } +} diff --git a/src/components/hc-center/HcCenterView.tsx b/src/components/hc-center/HcCenterView.tsx new file mode 100644 index 0000000..08c6172 --- /dev/null +++ b/src/components/hc-center/HcCenterView.tsx @@ -0,0 +1,204 @@ +import { AddLinkEventTracker, ClubGiftInfoEvent, CreateLinkEvent, GetClubGiftInfo, ILinkEventTracker, RemoveLinkEventTracker, ScrGetKickbackInfoMessageComposer, ScrKickbackData, ScrSendKickbackInfoMessageEvent } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { OverlayTrigger, Popover } from 'react-bootstrap'; +import { ClubStatus, FriendlyTime, GetClubBadge, GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../api'; +import { Base, Button, Column, Flex, LayoutAvatarImageView, LayoutBadgeImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common'; +import { useInventoryBadges, useMessageEvent, usePurse, useSessionInfo } from '../../hooks'; + + +export const HcCenterView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ kickbackData, setKickbackData ] = useState(null); + const [ unclaimedGifts, setUnclaimedGifts ] = useState(0); + const [ badgeCode, setBadgeCode ] = useState(null); + const { userFigure = null } = useSessionInfo(); + const { purse = null, clubStatus = null } = usePurse(); + const { badgeCodes = [], activate = null, deactivate = null } = useInventoryBadges(); + + const getClubText = () => + { + if(purse.clubDays <= 0) return LocalizeText('purse.clubdays.zero.amount.text'); + + if((purse.minutesUntilExpiration > -1) && (purse.minutesUntilExpiration < (60 * 24))) + { + return FriendlyTime.shortFormat(purse.minutesUntilExpiration * 60); + } + + return FriendlyTime.shortFormat(((purse.clubPeriods * 31) + purse.clubDays) * 86400); + } + + const getInfoText = () => + { + switch(clubStatus) + { + case ClubStatus.ACTIVE: + return LocalizeText(`hccenter.status.${ clubStatus }.info`, [ 'timeleft', 'joindate', 'streakduration' ], [ getClubText(), kickbackData?.firstSubscriptionDate, FriendlyTime.shortFormat(kickbackData?.currentHcStreak * 86400) ]); + case ClubStatus.EXPIRED: + return LocalizeText(`hccenter.status.${ clubStatus }.info`, [ 'joindate' ], [ kickbackData?.firstSubscriptionDate ]); + default: + return LocalizeText(`hccenter.status.${ clubStatus }.info`); + } + } + + const getHcPaydayTime = () => (!kickbackData || kickbackData.timeUntilPayday < 60) ? LocalizeText('hccenter.special.time.soon') : FriendlyTime.shortFormat(kickbackData.timeUntilPayday * 60); + const getHcPaydayAmount = () => LocalizeText('hccenter.special.sum', [ 'credits' ], [ (kickbackData?.creditRewardForStreakBonus + kickbackData?.creditRewardForMonthlySpent).toString() ]); + + useMessageEvent(ClubGiftInfoEvent, event => + { + const parser = event.getParser(); + + setUnclaimedGifts(parser.giftsAvailable); + }); + + useMessageEvent(ScrSendKickbackInfoMessageEvent, event => + { + const parser = event.getParser(); + + setKickbackData(parser.data); + }); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'open': + if(parts.length > 2) + { + switch(parts[2]) + { + case 'hccenter': + setIsVisible(true); + break; + } + } + return; + } + }, + eventUrlPrefix: 'habboUI/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + useEffect(() => + { + setBadgeCode(GetClubBadge(badgeCodes)); + }, [ badgeCodes ]); + + useEffect(() => + { + if(!isVisible) return; + + const id = activate(); + + return () => deactivate(id); + }, [ isVisible, activate, deactivate ]); + + useEffect(() => + { + SendMessageComposer(new GetClubGiftInfo()); + SendMessageComposer(new ScrGetKickbackInfoMessageComposer()); + }, []); + + if(!isVisible) return null; + + const popover = ( + + +
{ LocalizeText('hccenter.breakdown.title') }
+
{ LocalizeText('hccenter.breakdown.creditsspent', [ 'credits' ], [ kickbackData?.totalCreditsSpent.toString() ]) }
+
{ LocalizeText('hccenter.breakdown.paydayfactor.percent', [ 'percent' ], [ (kickbackData?.kickbackPercentage * 100).toString() ]) }
+
{ LocalizeText('hccenter.breakdown.streakbonus', [ 'credits' ], [ kickbackData?.creditRewardForStreakBonus.toString() ]) }
+
+
{ LocalizeText('hccenter.breakdown.total', [ 'credits', 'actual' ], [ getHcPaydayAmount(), ((((kickbackData?.kickbackPercentage * kickbackData?.totalCreditsSpent) + kickbackData?.creditRewardForStreakBonus) * 100) / 100).toString() ]) }
+
CreateLinkEvent('habbopages/' + GetConfigurationValue('hc.center')['payday.habbopage']) }> + { LocalizeText('hccenter.special.infolink') } +
+
+
+ ); + + return ( + + setIsVisible(false) } /> + + +
+ + + + + + + + + + + + + { LocalizeText('hccenter.status.' + clubStatus) } + + + + { GetConfigurationValue('hc.center')['payday.info'] && + + + +

{ LocalizeText('hccenter.special.title') }

+
{ LocalizeText('hccenter.special.info') }
+
CreateLinkEvent('habbopages/' + GetConfigurationValue('hc.center')['payday.habbopage']) }>{ LocalizeText('hccenter.special.infolink') }
+
+
+
{ LocalizeText('hccenter.special.time.title') }
+
+
+
{ getHcPaydayTime() }
+
+ { clubStatus === ClubStatus.ACTIVE && +
+
{ LocalizeText('hccenter.special.amount.title') }
+
+
{ getHcPaydayAmount() }
+ +
+ { LocalizeText('hccenter.breakdown.infolink') } +
+
+
+
} +
+ } + { GetConfigurationValue('hc.center')['gift.info'] && +
+
+

{ LocalizeText('hccenter.gift.title') }

+
0 ? LocalizeText('hccenter.unclaimedgifts', [ 'unclaimedgifts' ], [ unclaimedGifts.toString() ]) : LocalizeText('hccenter.gift.info') } }>
+
+ +
} + { GetConfigurationValue('hc.center')['benefits.info'] && +
+
{ LocalizeText('hccenter.general.title') }
+
+ +
} + + + ); +} diff --git a/src/components/help/HelpView.scss b/src/components/help/HelpView.scss new file mode 100644 index 0000000..abe4189 --- /dev/null +++ b/src/components/help/HelpView.scss @@ -0,0 +1,18 @@ +.nitro-help { + height: $help-height; + width: $help-width; + + .index-image { + background: url('@/assets/images/help/help_index.png'); + width: 126px; + height: 105px; + } +} + +.nitro-cfh-sanction-status { + width: 400px; +} + +.nitro-change-username { + width: 300px; +} diff --git a/src/components/help/HelpView.tsx b/src/components/help/HelpView.tsx new file mode 100644 index 0000000..f37e42e --- /dev/null +++ b/src/components/help/HelpView.tsx @@ -0,0 +1,116 @@ +import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, ReportState } from '../../api'; +import { Base, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../common'; +import { useHelp } from '../../hooks'; +import { DescribeReportView } from './views/DescribeReportView'; +import { HelpIndexView } from './views/HelpIndexView'; +import { ReportSummaryView } from './views/ReportSummaryView'; +import { SanctionSatusView } from './views/SanctionStatusView'; +import { SelectReportedChatsView } from './views/SelectReportedChatsView'; +import { SelectReportedUserView } from './views/SelectReportedUserView'; +import { SelectTopicView } from './views/SelectTopicView'; +import { NameChangeView } from './views/name-change/NameChangeView'; + +export const HelpView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const { activeReport = null, setActiveReport = null, report = null } = useHelp(); + + const onClose = () => + { + setActiveReport(null); + setIsVisible(false); + } + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'show': + setIsVisible(true); + return; + case 'hide': + setIsVisible(false); + return; + case 'toggle': + setIsVisible(prevValue => !prevValue); + return; + case 'tour': + // todo: launch tour + return; + case 'report': + if((parts.length >= 5) && (parts[2] === 'room')) + { + const roomId = parseInt(parts[3]); + const unknown = unescape(parts.splice(4).join('/')); + //this.reportRoom(roomId, unknown, ""); + } + return; + } + }, + eventUrlPrefix: 'help/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + useEffect(() => + { + if(!activeReport) return; + + setIsVisible(true); + }, [ activeReport ]); + + const CurrentStepView = () => + { + if(activeReport) + { + switch(activeReport.currentStep) + { + case ReportState.SELECT_USER: + return ; + case ReportState.SELECT_CHATS: + return ; + case ReportState.SELECT_TOPICS: + return ; + case ReportState.INPUT_REPORT_MESSAGE: + return ; + case ReportState.REPORT_SUMMARY: + return ; + } + } + + return ; + } + + return ( + <> + { isVisible && + + + + + + + + + + + + + } + + + + ); +} diff --git a/src/components/help/views/DescribeReportView.tsx b/src/components/help/views/DescribeReportView.tsx new file mode 100644 index 0000000..5f231d3 --- /dev/null +++ b/src/components/help/views/DescribeReportView.tsx @@ -0,0 +1,48 @@ +import { FC, useState } from 'react'; +import { LocalizeText, ReportState, ReportType } from '../../../api'; +import { Button, Column, Flex, Text } from '../../../common'; +import { useHelp } from '../../../hooks'; + +export const DescribeReportView: FC<{}> = props => +{ + const [ message, setMessage ] = useState(''); + const { activeReport = null, setActiveReport = null } = useHelp(); + + const submitMessage = () => + { + if(message.length < 15) return; + + setActiveReport(prevValue => + { + const currentStep = ReportState.REPORT_SUMMARY; + + return { ...prevValue, message, currentStep }; + }); + } + + const back = () => + { + setActiveReport(prevValue => + { + return { ...prevValue, currentStep: (prevValue.currentStep - 1) }; + }); + } + + return ( + <> + + { LocalizeText('help.emergency.chat_report.subtitle') } + { LocalizeText('help.cfh.input.text') } + + + + + + + + + ); +} diff --git a/src/components/mod-tools/views/tickets/CfhChatlogView.tsx b/src/components/mod-tools/views/tickets/CfhChatlogView.tsx new file mode 100644 index 0000000..c8bdd7b --- /dev/null +++ b/src/components/mod-tools/views/tickets/CfhChatlogView.tsx @@ -0,0 +1,41 @@ +import { CfhChatlogData, CfhChatlogEvent, GetCfhChatlogMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { SendMessageComposer } from '../../../../api'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; +import { useMessageEvent } from '../../../../hooks'; +import { ChatlogView } from '../chatlog/ChatlogView'; + +interface CfhChatlogViewProps +{ + issueId: number; + onCloseClick(): void; +} + +export const CfhChatlogView: FC = props => +{ + const { onCloseClick = null, issueId = null } = props; + const [ chatlogData, setChatlogData ] = useState(null); + + useMessageEvent(CfhChatlogEvent, event => + { + const parser = event.getParser(); + + if(!parser || parser.data.issueId !== issueId) return; + + setChatlogData(parser.data); + }); + + useEffect(() => + { + SendMessageComposer(new GetCfhChatlogMessageComposer(issueId)); + }, [ issueId ]); + + return ( + + + + { chatlogData && } + + + ); +} diff --git a/src/components/mod-tools/views/tickets/ModToolsIssueInfoView.tsx b/src/components/mod-tools/views/tickets/ModToolsIssueInfoView.tsx new file mode 100644 index 0000000..1179d41 --- /dev/null +++ b/src/components/mod-tools/views/tickets/ModToolsIssueInfoView.tsx @@ -0,0 +1,86 @@ +import { CloseIssuesMessageComposer, ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { GetIssueCategoryName, LocalizeText, SendMessageComposer } from '../../../../api'; +import { Button, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; +import { useModTools } from '../../../../hooks'; +import { CfhChatlogView } from './CfhChatlogView'; + +interface IssueInfoViewProps +{ + issueId: number; + onIssueInfoClosed(issueId: number): void; +} + +export const ModToolsIssueInfoView: FC = props => +{ + const { issueId = null, onIssueInfoClosed = null } = props; + const [ cfhChatlogOpen, setcfhChatlogOpen ] = useState(false); + const { tickets = [], openUserInfo = null } = useModTools(); + const ticket = tickets.find(issue => (issue.issueId === issueId)); + + const releaseIssue = (issueId: number) => + { + SendMessageComposer(new ReleaseIssuesMessageComposer([ issueId ])); + + onIssueInfoClosed(issueId); + } + + const closeIssue = (resolutionType: number) => + { + SendMessageComposer(new CloseIssuesMessageComposer([ issueId ], resolutionType)); + + onIssueInfoClosed(issueId) + } + + return ( + <> + + onIssueInfoClosed(issueId) } /> + + Issue Information + + + + + + + + + + + + + + + + + + + + + + + + + +
Source{ GetIssueCategoryName(ticket.categoryId) }
Category{ LocalizeText('help.cfh.topic.' + ticket.reportedCategoryId) }
Description{ ticket.message }
Caller + openUserInfo(ticket.reporterUserId) }>{ ticket.reporterUserName } +
Reported User + openUserInfo(ticket.reportedUserId) }>{ ticket.reportedUserName } +
+
+ + + + + + + +
+
+
+ { cfhChatlogOpen && + setcfhChatlogOpen(false) }/> } + + ); +} diff --git a/src/components/mod-tools/views/tickets/ModToolsMyIssuesTabView.tsx b/src/components/mod-tools/views/tickets/ModToolsMyIssuesTabView.tsx new file mode 100644 index 0000000..2e1827a --- /dev/null +++ b/src/components/mod-tools/views/tickets/ModToolsMyIssuesTabView.tsx @@ -0,0 +1,47 @@ +import { IssueMessageData, ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { SendMessageComposer } from '../../../../api'; +import { Base, Button, Column, Grid } from '../../../../common'; + +interface ModToolsMyIssuesTabViewProps +{ + myIssues: IssueMessageData[]; + handleIssue: (issueId: number) => void; +} + +export const ModToolsMyIssuesTabView: FC = props => +{ + const { myIssues = null, handleIssue = null } = props; + + return ( + + + + Type + Room/Player + Opened + + + + + + { myIssues && (myIssues.length > 0) && myIssues.map(issue => + { + return ( + + { issue.categoryId } + { issue.reportedUserName } + { new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString() } + + + + + + + + ); + }) } + + + ); +} diff --git a/src/components/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx b/src/components/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx new file mode 100644 index 0000000..6ee23cd --- /dev/null +++ b/src/components/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx @@ -0,0 +1,42 @@ +import { IssueMessageData, PickIssuesMessageComposer } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { SendMessageComposer } from '../../../../api'; +import { Base, Button, Column, Grid } from '../../../../common'; + +interface ModToolsOpenIssuesTabViewProps +{ + openIssues: IssueMessageData[]; +} + +export const ModToolsOpenIssuesTabView: FC = props => +{ + const { openIssues = null } = props; + + return ( + + + + Type + Room/Player + Opened + + + + + { openIssues && (openIssues.length > 0) && openIssues.map(issue => + { + return ( + + { issue.categoryId } + { issue.reportedUserName } + { new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString() } + + + + + ); + }) } + + + ); +} diff --git a/src/components/mod-tools/views/tickets/ModToolsPickedIssuesTabView.tsx b/src/components/mod-tools/views/tickets/ModToolsPickedIssuesTabView.tsx new file mode 100644 index 0000000..19b899b --- /dev/null +++ b/src/components/mod-tools/views/tickets/ModToolsPickedIssuesTabView.tsx @@ -0,0 +1,39 @@ +import { IssueMessageData } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { Base, Column, Grid } from '../../../../common'; + +interface ModToolsPickedIssuesTabViewProps +{ + pickedIssues: IssueMessageData[]; +} + +export const ModToolsPickedIssuesTabView: FC = props => +{ + const { pickedIssues = null } = props; + + return ( + + + + Type + Room/Player + Opened + Picker + + + + { pickedIssues && (pickedIssues.length > 0) && pickedIssues.map(issue => + { + return ( + + { issue.categoryId } + { issue.reportedUserName } + { new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString() } + { issue.pickerUserName } + + ); + }) } + + + ); +} diff --git a/src/components/mod-tools/views/tickets/ModToolsTicketsView.tsx b/src/components/mod-tools/views/tickets/ModToolsTicketsView.tsx new file mode 100644 index 0000000..76d6abd --- /dev/null +++ b/src/components/mod-tools/views/tickets/ModToolsTicketsView.tsx @@ -0,0 +1,90 @@ +import { GetSessionDataManager, IssueMessageData } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../common'; +import { useModTools } from '../../../../hooks'; +import { ModToolsIssueInfoView } from './ModToolsIssueInfoView'; +import { ModToolsMyIssuesTabView } from './ModToolsMyIssuesTabView'; +import { ModToolsOpenIssuesTabView } from './ModToolsOpenIssuesTabView'; +import { ModToolsPickedIssuesTabView } from './ModToolsPickedIssuesTabView'; + +interface ModToolsTicketsViewProps +{ + onCloseClick: () => void; +} + +const TABS: string[] = [ + 'Open Issues', + 'My Issues', + 'Picked Issues' +]; + +export const ModToolsTicketsView: FC = props => +{ + const { onCloseClick = null } = props; + const [ currentTab, setCurrentTab ] = useState(0); + const [ issueInfoWindows, setIssueInfoWindows ] = useState([]); + const { tickets = [] } = useModTools(); + + const openIssues = tickets.filter(issue => issue.state === IssueMessageData.STATE_OPEN); + const myIssues = tickets.filter(issue => (issue.state === IssueMessageData.STATE_PICKED) && (issue.pickerUserId === GetSessionDataManager().userId)); + const pickedIssues = tickets.filter(issue => issue.state === IssueMessageData.STATE_PICKED); + + const closeIssue = (issueId: number) => + { + setIssueInfoWindows(prevValue => + { + const newValue = [ ...prevValue ]; + const existingIndex = newValue.indexOf(issueId); + + if(existingIndex >= 0) newValue.splice(existingIndex, 1); + + return newValue; + }); + } + + const handleIssue = (issueId: number) => + { + setIssueInfoWindows(prevValue => + { + const newValue = [ ...prevValue ]; + const existingIndex = newValue.indexOf(issueId); + + if(existingIndex === -1) newValue.push(issueId); + else newValue.splice(existingIndex, 1); + + return newValue; + }) + } + + const CurrentTabComponent = () => + { + switch(currentTab) + { + case 0: return ; + case 1: return ; + case 2: return ; + } + + return null; + } + + return ( + <> + + + + { TABS.map((tab, index) => + { + return ( setCurrentTab(index) }> + { tab } + ); + }) } + + + + + + { issueInfoWindows && (issueInfoWindows.length > 0) && issueInfoWindows.map(issueId => ) } + + ); +} diff --git a/src/components/mod-tools/views/user/ModToolsUserChatlogView.tsx b/src/components/mod-tools/views/user/ModToolsUserChatlogView.tsx new file mode 100644 index 0000000..7bfb2e1 --- /dev/null +++ b/src/components/mod-tools/views/user/ModToolsUserChatlogView.tsx @@ -0,0 +1,44 @@ +import { ChatRecordData, GetUserChatlogMessageComposer, UserChatlogEvent } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { SendMessageComposer } from '../../../../api'; +import { DraggableWindowPosition, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; +import { useMessageEvent } from '../../../../hooks'; +import { ChatlogView } from '../chatlog/ChatlogView'; + +interface ModToolsUserChatlogViewProps +{ + userId: number; + onCloseClick: () => void; +} + +export const ModToolsUserChatlogView: FC = props => +{ + const { userId = null, onCloseClick = null } = props; + const [ userChatlog, setUserChatlog ] = useState(null); + const [ username, setUsername ] = useState(null); + + useMessageEvent(UserChatlogEvent, event => + { + const parser = event.getParser(); + + if(!parser || parser.data.userId !== userId) return; + + setUsername(parser.data.username); + setUserChatlog(parser.data.roomChatlogs); + }); + + useEffect(() => + { + SendMessageComposer(new GetUserChatlogMessageComposer(userId)); + }, [ userId ]); + + return ( + + + + { userChatlog && + } + + + ); +} diff --git a/src/components/mod-tools/views/user/ModToolsUserModActionView.tsx b/src/components/mod-tools/views/user/ModToolsUserModActionView.tsx new file mode 100644 index 0000000..bd4c2d5 --- /dev/null +++ b/src/components/mod-tools/views/user/ModToolsUserModActionView.tsx @@ -0,0 +1,176 @@ +import { CallForHelpTopicData, DefaultSanctionMessageComposer, ModAlertMessageComposer, ModBanMessageComposer, ModKickMessageComposer, ModMessageMessageComposer, ModMuteMessageComposer, ModTradingLockMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useMemo, useState } from 'react'; +import { ISelectedUser, LocalizeText, ModActionDefinition, NotificationAlertType, SendMessageComposer } from '../../../../api'; +import { Button, Column, DraggableWindowPosition, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; +import { useModTools, useNotification } from '../../../../hooks'; + +interface ModToolsUserModActionViewProps +{ + user: ISelectedUser; + onCloseClick: () => void; +} + +const MOD_ACTION_DEFINITIONS = [ + new ModActionDefinition(1, 'Alert', ModActionDefinition.ALERT, 1, 0), + new ModActionDefinition(2, 'Mute 1h', ModActionDefinition.MUTE, 2, 0), + new ModActionDefinition(3, 'Ban 18h', ModActionDefinition.BAN, 3, 0), + new ModActionDefinition(4, 'Ban 7 days', ModActionDefinition.BAN, 4, 0), + new ModActionDefinition(5, 'Ban 30 days (step 1)', ModActionDefinition.BAN, 5, 0), + new ModActionDefinition(7, 'Ban 30 days (step 2)', ModActionDefinition.BAN, 7, 0), + new ModActionDefinition(6, 'Ban 100 years', ModActionDefinition.BAN, 6, 0), + new ModActionDefinition(106, 'Ban avatar-only 100 years', ModActionDefinition.BAN, 6, 0), + new ModActionDefinition(101, 'Kick', ModActionDefinition.KICK, 0, 0), + new ModActionDefinition(102, 'Lock trade 1 week', ModActionDefinition.TRADE_LOCK, 0, 168), + new ModActionDefinition(104, 'Lock trade permanent', ModActionDefinition.TRADE_LOCK, 0, 876000), + new ModActionDefinition(105, 'Message', ModActionDefinition.MESSAGE, 0, 0), +]; + +export const ModToolsUserModActionView: FC = props => +{ + const { user = null, onCloseClick = null } = props; + const [ selectedTopic, setSelectedTopic ] = useState(-1); + const [ selectedAction, setSelectedAction ] = useState(-1); + const [ message, setMessage ] = useState(''); + const { cfhCategories = null, settings = null } = useModTools(); + const { simpleAlert = null } = useNotification(); + + const topics = useMemo(() => + { + const values: CallForHelpTopicData[] = []; + + if(cfhCategories && cfhCategories.length) + { + for(const category of cfhCategories) + { + for(const topic of category.topics) values.push(topic); + } + } + + return values; + }, [ cfhCategories ]); + + const sendAlert = (message: string) => simpleAlert(message, NotificationAlertType.DEFAULT, null, null, 'Error'); + + const sendDefaultSanction = () => + { + let errorMessage: string = null; + + const category = topics[selectedTopic]; + + if(selectedTopic === -1) errorMessage = 'You must select a CFH topic'; + + if(errorMessage) return sendAlert(errorMessage); + + const messageOrDefault = (message.trim().length === 0) ? LocalizeText(`help.cfh.topic.${ category.id }`) : message; + + SendMessageComposer(new DefaultSanctionMessageComposer(user.userId, selectedTopic, messageOrDefault)); + + onCloseClick(); + } + + const sendSanction = () => + { + let errorMessage: string = null; + + const category = topics[selectedTopic]; + const sanction = MOD_ACTION_DEFINITIONS[selectedAction]; + + if((selectedTopic === -1) || (selectedAction === -1)) errorMessage = 'You must select a CFH topic and Sanction'; + else if(!settings || !settings.cfhPermission) errorMessage = 'You do not have permission to do this'; + else if(!category) errorMessage = 'You must select a CFH topic'; + else if(!sanction) errorMessage = 'You must select a sanction'; + + if(errorMessage) + { + sendAlert(errorMessage); + + return; + } + + const messageOrDefault = (message.trim().length === 0) ? LocalizeText(`help.cfh.topic.${ category.id }`) : message; + + switch(sanction.actionType) + { + case ModActionDefinition.ALERT: { + if(!settings.alertPermission) + { + sendAlert('You have insufficient permissions'); + + return; + } + + SendMessageComposer(new ModAlertMessageComposer(user.userId, messageOrDefault, category.id)); + break; + } + case ModActionDefinition.MUTE: + SendMessageComposer(new ModMuteMessageComposer(user.userId, messageOrDefault, category.id)); + break; + case ModActionDefinition.BAN: { + if(!settings.banPermission) + { + sendAlert('You have insufficient permissions'); + + return; + } + + SendMessageComposer(new ModBanMessageComposer(user.userId, messageOrDefault, category.id, selectedAction, (sanction.actionId === 106))); + break; + } + case ModActionDefinition.KICK: { + if(!settings.kickPermission) + { + sendAlert('You have insufficient permissions'); + return; + } + + SendMessageComposer(new ModKickMessageComposer(user.userId, messageOrDefault, category.id)); + break; + } + case ModActionDefinition.TRADE_LOCK: { + const numSeconds = (sanction.actionLengthHours * 60); + + SendMessageComposer(new ModTradingLockMessageComposer(user.userId, messageOrDefault, numSeconds, category.id)); + break; + } + case ModActionDefinition.MESSAGE: { + if(message.trim().length === 0) + { + sendAlert('Please write a message to user'); + + return; + } + + SendMessageComposer(new ModMessageMessageComposer(user.userId, message, category.id)); + break; + } + } + + onCloseClick(); + } + + if(!user) return null; + + return ( + + onCloseClick() } /> + + + + + Optional message type, overrides default + + + + + ); +} diff --git a/src/components/mod-tools/views/user/ModToolsUserView.tsx b/src/components/mod-tools/views/user/ModToolsUserView.tsx new file mode 100644 index 0000000..0ceb8a2 --- /dev/null +++ b/src/components/mod-tools/views/user/ModToolsUserView.tsx @@ -0,0 +1,156 @@ +import { CreateLinkEvent, GetModeratorUserInfoMessageComposer, ModeratorUserInfoData, ModeratorUserInfoEvent } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useMemo, useState } from 'react'; +import { FriendlyTime, LocalizeText, SendMessageComposer } from '../../../../api'; +import { Button, Column, DraggableWindowPosition, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; +import { useMessageEvent } from '../../../../hooks'; +import { ModToolsUserModActionView } from './ModToolsUserModActionView'; +import { ModToolsUserRoomVisitsView } from './ModToolsUserRoomVisitsView'; +import { ModToolsUserSendMessageView } from './ModToolsUserSendMessageView'; + +interface ModToolsUserViewProps +{ + userId: number; + onCloseClick: () => void; +} + +export const ModToolsUserView: FC = props => +{ + const { onCloseClick = null, userId = null } = props; + const [ userInfo, setUserInfo ] = useState(null); + const [ sendMessageVisible, setSendMessageVisible ] = useState(false); + const [ modActionVisible, setModActionVisible ] = useState(false); + const [ roomVisitsVisible, setRoomVisitsVisible ] = useState(false); + + const userProperties = useMemo(() => + { + if(!userInfo) return null; + + return [ + { + localeKey: 'modtools.userinfo.userName', + value: userInfo.userName, + showOnline: true + }, + { + localeKey: 'modtools.userinfo.cfhCount', + value: userInfo.cfhCount.toString() + }, + { + localeKey: 'modtools.userinfo.abusiveCfhCount', + value: userInfo.abusiveCfhCount.toString() + }, + { + localeKey: 'modtools.userinfo.cautionCount', + value: userInfo.cautionCount.toString() + }, + { + localeKey: 'modtools.userinfo.banCount', + value: userInfo.banCount.toString() + }, + { + localeKey: 'modtools.userinfo.lastSanctionTime', + value: userInfo.lastSanctionTime + }, + { + localeKey: 'modtools.userinfo.tradingLockCount', + value: userInfo.tradingLockCount.toString() + }, + { + localeKey: 'modtools.userinfo.tradingExpiryDate', + value: userInfo.tradingExpiryDate + }, + { + localeKey: 'modtools.userinfo.minutesSinceLastLogin', + value: FriendlyTime.format(userInfo.minutesSinceLastLogin * 60, '.ago', 2) + }, + { + localeKey: 'modtools.userinfo.lastPurchaseDate', + value: userInfo.lastPurchaseDate + }, + { + localeKey: 'modtools.userinfo.primaryEmailAddress', + value: userInfo.primaryEmailAddress + }, + { + localeKey: 'modtools.userinfo.identityRelatedBanCount', + value: userInfo.identityRelatedBanCount.toString() + }, + { + localeKey: 'modtools.userinfo.registrationAgeInMinutes', + value: FriendlyTime.format(userInfo.registrationAgeInMinutes * 60, '.ago', 2) + }, + { + localeKey: 'modtools.userinfo.userClassification', + value: userInfo.userClassification + } + ]; + }, [ userInfo ]); + + useMessageEvent(ModeratorUserInfoEvent, event => + { + const parser = event.getParser(); + + if(!parser || parser.data.userId !== userId) return; + + setUserInfo(parser.data); + }); + + useEffect(() => + { + SendMessageComposer(new GetModeratorUserInfoMessageComposer(userId)); + }, [ userId ]); + + if(!userInfo) return null; + + return ( + <> + + onCloseClick() } /> + + + + + + { userProperties.map( (property, index) => + { + + return ( + + + + + ); + }) } + +
{ LocalizeText(property.localeKey) } + { property.value } + { property.showOnline && + } +
+
+ + + + + + +
+
+
+ { sendMessageVisible && + setSendMessageVisible(false) } /> } + { modActionVisible && + setModActionVisible(false) } /> } + { roomVisitsVisible && + setRoomVisitsVisible(false) } /> } + + ); +} diff --git a/src/components/navigator/NavigatorView.scss b/src/components/navigator/NavigatorView.scss new file mode 100644 index 0000000..ef235bc --- /dev/null +++ b/src/components/navigator/NavigatorView.scss @@ -0,0 +1,65 @@ +.nitro-navigator { + width: $navigator-width; + height: $navigator-height; + + .navigator-grid { + + .navigator-item { + + .badge { + width: 35px; + min-width: 35px; + } + } + + &:not(.two-columns) { + + .navigator-item { + + &:nth-child(odd) { + background-color: $grid-active-bg-color; + } + } + } + + &.two-columns { + + .navigator-item { + + &:nth-child(4n-2), + &:nth-child(4n-3) { + background: $grid-active-bg-color; + } + } + } + } +} + +.nitro-navigator-doorbell, +.nitro-navigator-password { + width: 250px; +} + +.nitro-room-info { + width: $room-info-width; +} + +.nitro-room-link { + width: 400px; +} + +.nitro-room-settings { + width: 400px; + + .list-container { + height: 100px; + + .list-item { + background-color: $grid-active-bg-color; + } + } +} + +.room-info { + width: 275px; +} diff --git a/src/components/navigator/NavigatorView.tsx b/src/components/navigator/NavigatorView.tsx new file mode 100644 index 0000000..e772db8 --- /dev/null +++ b/src/components/navigator/NavigatorView.tsx @@ -0,0 +1,233 @@ +import { AddLinkEventTracker, ConvertGlobalRoomIdMessageComposer, HabboWebTools, ILinkEventTracker, LegacyExternalInterface, NavigatorInitComposer, NavigatorSearchComposer, RemoveLinkEventTracker, RoomSessionEvent } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useRef, useState } from 'react'; +import { FaPlus } from 'react-icons/fa'; +import { LocalizeText, SendMessageComposer, TryVisitRoom } from '../../api'; +import { Base, Column, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common'; +import { useNavigator, useNitroEvent } from '../../hooks'; +import { NavigatorDoorStateView } from './views/NavigatorDoorStateView'; +import { NavigatorRoomCreatorView } from './views/NavigatorRoomCreatorView'; +import { NavigatorRoomInfoView } from './views/NavigatorRoomInfoView'; +import { NavigatorRoomLinkView } from './views/NavigatorRoomLinkView'; +import { NavigatorRoomSettingsView } from './views/room-settings/NavigatorRoomSettingsView'; +import { NavigatorSearchResultView } from './views/search/NavigatorSearchResultView'; +import { NavigatorSearchView } from './views/search/NavigatorSearchView'; + +export const NavigatorView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ isReady, setIsReady ] = useState(false); + const [ isCreatorOpen, setCreatorOpen ] = useState(false); + const [ isRoomInfoOpen, setRoomInfoOpen ] = useState(false); + const [ isRoomLinkOpen, setRoomLinkOpen ] = useState(false); + const [ isLoading, setIsLoading ] = useState(false); + const [ needsInit, setNeedsInit ] = useState(true); + const [ needsSearch, setNeedsSearch ] = useState(false); + const { searchResult = null, topLevelContext = null, topLevelContexts = null, navigatorData = null } = useNavigator(); + const pendingSearch = useRef<{ value: string, code: string }>(null); + const elementRef = useRef(); + + useNitroEvent(RoomSessionEvent.CREATED, event => + { + setIsVisible(false); + setCreatorOpen(false); + }); + + const sendSearch = useCallback((searchValue: string, contextCode: string) => + { + setCreatorOpen(false); + + SendMessageComposer(new NavigatorSearchComposer(contextCode, searchValue)); + + setIsLoading(true); + }, []); + + const reloadCurrentSearch = useCallback(() => + { + if(!isReady) + { + setNeedsSearch(true); + + return; + } + + if(pendingSearch.current) + { + sendSearch(pendingSearch.current.value, pendingSearch.current.code); + + pendingSearch.current = null; + + return; + } + + if(searchResult) + { + sendSearch(searchResult.data, searchResult.code); + + return; + } + + if(!topLevelContext) return; + + sendSearch('', topLevelContext.code); + }, [ isReady, searchResult, topLevelContext, sendSearch ]); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'show': { + setIsVisible(true); + setNeedsSearch(true); + return; + } + case 'hide': + setIsVisible(false); + return; + case 'toggle': { + if(isVisible) + { + setIsVisible(false); + + return; + } + + setIsVisible(true); + setNeedsSearch(true); + return; + } + case 'toggle-room-info': + setRoomInfoOpen(value => !value); + return; + case 'toggle-room-link': + setRoomLinkOpen(value => !value); + return; + case 'goto': + if(parts.length <= 2) return; + + switch(parts[2]) + { + case 'home': + if(navigatorData.homeRoomId <= 0) return; + + TryVisitRoom(navigatorData.homeRoomId); + break; + default: { + const roomId = parseInt(parts[2]); + + TryVisitRoom(roomId); + } + } + return; + case 'create': + setIsVisible(true); + setCreatorOpen(true); + return; + case 'search': + if(parts.length > 2) + { + const topLevelContextCode = parts[2]; + + let searchValue = ''; + + if(parts.length > 3) searchValue = parts[3]; + + pendingSearch.current = { value: searchValue, code: topLevelContextCode }; + + setIsVisible(true); + setNeedsSearch(true); + } + return; + } + }, + eventUrlPrefix: 'navigator/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, [ isVisible, navigatorData ]); + + useEffect(() => + { + if(!searchResult) return; + + setIsLoading(false); + + if(elementRef && elementRef.current) elementRef.current.scrollTop = 0; + }, [ searchResult ]); + + useEffect(() => + { + if(!isVisible || !isReady || !needsSearch) return; + + reloadCurrentSearch(); + + setNeedsSearch(false); + }, [ isVisible, isReady, needsSearch, reloadCurrentSearch ]); + + useEffect(() => + { + if(isReady || !topLevelContext) return; + + setIsReady(true); + }, [ isReady, topLevelContext ]); + + useEffect(() => + { + if(!isVisible || !needsInit) return; + + SendMessageComposer(new NavigatorInitComposer()); + + setNeedsInit(false); + }, [ isVisible, needsInit ]); + + useEffect(() => + { + LegacyExternalInterface.addCallback(HabboWebTools.OPENROOM, (k: string, _arg_2: boolean = false, _arg_3: string = null) => SendMessageComposer(new ConvertGlobalRoomIdMessageComposer(k))); + }, []); + + return ( + <> + { isVisible && + + setIsVisible(false) } /> + + { topLevelContexts && (topLevelContexts.length > 0) && topLevelContexts.map((context, index) => + { + return ( + sendSearch('', context.code) }> + { LocalizeText(('navigator.toplevelview.' + context.code)) } + + ); + }) } + setCreatorOpen(true) }> + + + + + { isLoading && + } + { !isCreatorOpen && + <> + + + { (searchResult && searchResult.results.map((result, index) => )) } + + } + { isCreatorOpen && } + + } + + { isRoomInfoOpen && setRoomInfoOpen(false) } /> } + { isRoomLinkOpen && setRoomLinkOpen(false) } /> } + + + ); +} diff --git a/src/components/navigator/views/NavigatorDoorStateView.tsx b/src/components/navigator/views/NavigatorDoorStateView.tsx new file mode 100644 index 0000000..4c8a3e2 --- /dev/null +++ b/src/components/navigator/views/NavigatorDoorStateView.tsx @@ -0,0 +1,110 @@ +import { FC, useEffect, useState } from 'react'; +import { CreateRoomSession, DoorStateType, GoToDesktop, LocalizeText } from '../../../api'; +import { Button, Column, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; +import { useNavigator } from '../../../hooks'; + +const VISIBLE_STATES = [ DoorStateType.START_DOORBELL, DoorStateType.STATE_WAITING, DoorStateType.STATE_NO_ANSWER, DoorStateType.START_PASSWORD, DoorStateType.STATE_WRONG_PASSWORD ]; +const DOORBELL_STATES = [ DoorStateType.START_DOORBELL, DoorStateType.STATE_WAITING, DoorStateType.STATE_NO_ANSWER ]; +const PASSWORD_STATES = [ DoorStateType.START_PASSWORD, DoorStateType.STATE_WRONG_PASSWORD ]; + +export const NavigatorDoorStateView: FC<{}> = props => +{ + const [ password, setPassword ] = useState(''); + const { doorData = null, setDoorData = null } = useNavigator(); + + const onClose = () => + { + if(doorData && (doorData.state === DoorStateType.STATE_WAITING)) GoToDesktop(); + + setDoorData(null); + } + + const ring = () => + { + if(!doorData || !doorData.roomInfo) return; + + CreateRoomSession(doorData.roomInfo.roomId); + + setDoorData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.state = DoorStateType.STATE_PENDING_SERVER; + + return newValue; + }); + } + + const tryEntering = () => + { + if(!doorData || !doorData.roomInfo) return; + + CreateRoomSession(doorData.roomInfo.roomId, password); + + setDoorData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.state = DoorStateType.STATE_PENDING_SERVER; + + return newValue; + }); + } + + useEffect(() => + { + if(!doorData || (doorData.state !== DoorStateType.STATE_NO_ANSWER)) return; + + GoToDesktop(); + }, [ doorData ]); + + if(!doorData || (doorData.state === DoorStateType.NONE) || (VISIBLE_STATES.indexOf(doorData.state) === -1)) return null; + + const isDoorbell = (DOORBELL_STATES.indexOf(doorData.state) >= 0); + + return ( + + + + + { doorData && doorData.roomInfo && doorData.roomInfo.roomName } + { (doorData.state === DoorStateType.START_DOORBELL) && + { LocalizeText('navigator.doorbell.info') } } + { (doorData.state === DoorStateType.STATE_WAITING) && + { LocalizeText('navigator.doorbell.waiting') } } + { (doorData.state === DoorStateType.STATE_NO_ANSWER) && + { LocalizeText('navigator.doorbell.no.answer') } } + { (doorData.state === DoorStateType.START_PASSWORD) && + { LocalizeText('navigator.password.info') } } + { (doorData.state === DoorStateType.STATE_WRONG_PASSWORD) && + { LocalizeText('navigator.password.retryinfo') } } + + { isDoorbell && + + { (doorData.state === DoorStateType.START_DOORBELL) && + } + + } + { !isDoorbell && + <> + + { LocalizeText('navigator.password.enter') } + setPassword(event.target.value) } /> + + + + + + } + + + ); +} diff --git a/src/components/navigator/views/NavigatorRoomCreatorView.tsx b/src/components/navigator/views/NavigatorRoomCreatorView.tsx new file mode 100644 index 0000000..95a5bd6 --- /dev/null +++ b/src/components/navigator/views/NavigatorRoomCreatorView.tsx @@ -0,0 +1,122 @@ +/* eslint-disable no-template-curly-in-string */ +import { CreateFlatMessageComposer, HabboClubLevelEnum } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { GetClubMemberLevel, GetConfigurationValue, IRoomModel, LocalizeText, SendMessageComposer } from '../../../api'; +import { Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, Text } from '../../../common'; +import { useNavigator } from '../../../hooks'; + +export const NavigatorRoomCreatorView: FC<{}> = props => +{ + const [ maxVisitorsList, setMaxVisitorsList ] = useState(null); + const [ name, setName ] = useState(null); + const [ description, setDescription ] = useState(null); + const [ category, setCategory ] = useState(null); + const [ visitorsCount, setVisitorsCount ] = useState(null); + const [ tradesSetting, setTradesSetting ] = useState(0); + const [ roomModels, setRoomModels ] = useState([]); + const [ selectedModelName, setSelectedModelName ] = useState(''); + const { categories = null } = useNavigator(); + + const hcDisabled = GetConfigurationValue('hc.disabled', false); + + const getRoomModelImage = (name: string) => GetConfigurationValue('images.url') + `/navigator/models/model_${ name }.png`; + + const selectModel = (model: IRoomModel, index: number) => + { + if(!model || (model.clubLevel > GetClubMemberLevel())) return; + + setSelectedModelName(roomModels[index].name); + }; + + const createRoom = () => + { + SendMessageComposer(new CreateFlatMessageComposer(name, description, 'model_' + selectedModelName, Number(category), Number(visitorsCount), tradesSetting)); + }; + + useEffect(() => + { + if(!maxVisitorsList) + { + const list = []; + + for(let i = 10; i <= 100; i = i + 10) list.push(i); + + setMaxVisitorsList(list); + setVisitorsCount(list[0]); + } + }, [ maxVisitorsList ]); + + useEffect(() => + { + if(categories && categories.length) setCategory(categories[0].id); + }, [ categories ]); + + useEffect(() => + { + const models = GetConfigurationValue('navigator.room.models'); + + if(models && models.length) + { + setRoomModels(models); + setSelectedModelName(models[0].name); + } + }, []); + + return ( + + + + + { LocalizeText('navigator.createroom.roomnameinfo') } + setName(event.target.value) } placeholder={ LocalizeText('navigator.createroom.roomnameinfo') } /> + + + { LocalizeText('navigator.createroom.roomdescinfo') } + +
+
+ + ); +} diff --git a/src/components/room/widgets/furniture/FurnitureStackHeightView.tsx b/src/components/room/widgets/furniture/FurnitureStackHeightView.tsx new file mode 100644 index 0000000..d4d1039 --- /dev/null +++ b/src/components/room/widgets/furniture/FurnitureStackHeightView.tsx @@ -0,0 +1,58 @@ +import { FurnitureStackHeightComposer } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { LocalizeText, SendMessageComposer } from '../../../../api'; +import { Button, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; +import { useFurnitureStackHeightWidget } from '../../../../hooks'; + +export const FurnitureStackHeightView: FC<{}> = props => +{ + const { objectId = -1, height = 0, maxHeight = 40, onClose = null, updateHeight = null } = useFurnitureStackHeightWidget(); + const [ tempHeight, setTempHeight ] = useState(''); + + const updateTempHeight = (value: string) => + { + setTempHeight(value); + + const newValue = parseFloat(value); + + if(isNaN(newValue) || (newValue === height)) return; + + updateHeight(newValue); + } + + useEffect(() => + { + setTempHeight(height.toString()); + }, [ height ]); + + if(objectId === -1) return null; + + return ( + + + + { LocalizeText('widget.custom.stack.height.text') } + + updateHeight(event) } + renderThumb={ (props, state) =>
{ state.valueNow }
} /> + updateTempHeight(event.target.value) } /> +
+ + + + +
+
+ ); +} diff --git a/src/components/room/widgets/furniture/FurnitureStickieView.tsx b/src/components/room/widgets/furniture/FurnitureStickieView.tsx new file mode 100644 index 0000000..2f0e0fe --- /dev/null +++ b/src/components/room/widgets/furniture/FurnitureStickieView.tsx @@ -0,0 +1,66 @@ +import { FC, useEffect, useState } from 'react'; +import { ColorUtils } from '../../../../api'; +import { DraggableWindow, DraggableWindowPosition } from '../../../../common'; +import { useFurnitureStickieWidget } from '../../../../hooks'; + +const STICKIE_COLORS = [ '9CCEFF','FF9CFF', '9CFF9C','FFFF33' ]; +const STICKIE_COLOR_NAMES = [ 'blue', 'pink', 'green', 'yellow' ]; +const STICKIE_TYPES = [ 'post_it','post_it_shakesp', 'post_it_dreams','post_it_xmas', 'post_it_vd', 'post_it_juninas' ]; +const STICKIE_TYPE_NAMES = [ 'post_it', 'shakesp', 'dreams', 'christmas', 'heart', 'juninas' ]; + +const getStickieColorName = (color: string) => +{ + let index = STICKIE_COLORS.indexOf(color); + + if(index === -1) index = 0; + + return STICKIE_COLOR_NAMES[index]; +} + +const getStickieTypeName = (type: string) => +{ + let index = STICKIE_TYPES.indexOf(type); + + if(index === -1) index = 0; + + return STICKIE_TYPE_NAMES[index]; +} + +export const FurnitureStickieView: FC<{}> = props => +{ + const { objectId = -1, color = '0', text = '', type = '', canModify = false, updateColor = null, updateText = null, trash = null, onClose = null } = useFurnitureStickieWidget(); + const [ isEditing, setIsEditing ] = useState(false); + + useEffect(() => + { + setIsEditing(false); + }, [ objectId, color, text, type ]); + + if(objectId === -1) return null; + + return ( + +
+
+
+ { canModify && + <> +
+ { type == 'post_it' && + <> + { STICKIE_COLORS.map(color => + { + return
updateColor(color) } style={ { backgroundColor: ColorUtils.makeColorHex(color) } } /> + }) } + } + } +
+
+
+
+ { (!isEditing || !canModify) ?
(canModify && setIsEditing(true)) }>{ text }
: } +
+
+ + ); +} diff --git a/src/components/room/widgets/furniture/FurnitureTrophyView.tsx b/src/components/room/widgets/furniture/FurnitureTrophyView.tsx new file mode 100644 index 0000000..7977097 --- /dev/null +++ b/src/components/room/widgets/furniture/FurnitureTrophyView.tsx @@ -0,0 +1,12 @@ +import { FC } from 'react'; +import { LayoutTrophyView } from '../../../../common'; +import { useFurnitureTrophyWidget } from '../../../../hooks'; + +export const FurnitureTrophyView: FC<{}> = props => +{ + const { objectId = -1, color = '1', senderName = '', date = '', message = '', onClose = null } = useFurnitureTrophyWidget(); + + if(objectId === -1) return null; + + return ; +} diff --git a/src/components/room/widgets/furniture/FurnitureWidgets.scss b/src/components/room/widgets/furniture/FurnitureWidgets.scss new file mode 100644 index 0000000..65b6559 --- /dev/null +++ b/src/components/room/widgets/furniture/FurnitureWidgets.scss @@ -0,0 +1,526 @@ +.nitro-room-widgets { + pointer-events: none; +} + +.nitro-widget-custom-stack-height { + width: $nitro-widget-custom-stack-height-width; + height: $nitro-widget-custom-stack-height-height; +} + +.nitro-room-widget-toner { + width: 190px; +} + +.nitro-room-widget-dimmer { + width: 275px; + + .dimmer-banner { + width: 56px; + height: 79px; + background: url('@/assets/images/room-widgets/dimmer-widget/dimmer_banner.png') + center no-repeat; + } + + .color-swatch { + height: 30px; + border: 2px solid $white; + box-shadow: inset 3px 3px rgba(0, 0, 0, 0.2); + + &.active { + box-shadow: none; + } + } +} + +.nitro-widget-crafting { + width: $nitro-widget-crafting-width; + height: $nitro-widget-crafting-height; +} + +.nitro-widget-exchange-credit { + width: $nitro-widget-exchange-credit-width; + height: $nitro-widget-exchange-credit-height; + + .exchange-image { + background-image: url('@/assets/images/room-widgets/exchange-credit/exchange-credit-image.png'); + width: 103px; + height: 103px; + } +} + +.nitro-external-image-widget { + .picture-preview { + width: 320px; + height: 320px; + } + + .picture-preview-buttons { + display: flex; + align-items: center; + justify-content: space-between; + color: black; + } + + .picture-preview-buttons-previous, + .picture-preview-buttons-next { + color: #222; + background-color: white; + padding: 10px; + border-radius: 50%; + } +} + +.nitro-gift-opening { + width: 340px; + resize: none; +} + +.nitro-mannequin { + width: 300px; + + .mannequin-preview { + display: flex; + justify-content: center; + align-items: center; + width: 83px; + height: 130px; + background-image: url('@/assets/images/room-widgets/mannequin-widget/mannequin-spritesheet.png'); + overflow: hidden; + + .avatar-image { + background-position: unset; + top: -8px; + } + } +} + +.nitro-stickie { + position: relative; + width: 185px; + height: 178px; + top: 25px; + left: 25px; + padding: 1px; + pointer-events: all; + + .stickie-header { + width: 183px; + height: 18px; + padding: 0 7px; + + .header-trash, + .header-close { + cursor: pointer; + } + + .stickie-color { + width: 10px; + height: 10px; + cursor: pointer; + } + } + + .stickie-context { + width: 183px; + height: 145px; + padding: 2px 7px; + font-size: 12px; + color: $black; + + .context-text { + width: 100%; + height: 100%; + padding: 0; + overflow-wrap: break-word; + white-space: break-spaces; + overflow-y: auto; + } + + textarea { + background: transparent; + border: 0; + outline: none; + box-shadow: none; + resize: none; + font-style: italic; + + &:active { + border: 0; + outline: none; + box-shadow: none; + } + } + } +} + +.nitro-stickie-image { + background-image: url('@/assets/images/room-widgets/stickie-widget/stickie-spritesheet.png'); + + &.stickie-blue, + &.stickie-yellow, + &.stickie-green, + &.stickie-pink { + width: 185px; + height: 178px; + } + + &.stickie-blue { + background-position: -2px -2px; + } + + &.stickie-yellow { + background-image: url('@/assets/images/room-widgets/stickie-widget/stickie-yellow.png'); + //background-position: -191px -184px; + } + + &.stickie-green { + background-position: -191px -2px; + } + + &.stickie-pink { + background-position: -2px -184px; + } + + &.stickie-christmas { + background-image: url('@/assets/images/room-widgets/stickie-widget/stickie-christmas.png'); + } + + &.stickie-shakesp { + background-image: url('@/assets/images/room-widgets/stickie-widget/stickie-shakesp.png'); + } + + &.stickie-dreams { + background-image: url('@/assets/images/room-widgets/stickie-widget/stickie-dreams.png'); + } + + &.stickie-heart { + background-image: url('@/assets/images/room-widgets/stickie-widget/stickie-heart.png'); + } + + &.stickie-juninas { + background-image: url('@/assets/images/room-widgets/stickie-widget/stickie-juninas.png'); + } + + &.stickie-close { + width: 10px; + height: 10px; + background-position: -2px -366px; + } + + &.stickie-trash { + width: 9px; + height: 10px; + background-position: -16px -366px; + } +} + +.nitro-engraving-lock { + width: 300px; + + .engraving-lock-stage-1 { + width: 31px; + height: 39px; + background-position: -380px -43px; + 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'); + } +} + +.nitro-engraving-lock-view { + width: 375px; + height: 210px; + background-position: 0px 0px; + background-image: url('@/assets/images/room-widgets/engraving-lock-widget/engraving-lock-spritesheet.png'); + + color: #622e54; + font-weight: bold; + font-size: 16px; + text-shadow: 0px 1px white; + + &.engraving-lock-3 { + background-position: 0px -210px; + color: #614110; + } + + &.engraving-lock-4 { + background-position: 0px -420px; + color: #f1dcc8; + text-shadow: 0px 2px rgba(0, 0, 0, 0.4); + + .engraving-lock-avatar { + margin-bottom: 10px; + } + } + + .engraving-lock-close { + position: absolute; + cursor: pointer; + width: 15px; + height: 15px; + top: 34px; + right: 27px; + } + + .engraving-lock-avatar { + width: 70px; + height: 120px; + + div { + position: absolute; + margin-top: -5px; + } + + &:nth-child(1) { + div { + margin-left: -10px; + } + } + + &:nth-child(2) { + div { + margin-left: -15px; + } + } + } +} + +.nitro-widget-high-score { + width: 250px; + max-width: 250px; + height: 200px; +} + +.youtube-tv-widget { + width: 600px; + height: 380px; + + .youtube-video-container { + .empty-video { + background-color: black; + color: white; + width: 100%; + height: 100%; + text-align: center; + } + + .youtubeContainer { + position: relative; + width: 100%; + height: 100%; + overflow: hidden; + margin-bottom: 50px; + } + + .youtubeContainer iframe { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + } + } + + .playlist-container { + overflow-y: auto; + margin-right: -10px; + color: black; + height: 100%; + + .playlist-controls { + width: 100%; + .icon { + margin-right: 10px; + margin-bottom: 10px; + } + } + + .playlist-grid { + height: 100%; + width: 100%; + } + } +} + +.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'); + 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'); + mask-image: url('@/assets/images/room-widgets/playlist-editor/disk_image.png'); + height: 76px; + width: 76px; + } + } +} + +.nitro-mysterybox-dialog { + width: 375px; + height: 210px; + + .prize-container { + height: 80px; + width: 81px; + background-image: url('@/assets/images/prize/prize_background.png'); + background-repeat: no-repeat; + background-position: center; + } +} + +.nitro-mysterytrophy-dialog +{ + .mysterytrophy-dialog-top + { + width: 400px; + height: 120px; + border-radius: 2px; + background-color: #0E3F52; + + .mysterytrophy-image + { + width: 80px; + height: 84px; + position: relative; + background-image: url('@/assets/images/mysterytrophy/frank_mystery_trophy.png'); + background-repeat: no-repeat; + } + + .mysterytrophy-text-big + { + font-size: 16px; + } + } + + .mysterytrophy-dialog-bottom + { + display: flex; + justify-content: center; + width: 400px; + height: 120px; + border-radius: 2px; + background-color: #E9E9E1; + + .input-mysterytrophy-dialog + { + width: 380px; + border: 1px solid black; + + .input-mysterytrophy + { + width: 350px; + border: 0; + outline: 0; + } + + .mysterytrophy-pencil-image + { + width: 16px; + height: 16px; + position: relative; + background-image: url('@/assets/images/infostand/pencil-icon.png'); + background-repeat: no-repeat; + } + } + + .text-decoration + { + text-decoration: underline; + } + } +} diff --git a/src/components/room/widgets/furniture/FurnitureWidgetsView.tsx b/src/components/room/widgets/furniture/FurnitureWidgetsView.tsx new file mode 100644 index 0000000..d0e4066 --- /dev/null +++ b/src/components/room/widgets/furniture/FurnitureWidgetsView.tsx @@ -0,0 +1,48 @@ +import { FC } from 'react'; +import { Base } from '../../../../common'; +import { FurnitureContextMenuView } from './context-menu/FurnitureContextMenuView'; +import { FurnitureBackgroundColorView } from './FurnitureBackgroundColorView'; +import { FurnitureBadgeDisplayView } from './FurnitureBadgeDisplayView'; +import { FurnitureCraftingView } from './FurnitureCraftingView'; +import { FurnitureDimmerView } from './FurnitureDimmerView'; +import { FurnitureExchangeCreditView } from './FurnitureExchangeCreditView'; +import { FurnitureExternalImageView } from './FurnitureExternalImageView'; +import { FurnitureFriendFurniView } from './FurnitureFriendFurniView'; +import { FurnitureGiftOpeningView } from './FurnitureGiftOpeningView'; +import { FurnitureHighScoreView } from './FurnitureHighScoreView'; +import { FurnitureInternalLinkView } from './FurnitureInternalLinkView'; +import { FurnitureMannequinView } from './FurnitureMannequinView'; +import { FurnitureRoomLinkView } from './FurnitureRoomLinkView'; +import { FurnitureSpamWallPostItView } from './FurnitureSpamWallPostItView'; +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 => +{ + return ( + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/components/room/widgets/furniture/FurnitureYoutubeDisplayView.tsx b/src/components/room/widgets/furniture/FurnitureYoutubeDisplayView.tsx new file mode 100644 index 0000000..fba0f67 --- /dev/null +++ b/src/components/room/widgets/furniture/FurnitureYoutubeDisplayView.tsx @@ -0,0 +1,109 @@ +import { FC, useEffect, useState } from 'react'; +import YouTube, { Options } from 'react-youtube'; +import { YouTubePlayer } from 'youtube-player/dist/types'; +import { LocalizeText, YoutubeVideoPlaybackStateEnum } from '../../../../api'; +import { AutoGrid, AutoGridProps, LayoutGridItem, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; +import { useFurnitureYoutubeWidget } from '../../../../hooks'; + +interface FurnitureYoutubeDisplayViewProps extends AutoGridProps +{ + +} + +export const FurnitureYoutubeDisplayView: FC<{}> = FurnitureYoutubeDisplayViewProps => +{ + const [ player, setPlayer ] = useState(null); + const { objectId = -1, videoId = null, videoStart = 0, videoEnd = 0, currentVideoState = null, selectedVideo = null, playlists = [], onClose = null, previous = null, next = null, pause = null, play = null, selectVideo = null } = useFurnitureYoutubeWidget(); + + const onStateChange = (event: { target: YouTubePlayer; data: number }) => + { + setPlayer(event.target); + + if(objectId === -1) return; + + switch(event.target.getPlayerState()) + { + case -1: + case 1: + if(currentVideoState === 2) + { + //event.target.pauseVideo(); + } + + if(currentVideoState !== 1) play(); + return; + case 2: + if(currentVideoState !== 2) pause(); + } + } + + useEffect(() => + { + if((currentVideoState === null) || !player) return; + + if((currentVideoState === YoutubeVideoPlaybackStateEnum.PLAYING) && (player.getPlayerState() !== YoutubeVideoPlaybackStateEnum.PLAYING)) + { + player.playVideo(); + + return; + } + + if((currentVideoState === YoutubeVideoPlaybackStateEnum.PAUSED) && (player.getPlayerState() !== YoutubeVideoPlaybackStateEnum.PAUSED)) + { + player.pauseVideo(); + + return; + } + }, [ currentVideoState, player ]); + + 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 + } + } + + return ( + + + +
+
+ { (videoId && videoId.length > 0) && + setPlayer(event.target) } onStateChange={ onStateChange } containerClassName={ 'youtubeContainer' } /> + } + { (!videoId || videoId.length === 0) && +
{ LocalizeText('widget.furni.video_viewer.no_videos') }
+ } +
+
+ + + + +
{ LocalizeText('widget.furni.video_viewer.playlists') }
+ + { playlists && playlists.map((entry, index) => + { + return ( + selectVideo(entry.video) } itemActive={ (entry.video === selectedVideo) }> + { entry.title } + + ) + }) } + +
+
+
+
+ ) +} diff --git a/src/components/room/widgets/furniture/context-menu/EffectBoxConfirmView.tsx b/src/components/room/widgets/furniture/context-menu/EffectBoxConfirmView.tsx new file mode 100644 index 0000000..f7e35b9 --- /dev/null +++ b/src/components/room/widgets/furniture/context-menu/EffectBoxConfirmView.tsx @@ -0,0 +1,40 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../../../api'; +import { Button, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../../common'; +import { useRoom } from '../../../../../hooks'; + +interface EffectBoxConfirmViewProps +{ + objectId: number; + onClose: () => void; +} + +export const EffectBoxConfirmView: FC = props => +{ + const { objectId = -1, onClose = null } = props; + const { roomSession = null } = useRoom(); + + const useProduct = () => + { + roomSession.useMultistateItem(objectId); + + onClose(); + } + + return ( + + + + + + { LocalizeText('effectbox.header.description') } + + + + + + + + + ); +} diff --git a/src/components/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx b/src/components/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx new file mode 100644 index 0000000..d58d369 --- /dev/null +++ b/src/components/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx @@ -0,0 +1,130 @@ +import { ContextMenuEnum, CustomUserNotificationMessageEvent, GetSessionDataManager, RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { GetGroupInformation, LocalizeText } from '../../../../../api'; +import { EFFECTBOX_OPEN, GROUP_FURNITURE, MONSTERPLANT_SEED_CONFIRMATION, MYSTERYTROPHY_OPEN_DIALOG, PURCHASABLE_CLOTHING_CONFIRMATION, useFurnitureContextMenuWidget, useMessageEvent, useNotification } from '../../../../../hooks'; +import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView'; +import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView'; +import { ContextMenuView } from '../../context-menu/ContextMenuView'; +import { FurnitureMysteryBoxOpenDialogView } from '../FurnitureMysteryBoxOpenDialogView'; +import { FurnitureMysteryTrophyOpenDialogView } from '../FurnitureMysteryTrophyOpenDialogView'; +import { EffectBoxConfirmView } from './EffectBoxConfirmView'; +import { MonsterPlantSeedConfirmView } from './MonsterPlantSeedConfirmView'; +import { PurchasableClothingConfirmView } from './PurchasableClothingConfirmView'; + +export const FurnitureContextMenuView: FC<{}> = props => +{ + const { closeConfirm = null, processAction = null, onClose = null, objectId = -1, mode = null, confirmMode = null, confirmingObjectId = -1, groupData = null, isGroupMember = false, objectOwnerId = -1 } = useFurnitureContextMenuWidget(); + const { simpleAlert = null } = useNotification(); + + useMessageEvent(CustomUserNotificationMessageEvent, event => + { + const parser = event.getParser(); + + if(!parser) return; + + // HOPPER_NO_COSTUME = 1; HOPPER_NO_HC = 2; GATE_NO_HC = 3; STARS_NOT_CANDIDATE = 4 (not coded in Emulator); STARS_NOT_ENOUGH_USERS = 5 (not coded in Emulator); + + switch(parser.count) + { + case 1: + simpleAlert(LocalizeText('costumehopper.costumerequired.bodytext'), null, 'catalog/open/temporary_effects' , LocalizeText('costumehopper.costumerequired.buy'), LocalizeText('costumehopper.costumerequired.header'), null); + break; + case 2: + simpleAlert(LocalizeText('viphopper.viprequired.bodytext'), null, 'catalog/open/habbo_club' , LocalizeText('viprequired.buy.vip'), LocalizeText('viprequired.header'), null); + break; + case 3: + simpleAlert(LocalizeText('gate.viprequired.bodytext'), null, 'catalog/open/habbo_club' , LocalizeText('viprequired.buy.vip'), LocalizeText('gate.viprequired.title'), null); + break; + } + }); + + const isOwner = GetSessionDataManager().userId === objectOwnerId; + + return ( + <> + { (confirmMode === MONSTERPLANT_SEED_CONFIRMATION) && + } + { (confirmMode === PURCHASABLE_CLOTHING_CONFIRMATION) && + } + { (confirmMode === EFFECTBOX_OPEN) && + } + { (confirmMode === MYSTERYTROPHY_OPEN_DIALOG) && + } + + { (objectId >= 0) && mode && + + { (mode === ContextMenuEnum.FRIEND_FURNITURE) && + <> + + { LocalizeText('friendfurni.context.title') } + + processAction('use_friend_furni') }> + { LocalizeText('friendfurni.context.use') } + + } + { (mode === ContextMenuEnum.MONSTERPLANT_SEED) && + <> + + { LocalizeText('furni.mnstr_seed.name') } + + processAction('use_monsterplant_seed') }> + { LocalizeText('widget.monsterplant_seed.button.use') } + + } + { (mode === ContextMenuEnum.RANDOM_TELEPORT) && + <> + + { LocalizeText('furni.random_teleport.name') } + + processAction('use_random_teleport') }> + { LocalizeText('widget.random_teleport.button.use') } + + } + { (mode === ContextMenuEnum.PURCHASABLE_CLOTHING) && + <> + + { LocalizeText('furni.generic_usable.name') } + + processAction('use_purchaseable_clothing') }> + { LocalizeText('widget.generic_usable.button.use') } + + } + { (mode === ContextMenuEnum.MYSTERY_BOX) && + <> + + { LocalizeText('mysterybox.context.title') } + + processAction('use_mystery_box') }> + { LocalizeText('mysterybox.context.' + ((isOwner) ? 'owner' : 'other') + '.use') } + + } + { (mode === ContextMenuEnum.MYSTERY_TROPHY) && + <> + + { LocalizeText('mysterytrophy.header.title') } + + processAction('use_mystery_trophy') }> + { LocalizeText('friendfurni.context.use') } + + } + { (mode === GROUP_FURNITURE) && groupData && + <> + GetGroupInformation(groupData.guildId) }> + { groupData.guildName } + + { !isGroupMember && + processAction('join_group') }> + { LocalizeText('widget.furniture.button.join.group') } + } + processAction('go_to_group_homeroom') }> + { LocalizeText('widget.furniture.button.go.to.group.home.room') } + + { groupData.guildHasReadableForum && + processAction('open_forum') }> + { LocalizeText('widget.furniture.button.open_group_forum') } + } + } + } + + ) +} diff --git a/src/components/room/widgets/furniture/context-menu/MonsterPlantSeedConfirmView.tsx b/src/components/room/widgets/furniture/context-menu/MonsterPlantSeedConfirmView.tsx new file mode 100644 index 0000000..b7163cc --- /dev/null +++ b/src/components/room/widgets/furniture/context-menu/MonsterPlantSeedConfirmView.tsx @@ -0,0 +1,85 @@ +import { IFurnitureData, RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { FurniCategory, GetFurnitureDataForRoomObject, LocalizeText } from '../../../../../api'; +import { Base, Button, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../../common'; +import { useRoom } from '../../../../../hooks'; + +interface MonsterPlantSeedConfirmViewProps +{ + objectId: number; + onClose: () => void; +} + +const MODE_DEFAULT: number = -1; +const MODE_MONSTERPLANT_SEED: number = 0; + +export const MonsterPlantSeedConfirmView: FC = props => +{ + const { objectId = -1, onClose = null } = props; + const [ furniData, setFurniData ] = useState(null); + const [ mode, setMode ] = useState(MODE_DEFAULT); + const { roomSession = null } = useRoom(); + + const useProduct = () => + { + roomSession.useMultistateItem(objectId); + + onClose(); + } + + useEffect(() => + { + if(!roomSession || (objectId === -1)) return; + + const furniData = GetFurnitureDataForRoomObject(roomSession.roomId, objectId, RoomObjectCategory.FLOOR); + + if(!furniData) return; + + setFurniData(furniData); + + let mode = MODE_DEFAULT; + + switch(furniData.specialType) + { + case FurniCategory.MONSTERPLANT_SEED: + mode = MODE_MONSTERPLANT_SEED; + break; + } + + if(mode === MODE_DEFAULT) + { + onClose(); + + return; + } + + setMode(mode); + }, [ roomSession, objectId, onClose ]); + + if(mode === MODE_DEFAULT) return null; + + return ( + + + + + + + + + + + + { LocalizeText('useproduct.widget.text.plant_seed', [ 'productName' ], [ furniData.name ] ) } + { LocalizeText('useproduct.widget.info.plant_seed') } + + + + + + + + + + ); +} diff --git a/src/components/room/widgets/furniture/context-menu/PurchasableClothingConfirmView.tsx b/src/components/room/widgets/furniture/context-menu/PurchasableClothingConfirmView.tsx new file mode 100644 index 0000000..5ba9905 --- /dev/null +++ b/src/components/room/widgets/furniture/context-menu/PurchasableClothingConfirmView.tsx @@ -0,0 +1,104 @@ +import { GetAvatarRenderManager, GetSessionDataManager, RedeemItemClothingComposer, RoomObjectCategory, UserFigureComposer } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { FigureData, FurniCategory, GetFurnitureDataForRoomObject, LocalizeText, SendMessageComposer } from '../../../../../api'; +import { Base, Button, Column, Flex, LayoutAvatarImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../../common'; +import { useRoom } from '../../../../../hooks'; + +interface PurchasableClothingConfirmViewProps +{ + objectId: number; + onClose: () => void; +} + +const MODE_DEFAULT: number = -1; +const MODE_PURCHASABLE_CLOTHING: number = 0; + +export const PurchasableClothingConfirmView: FC = props => +{ + const { objectId = -1, onClose = null } = props; + const [ mode, setMode ] = useState(MODE_DEFAULT); + const [ gender, setGender ] = useState(FigureData.MALE); + const [ newFigure, setNewFigure ] = useState(null); + const { roomSession = null } = useRoom(); + + const useProduct = () => + { + SendMessageComposer(new RedeemItemClothingComposer(objectId)); + SendMessageComposer(new UserFigureComposer(gender, newFigure)); + + onClose(); + } + + useEffect(() => + { + let mode = MODE_DEFAULT; + + const figure = GetSessionDataManager().figure; + const gender = GetSessionDataManager().gender; + const validSets: number[] = []; + + if(roomSession && (objectId >= 0)) + { + const furniData = GetFurnitureDataForRoomObject(roomSession.roomId, objectId, RoomObjectCategory.FLOOR); + + if(furniData) + { + switch(furniData.specialType) + { + case FurniCategory.FIGURE_PURCHASABLE_SET: + mode = MODE_PURCHASABLE_CLOTHING; + + const setIds = furniData.customParams.split(',').map(part => parseInt(part)); + + for(const setId of setIds) + { + if(GetAvatarRenderManager().isValidFigureSetForGender(setId, gender)) validSets.push(setId); + } + + break; + } + } + } + + if(mode === MODE_DEFAULT) + { + onClose(); + + return; + } + + setGender(gender); + setNewFigure(GetAvatarRenderManager().getFigureStringWithFigureIds(figure, gender, validSets)); + + // if owns clothing, change to it + + setMode(mode); + }, [ roomSession, objectId, onClose ]); + + if(mode === MODE_DEFAULT) return null; + + return ( + + + + + + + + + + + + { LocalizeText('useproduct.widget.text.bind_clothing') } + { LocalizeText('useproduct.widget.info.bind_clothing') } + + + + + + + + + + ); +} 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 0000000..baf1bbf --- /dev/null +++ b/src/components/room/widgets/furniture/playlist-editor/DiskInventoryView.tsx @@ -0,0 +1,94 @@ +import { CreateLinkEvent, GetSoundManager, IAdvancedMap, MusicPriorities } from '@nitrots/nitro-renderer'; +import { FC, MouseEvent, useCallback, useEffect, useState } from 'react'; +import { CatalogPageName, GetConfigurationValue, GetDiskColor, 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, GetSoundManager().musicController?.getRoomItemPlaylist()?.length) + }, [ addToPlaylist ]); + + const openCatalogPage = () => + { + CreateLinkEvent('catalog/open/' + CatalogPageName.TRAX_SONGS); + } + + useEffect(() => + { + if(previewSongId === -1) return; + + GetSoundManager().musicController?.playSong(previewSongId, MusicPriorities.PRIORITY_SONG_PLAY, 0, 0, 0, 0); + + return () => + { + GetSoundManager().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 songInfo = GetSoundManager().musicController?.getSongInfo(songId); + + return ( + setSelectedItem(prev => prev === index ? -1 : index) } classNames={ [ 'text-black' ] }> +
+
+ { songInfo?.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 0000000..ceb0db9 --- /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 0000000..3e88329 --- /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 { GetConfigurationValue, 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/components/room/widgets/mysterybox/MysteryBoxExtensionView.scss b/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.scss new file mode 100644 index 0000000..254821c --- /dev/null +++ b/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.scss @@ -0,0 +1,52 @@ +.mysterybox-extension { + + .mysterybox-container { + max-width: 50px; + max-height: 50px; + width: 50px; + height: 50px; + + background-color: rgba(28, 28, 32, 0.95); + box-shadow: inset 0px 5px rgb(34 34 39 / 60%), inset 0 -4px rgb(18 18 21 / 60%); + border-color: #5b5a57; + } + + .box-image { + width: 31px; + height: 36px; + position: relative; + background-image: url('@/assets/images/mysterybox/mystery_box.png'); + -webkit-mask-image: url('@/assets/images/mysterybox/mystery_box.png'); + mask-image: url('@/assets/images/mysterybox/mystery_box.png'); + + .chain-overlay-image { + width: 31px; + height: 36px; + position: absolute; + background-image: url('@/assets/images/mysterybox/chain_mysterybox_box_overlay.png'); + } + } + + .key-image { + width: 39px; + height: 39px; + position: relative; + background-image: url('@/assets/images/mysterybox/mystery_box_key.png'); + -webkit-mask-image: url('@/assets/images/mysterybox/mystery_box_key.png'); + mask-image: url('@/assets/images/mysterybox/mystery_box_key.png'); + + .key-overlay-image { + width: 39px; + height: 39px; + position: absolute; + background-image: url('@/assets/images/mysterybox/key_overlay.png'); + } + } + + .box-image, + .key-image { + background-blend-mode: multiply; + background-position: center; + background-repeat: no-repeat; + } +} diff --git a/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.tsx b/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.tsx new file mode 100644 index 0000000..3f69826 --- /dev/null +++ b/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.tsx @@ -0,0 +1,67 @@ +import { MysteryBoxKeysUpdateEvent } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { FaChevronDown, FaChevronUp } from 'react-icons/fa'; +import { ColorUtils, LocalizeText } from '../../../../api'; +import { Base, Column, Flex, LayoutGridItem, Text } from '../../../../common'; +import { useNitroEvent } from '../../../../hooks'; + +const colorMap = { + 'purple': 9452386, + 'blue': 3891856, + 'green': 6459451, + 'yellow': 10658089, + 'lilac': 6897548, + 'orange': 10841125, + 'turquoise': 2661026, + 'red': 10104881 +} + +export const MysteryBoxExtensionView: FC<{}> = props => +{ + const [ isOpen, setIsOpen ] = useState(true); + const [ keyColor, setKeyColor ] = useState(''); + const [ boxColor, setBoxColor ] = useState(''); + + useNitroEvent(MysteryBoxKeysUpdateEvent.MYSTERY_BOX_KEYS_UPDATE, event => + { + setKeyColor(event.keyColor); + setBoxColor(event.boxColor); + }); + + const getRgbColor = (color: string) => + { + const colorInt = colorMap[color]; + + return ColorUtils.int2rgb(colorInt); + } + + if(keyColor === '' && boxColor === '') return null; + + return ( + + + setIsOpen(value => !value) }> + { LocalizeText('mysterybox.tracker.title') } + { isOpen && } + { !isOpen && } + + { isOpen && + <> + { LocalizeText('mysterybox.tracker.description') } + + +
+
+
+ + +
+
+
+ + + } + + + ); +} diff --git a/src/components/room/widgets/object-location/ObjectLocationView.tsx b/src/components/room/widgets/object-location/ObjectLocationView.tsx new file mode 100644 index 0000000..237090b --- /dev/null +++ b/src/components/room/widgets/object-location/ObjectLocationView.tsx @@ -0,0 +1,61 @@ +import { GetTicker } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useRef, useState } from 'react'; +import { GetRoomObjectBounds, GetRoomSession } from '../../../../api'; +import { Base, BaseProps } from '../../../../common'; + +interface ObjectLocationViewProps extends BaseProps +{ + objectId: number; + category: number; + noFollow?: boolean; +} + +export const ObjectLocationView: FC = props => +{ + const { objectId = -1, category = -1, noFollow = false, position = 'absolute', ...rest } = props; + const [ pos, setPos ] = useState<{ x: number, y: number }>({ x: -1, y: -1 }); + const elementRef = useRef(); + + useEffect(() => + { + let remove = false; + + const getObjectLocation = () => + { + const roomSession = GetRoomSession(); + const objectBounds = GetRoomObjectBounds(roomSession.roomId, objectId, category, 1); + + return objectBounds; + } + + const updatePosition = () => + { + const bounds = getObjectLocation(); + + if(!bounds || !elementRef.current) return; + + setPos({ + x: Math.round(((bounds.left + (bounds.width / 2)) - (elementRef.current.offsetWidth / 2))), + y: Math.round((bounds.top - elementRef.current.offsetHeight) + 10) + }); + } + + if(noFollow) + { + updatePosition(); + } + else + { + remove = true; + + GetTicker().add(updatePosition); + } + + return () => + { + if(remove) GetTicker().remove(updatePosition); + } + }, [ objectId, category, noFollow ]); + + return -1 } className="object-location" style={ { left: pos.x, top: pos.y } } { ...rest } />; +} diff --git a/src/components/room/widgets/pet-package/PetPackageWidgetView.scss b/src/components/room/widgets/pet-package/PetPackageWidgetView.scss new file mode 100644 index 0000000..97a6d2e --- /dev/null +++ b/src/components/room/widgets/pet-package/PetPackageWidgetView.scss @@ -0,0 +1,106 @@ +.nitro-pet-package +{ + .pet-package-container-top + { + width: 400px; + height: 120px; + border-radius: 2px; + background-color: #0E3F52; + + .package-image-gnome_box + { + width: 80px; + height: 84px; + position: relative; + background-image: url('@/assets/images/pets/pet-package/gnome.png'); + background-repeat: no-repeat; + } + + .package-image-leprechaun_box + { + width: 80px; + height: 84px; + position: relative; + background-image: url('@/assets/images/pets/pet-package/leprechaun_box.png'); + background-repeat: no-repeat; + } + + .package-image-val11_present + { + width: 80px; + height: 84px; + position: relative; + background-image: url('@/assets/images/pets/pet-package/val11_present.png'); + background-repeat: no-repeat; + } + + .package-image-velociraptor_egg + { + width: 80px; + height: 84px; + position: relative; + background-image: url('@/assets/images/pets/pet-package/velociraptor_egg.png'); + background-repeat: no-repeat; + } + + .package-image-pterosaur_egg + { + width: 80px; + height: 84px; + position: relative; + background-image: url('@/assets/images/pets/pet-package/pterosaur_egg.png'); + background-repeat: no-repeat; + } + + .package-image-petbox_epic + { + width: 80px; + height: 84px; + position: relative; + background-image: url('@/assets/images/pets/pet-package/petbox_epic.png'); + background-repeat: no-repeat; + } + + .package-text-big + { + font-size: 16px; + } + } + + .pet-package-container-bottom + { + display: flex; + justify-content: center; + width: 400px; + height: 120px; + border-radius: 2px; + background-color: #E9E9E1; + + .input-pet-package-container + { + width: 380px; + border: 1px solid black; + + .input-pet-package + { + width: 350px; + border: 0; + outline: 0; + } + + .package-pencil-image + { + width: 16px; + height: 16px; + position: relative; + background-image: url('@/assets/images/infostand/pencil-icon.png'); + background-repeat: no-repeat; + } + } + + .text-decoration + { + text-decoration: underline; + } + } +} diff --git a/src/components/room/widgets/pet-package/PetPackageWidgetView.tsx b/src/components/room/widgets/pet-package/PetPackageWidgetView.tsx new file mode 100644 index 0000000..e204f5e --- /dev/null +++ b/src/components/room/widgets/pet-package/PetPackageWidgetView.tsx @@ -0,0 +1,42 @@ +import { FC } from 'react'; +import { Button } from 'react-bootstrap'; +import { GetConfigurationValue, LocalizeText } from '../../../../api'; +import { Base, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; +import { usePetPackageWidget } from '../../../../hooks'; + +export const PetPackageWidgetView: FC<{}> = props => +{ + const { isVisible = false, errorResult = null, petName = null, objectType = null, onChangePetName = null, onConfirm = null, onClose = null } = usePetPackageWidget(); + + return ( + <> + { isVisible && + + onClose() } /> + + +
+
+ { objectType === 'gnome_box' ? LocalizeText('widgets.gnomepackage.name.title') : LocalizeText('furni.petpackage') } +
+
+ + + + onChangePetName(event.target.value) } /> +
+
+ { (errorResult.length > 0) && + { errorResult } } + + onClose() }>{ LocalizeText('cancel') } + + +
+
+
+
+ } + + ); +} diff --git a/src/components/room/widgets/room-filter-words/RoomFilterWordsWidgetView.tsx b/src/components/room/widgets/room-filter-words/RoomFilterWordsWidgetView.tsx new file mode 100644 index 0000000..4f7825f --- /dev/null +++ b/src/components/room/widgets/room-filter-words/RoomFilterWordsWidgetView.tsx @@ -0,0 +1,74 @@ +import { UpdateRoomFilterMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { LocalizeText, SendMessageComposer } from '../../../../api'; +import { Button, classNames, Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; +import { useFilterWordsWidget, useNavigator } from '../../../../hooks'; + +export const RoomFilterWordsWidgetView: FC<{}> = props => +{ + const [ word, setWord ] = useState('bobba'); + const [ selectedWord, setSelectedWord ] = useState(''); + const [ isSelectingWord, setIsSelectingWord ] = useState(false); + const { wordsFilter = [], isVisible = null, setWordsFilter, onClose = null } = useFilterWordsWidget(); + const { navigatorData = null } = useNavigator(); + + const processAction = (isAddingWord: boolean) => + { + if ((isSelectingWord) ? (!selectedWord) : (!word)) return; + + SendMessageComposer(new UpdateRoomFilterMessageComposer(navigatorData.enteredGuestRoom.roomId, isAddingWord, (isSelectingWord ? selectedWord : word))); + setSelectedWord(''); + setWord('bobba'); + setIsSelectingWord(false); + + if (isAddingWord && wordsFilter.includes((isSelectingWord ? selectedWord : word))) return; + + setWordsFilter(prevValue => + { + const newWords = [ ...prevValue ]; + + isAddingWord ? newWords.push((isSelectingWord ? selectedWord : word)) : newWords.splice(newWords.indexOf((isSelectingWord ? selectedWord : word)), 1); + + return newWords; + }); + } + + const onTyping = (word: string) => + { + setWord(word); + setIsSelectingWord(false); + } + + const onSelectedWord = (word: string) => + { + setSelectedWord(word); + setIsSelectingWord(true); + } + + if (!isVisible) return null; + + return ( + + onClose() } /> + + + onTyping(event.target.value) } /> + + + + { wordsFilter && (wordsFilter.length > 0) && wordsFilter.map((word, index) => + { + return ( + onSelectedWord(word) }> + { word } + + ) + }) } + + + + + + + ); +}; diff --git a/src/components/room/widgets/room-promotes/RoomPromotesWidgetView.tsx b/src/components/room/widgets/room-promotes/RoomPromotesWidgetView.tsx new file mode 100644 index 0000000..b97b35a --- /dev/null +++ b/src/components/room/widgets/room-promotes/RoomPromotesWidgetView.tsx @@ -0,0 +1,55 @@ +import { DesktopViewEvent, GetSessionDataManager } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { FaChevronDown, FaChevronUp } from 'react-icons/fa'; +import { Base, Column, Flex, Text } from '../../../../common'; +import { useMessageEvent, useRoomPromote } from '../../../../hooks'; +import { RoomPromoteEditWidgetView, RoomPromoteMyOwnEventWidgetView, RoomPromoteOtherEventWidgetView } from './views'; + +export const RoomPromotesWidgetView: FC<{}> = props => +{ + const [ isEditingPromote, setIsEditingPromote ] = useState(false); + const [ isOpen, setIsOpen ] = useState(true); + const { promoteInformation, setPromoteInformation } = useRoomPromote(); + + useMessageEvent(DesktopViewEvent, event => + { + setPromoteInformation(null); + }); + + if(!promoteInformation) return null; + + return ( + <> + { promoteInformation.data.adId !== -1 && + + + setIsOpen(value => !value) }> + { promoteInformation.data.eventName } + { isOpen && } + { !isOpen && } + + { (isOpen && GetSessionDataManager().userId !== promoteInformation.data.ownerAvatarId) && + + } + { (isOpen && GetSessionDataManager().userId === promoteInformation.data.ownerAvatarId) && + setIsEditingPromote(true) } + /> + } + { isEditingPromote && + setIsEditingPromote(false) } + /> + } + + + } + + ); +}; diff --git a/src/components/room/widgets/room-promotes/views/RoomPromoteEditWidgetView.tsx b/src/components/room/widgets/room-promotes/views/RoomPromoteEditWidgetView.tsx new file mode 100644 index 0000000..f9b8462 --- /dev/null +++ b/src/components/room/widgets/room-promotes/views/RoomPromoteEditWidgetView.tsx @@ -0,0 +1,44 @@ +import { EditEventMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { LocalizeText, SendMessageComposer } from '../../../../../api'; +import { Button, Column, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../../common'; + +interface RoomPromoteEditWidgetViewProps +{ + eventId: number; + eventName: string; + eventDescription: string; + setIsEditingPromote: (value: boolean) => void; +} + +export const RoomPromoteEditWidgetView: FC = props => +{ + const { eventId = -1, eventName = '', eventDescription = '', setIsEditingPromote = null } = props; + const [ newEventName, setNewEventName ] = useState(eventName); + const [ newEventDescription, setNewEventDescription ] = useState(eventDescription); + + const updatePromote = () => + { + SendMessageComposer(new EditEventMessageComposer(eventId, newEventName, newEventDescription)); + setIsEditingPromote(false); + } + + return ( + + setIsEditingPromote(false) } /> + + + { LocalizeText('navigator.eventsettings.name') } + setNewEventName(event.target.value) } /> + + + { LocalizeText('navigator.eventsettings.desc') } + + + + + + + + ); +}; diff --git a/src/components/room/widgets/room-promotes/views/RoomPromoteMyOwnEventWidgetView.tsx b/src/components/room/widgets/room-promotes/views/RoomPromoteMyOwnEventWidgetView.tsx new file mode 100644 index 0000000..01a5f78 --- /dev/null +++ b/src/components/room/widgets/room-promotes/views/RoomPromoteMyOwnEventWidgetView.tsx @@ -0,0 +1,36 @@ +import { CreateLinkEvent } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { LocalizeText } from '../../../../../api'; +import { Button, Flex, Grid, Text } from '../../../../../common'; +import { useRoomPromote } from '../../../../../hooks'; + +interface RoomPromoteMyOwnEventWidgetViewProps +{ + eventDescription: string; + setIsEditingPromote: (value: boolean) => void; +} + +export const RoomPromoteMyOwnEventWidgetView: FC = props => +{ + const { eventDescription = '', setIsEditingPromote = null } = props; + const { setIsExtended } = useRoomPromote(); + + const extendPromote = () => + { + setIsExtended(true); + CreateLinkEvent('catalog/open/room_event'); + } + + return ( + <> + + { eventDescription } + +

+ + + + + + ); +}; diff --git a/src/components/room/widgets/room-promotes/views/RoomPromoteOtherEventWidgetView.tsx b/src/components/room/widgets/room-promotes/views/RoomPromoteOtherEventWidgetView.tsx new file mode 100644 index 0000000..5adb517 --- /dev/null +++ b/src/components/room/widgets/room-promotes/views/RoomPromoteOtherEventWidgetView.tsx @@ -0,0 +1,30 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../../../api'; +import { Base, Column, Flex, Text } from '../../../../../common'; + +interface RoomPromoteOtherEventWidgetViewProps +{ + eventDescription: string; +} + +export const RoomPromoteOtherEventWidgetView: FC = props => +{ + const { eventDescription = '' } = props; + + return ( + <> + + { eventDescription } + +

+ + + + { LocalizeText('navigator.eventinprogress') } + +   + + + + ); +}; diff --git a/src/components/room/widgets/room-promotes/views/index.ts b/src/components/room/widgets/room-promotes/views/index.ts new file mode 100644 index 0000000..da74691 --- /dev/null +++ b/src/components/room/widgets/room-promotes/views/index.ts @@ -0,0 +1,3 @@ +export * from './RoomPromoteEditWidgetView'; +export * from './RoomPromoteMyOwnEventWidgetView'; +export * from './RoomPromoteOtherEventWidgetView'; diff --git a/src/components/room/widgets/room-thumbnail/RoomThumbnailWidgetView.tsx b/src/components/room/widgets/room-thumbnail/RoomThumbnailWidgetView.tsx new file mode 100644 index 0000000..edcb3ec --- /dev/null +++ b/src/components/room/widgets/room-thumbnail/RoomThumbnailWidgetView.tsx @@ -0,0 +1,41 @@ +import { GetRoomEngine, NitroRenderTexture } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { LayoutMiniCameraView } from '../../../../common'; +import { RoomWidgetThumbnailEvent } from '../../../../events'; +import { useRoom, useUiEvent } from '../../../../hooks'; + +export const RoomThumbnailWidgetView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const { roomSession = null } = useRoom(); + + useUiEvent([ + RoomWidgetThumbnailEvent.SHOW_THUMBNAIL, + RoomWidgetThumbnailEvent.HIDE_THUMBNAIL, + RoomWidgetThumbnailEvent.TOGGLE_THUMBNAIL ], event => + { + switch(event.type) + { + case RoomWidgetThumbnailEvent.SHOW_THUMBNAIL: + setIsVisible(true); + return; + case RoomWidgetThumbnailEvent.HIDE_THUMBNAIL: + setIsVisible(false); + return; + case RoomWidgetThumbnailEvent.TOGGLE_THUMBNAIL: + setIsVisible(value => !value); + return; + } + }); + + const receiveTexture = async (texture: NitroRenderTexture) => + { + await GetRoomEngine().saveTextureAsScreenshot(texture, true); + + setIsVisible(false); + } + + if(!isVisible) return null; + + return setIsVisible(false) } /> +}; diff --git a/src/components/room/widgets/room-tools/RoomToolsWidgetView.tsx b/src/components/room/widgets/room-tools/RoomToolsWidgetView.tsx new file mode 100644 index 0000000..76fd022 --- /dev/null +++ b/src/components/room/widgets/room-tools/RoomToolsWidgetView.tsx @@ -0,0 +1,100 @@ +import { CreateLinkEvent, GetGuestRoomResultEvent, GetRoomEngine, NavigatorSearchComposer, RateFlatMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, SendMessageComposer } from '../../../../api'; +import { Base, Column, Flex, Text, TransitionAnimation, TransitionAnimationTypes, classNames } from '../../../../common'; +import { useMessageEvent, useNavigator, useRoom } from '../../../../hooks'; + +export const RoomToolsWidgetView: FC<{}> = props => +{ + const [ isZoomedIn, setIsZoomedIn ] = useState(false); + const [ roomName, setRoomName ] = useState(null); + const [ roomOwner, setRoomOwner ] = useState(null); + const [ roomTags, setRoomTags ] = useState(null); + const [ isOpen, setIsOpen ] = useState(false); + const { navigatorData = null } = useNavigator(); + const { roomSession = null } = useRoom(); + + const handleToolClick = (action: string, value?: string) => + { + switch(action) + { + case 'settings': + CreateLinkEvent('navigator/toggle-room-info'); + return; + case 'zoom': + setIsZoomedIn(prevValue => + { + let scale = GetRoomEngine().getRoomInstanceRenderingCanvasScale(roomSession.roomId, 1); + + if(!prevValue) scale /= 2; + else scale *= 2; + + GetRoomEngine().setRoomInstanceRenderingCanvasScale(roomSession.roomId, 1, scale); + + return !prevValue; + }); + return; + case 'chat_history': + CreateLinkEvent('chat-history/toggle'); + return; + case 'like_room': + SendMessageComposer(new RateFlatMessageComposer(1)); + return; + case 'toggle_room_link': + CreateLinkEvent('navigator/toggle-room-link'); + return; + case 'navigator_search_tag': + CreateLinkEvent(`navigator/search/${ value }`); + SendMessageComposer(new NavigatorSearchComposer('hotel_view', `tag:${ value }`)); + return; + } + } + + useMessageEvent(GetGuestRoomResultEvent, event => + { + const parser = event.getParser(); + + if(!parser.roomEnter || (parser.data.roomId !== roomSession.roomId)) return; + + if(roomName !== parser.data.roomName) setRoomName(parser.data.roomName); + if(roomOwner !== parser.data.ownerName) setRoomOwner(parser.data.ownerName); + if(roomTags !== parser.data.tags) setRoomTags(parser.data.tags); + }); + + useEffect(() => + { + setIsOpen(true); + + const timeout = setTimeout(() => setIsOpen(false), 5000); + + return () => clearTimeout(timeout); + }, [ roomName, roomOwner, roomTags ]); + + return ( + + + handleToolClick('settings') } /> + handleToolClick('zoom') } className={ classNames('icon', (!isZoomedIn && 'icon-zoom-less'), (isZoomedIn && 'icon-zoom-more')) } /> + handleToolClick('chat_history') } className="icon icon-chat-history" /> + { navigatorData.canRate && + handleToolClick('like_room') } className="icon icon-like-room" /> } + + + + + + + { roomName } + { roomOwner } + + { roomTags && roomTags.length > 0 && + + { roomTags.map((tag, index) => handleToolClick('navigator_search_tag', tag) }>#{ tag }) } + } + + + + + + ); +} diff --git a/src/components/room/widgets/user-location/UserLocationView.tsx b/src/components/room/widgets/user-location/UserLocationView.tsx new file mode 100644 index 0000000..ca281ec --- /dev/null +++ b/src/components/room/widgets/user-location/UserLocationView.tsx @@ -0,0 +1,24 @@ +import { RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { BaseProps } from '../../../../common'; +import { useRoom } from '../../../../hooks'; +import { ObjectLocationView } from '../object-location/ObjectLocationView'; + +interface UserLocationViewProps extends BaseProps +{ + userId: number; +} + +export const UserLocationView: FC = props => +{ + const { userId = -1, ...rest } = props; + const { roomSession = null } = useRoom(); + + if((userId === -1) || !roomSession) return null; + + const userData = roomSession.userDataManager.getUserData(userId); + + if(!userData) return null; + + return ; +} diff --git a/src/components/room/widgets/word-quiz/WordQuizQuestionView.tsx b/src/components/room/widgets/word-quiz/WordQuizQuestionView.tsx new file mode 100644 index 0000000..3b4d051 --- /dev/null +++ b/src/components/room/widgets/word-quiz/WordQuizQuestionView.tsx @@ -0,0 +1,44 @@ +import { FC } from 'react'; +import { VALUE_KEY_DISLIKE, VALUE_KEY_LIKE } from '../../../../api'; +import { Base, Column, Flex, Text } from '../../../../common'; + +interface WordQuizQuestionViewProps +{ + question: string; + canVote: boolean; + vote(value: string): void; + noVotes: number; + yesVotes: number; +} + +export const WordQuizQuestionView: FC = props => +{ + const { question = null, canVote = null, vote = null, noVotes = null, yesVotes = null } = props; + + return ( + + { !canVote && + + + { noVotes } + + { question } + + { yesVotes } + + } + { canVote && + + { question } + + vote(VALUE_KEY_DISLIKE) }> + + + vote(VALUE_KEY_LIKE) }> + + + + } + + ); +} diff --git a/src/components/room/widgets/word-quiz/WordQuizVoteView.tsx b/src/components/room/widgets/word-quiz/WordQuizVoteView.tsx new file mode 100644 index 0000000..8437d76 --- /dev/null +++ b/src/components/room/widgets/word-quiz/WordQuizVoteView.tsx @@ -0,0 +1,24 @@ +import { RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { VALUE_KEY_DISLIKE } from '../../../../api'; +import { Base, BaseProps, Flex } from '../../../../common'; +import { ObjectLocationView } from '../object-location/ObjectLocationView'; + +interface WordQuizVoteViewProps extends BaseProps +{ + userIndex: number; + vote: string; +} + +export const WordQuizVoteView: FC = props => +{ + const { userIndex = null, vote = null, ...rest } = props; + + return ( + + + + + + ); +} diff --git a/src/components/room/widgets/word-quiz/WordQuizWidgetView.tsx b/src/components/room/widgets/word-quiz/WordQuizWidgetView.tsx new file mode 100644 index 0000000..a04cfd8 --- /dev/null +++ b/src/components/room/widgets/word-quiz/WordQuizWidgetView.tsx @@ -0,0 +1,19 @@ +import { FC } from 'react'; +import { VALUE_KEY_DISLIKE, VALUE_KEY_LIKE } from '../../../../api'; +import { useWordQuizWidget } from '../../../../hooks'; +import { WordQuizQuestionView } from './WordQuizQuestionView'; +import { WordQuizVoteView } from './WordQuizVoteView'; + +export const WordQuizWidgetView: FC<{}> = props => +{ + const { question = null, answerSent = false, answerCounts = null, userAnswers = null, vote = null } = useWordQuizWidget(); + + return ( + <> + { question && + } + { userAnswers && + Array.from(userAnswers.entries()).map(([ key, value ], index) => ) } + + ); +} diff --git a/src/components/toolbar/ToolbarMeView.tsx b/src/components/toolbar/ToolbarMeView.tsx new file mode 100644 index 0000000..d0329cc --- /dev/null +++ b/src/components/toolbar/ToolbarMeView.tsx @@ -0,0 +1,52 @@ +import { CreateLinkEvent, GetRoomEngine, GetSessionDataManager, MouseEventType, RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { Dispatch, FC, PropsWithChildren, SetStateAction, useEffect, useRef } from 'react'; +import { DispatchUiEvent, GetConfigurationValue, GetRoomSession, GetUserProfile } from '../../api'; +import { Base, Flex, LayoutItemCountView } from '../../common'; +import { GuideToolEvent } from '../../events'; + +interface ToolbarMeViewProps +{ + useGuideTool: boolean; + unseenAchievementCount: number; + setMeExpanded: Dispatch>; +} + +export const ToolbarMeView: FC> = props => +{ + const { useGuideTool = false, unseenAchievementCount = 0, setMeExpanded = null, children = null, ...rest } = props; + const elementRef = useRef(); + + useEffect(() => + { + const roomSession = GetRoomSession(); + + if(!roomSession) return; + + GetRoomEngine().selectRoomObject(roomSession.roomId, roomSession.ownRoomIndex, RoomObjectCategory.UNIT); + }, []); + + useEffect(() => + { + const onClick = (event: MouseEvent) => setMeExpanded(false); + + document.addEventListener('click', onClick); + + return () => document.removeEventListener(MouseEventType.MOUSE_CLICK, onClick); + }, [ setMeExpanded ]); + + return ( + + { (GetConfigurationValue('guides.enabled') && useGuideTool) && + DispatchUiEvent(new GuideToolEvent(GuideToolEvent.TOGGLE_GUIDE_TOOL)) } /> } + CreateLinkEvent('achievements/toggle') }> + { (unseenAchievementCount > 0) && + } + + GetUserProfile(GetSessionDataManager().userId) } /> + CreateLinkEvent('navigator/search/myworld_view') } /> + CreateLinkEvent('avatar-editor/toggle') } /> + CreateLinkEvent('user-settings/toggle') } /> + { children } + + ); +} diff --git a/src/components/toolbar/ToolbarView.scss b/src/components/toolbar/ToolbarView.scss new file mode 100644 index 0000000..6e7f651 --- /dev/null +++ b/src/components/toolbar/ToolbarView.scss @@ -0,0 +1,81 @@ +.nitro-toolbar { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: $toolbar-height; + z-index: $toolbar-zindex; + pointer-events: all; + background: rgba($dark, 0.95); + box-shadow: inset 0px 5px lighten(rgba($dark, 0.6), 2.5), + inset 0 -4px darken(rgba($dark, 0.6), 4); + + .navigation-item { + position: relative; + + &.item-avatar { + width: 50px; + height: 45px; + overflow: hidden; + + .avatar-image { + margin-left: -5px; + margin-top: 25px; + } + } + + &:hover { + -webkit-transform: translate(-1px, -1px); + transform: translate(-1px, -1px); + filter: drop-shadow(2px 2px 0 rgba($black, 0.8)); + } + + &.active, + &:active { + -webkit-transform: unset; + transform: unset; + filter: none; + } + } + + #toolbar-chat-input-container { + + @include media-breakpoint-down(sm) { + width: 0px; + height: 0px; + } + } +} + +.nitro-toolbar-me { + position: absolute; + bottom: 60px; + left: 15px; + z-index: $toolbar-memenu-zindex; + background: rgba(20, 20, 20, .95); + border: 1px solid #101010; + box-shadow: inset 2px 2px rgba(255, 255, 255, .1), inset -2px -2px rgba(255, 255, 255, .1); + border-radius: $border-radius; + + .navigation-item { + transition: filter .2s ease-out; + filter: grayscale(1); + + &:hover { + filter: grayscale(0) drop-shadow(2px 2px 0 rgba($black, 0.8)); + } + } +} + +.toolbar-icon-animation { + position: absolute; + object-fit: cover; + height: auto; + width: auto; + max-width: 120px; + max-height: 150px; + z-index: 500; + filter: drop-shadow(2px 1px 0 rgba($white, 1)) + drop-shadow(-2px 1px 0 rgba($white, 1)) + drop-shadow(0 -2px 0 rgba($white, 1)); +} diff --git a/src/components/toolbar/ToolbarView.tsx b/src/components/toolbar/ToolbarView.tsx new file mode 100644 index 0000000..1fd6d2d --- /dev/null +++ b/src/components/toolbar/ToolbarView.tsx @@ -0,0 +1,111 @@ +import { CreateLinkEvent, Dispose, DropBounce, EaseOut, GetSessionDataManager, JumpBy, Motions, NitroToolbarAnimateIconEvent, PerkAllowancesMessageEvent, PerkEnum, Queue, Wait } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { GetConfigurationValue, MessengerIconState, OpenMessengerChat, VisitDesktop } from '../../api'; +import { Base, Flex, LayoutAvatarImageView, LayoutItemCountView, TransitionAnimation, TransitionAnimationTypes } from '../../common'; +import { useAchievements, useFriends, useInventoryUnseenTracker, useMessageEvent, useMessenger, useNitroEvent, useSessionInfo } from '../../hooks'; +import { ToolbarMeView } from './ToolbarMeView'; + +export const ToolbarView: FC<{ isInRoom: boolean }> = props => +{ + const { isInRoom } = props; + const [ isMeExpanded, setMeExpanded ] = useState(false); + const [ useGuideTool, setUseGuideTool ] = useState(false); + const { userFigure = null } = useSessionInfo(); + const { getFullCount = 0 } = useInventoryUnseenTracker(); + const { getTotalUnseen = 0 } = useAchievements(); + const { requests = [] } = useFriends(); + const { iconState = MessengerIconState.HIDDEN } = useMessenger(); + const isMod = GetSessionDataManager().isModerator; + + useMessageEvent(PerkAllowancesMessageEvent, event => + { + const parser = event.getParser(); + + setUseGuideTool(parser.isAllowed(PerkEnum.USE_GUIDE_TOOL)); + }); + + useNitroEvent(NitroToolbarAnimateIconEvent.ANIMATE_ICON, event => + { + const animationIconToToolbar = (iconName: string, image: HTMLImageElement, x: number, y: number) => + { + const target = (document.body.getElementsByClassName(iconName)[0] as HTMLElement); + + if(!target) return; + + image.className = 'toolbar-icon-animation'; + image.style.visibility = 'visible'; + image.style.left = (x + 'px'); + image.style.top = (y + 'px'); + + document.body.append(image); + + const targetBounds = target.getBoundingClientRect(); + const imageBounds = image.getBoundingClientRect(); + + const left = (imageBounds.x - targetBounds.x); + const top = (imageBounds.y - targetBounds.y); + const squared = Math.sqrt(((left * left) + (top * top))); + const wait = (500 - Math.abs(((((1 / squared) * 100) * 500) * 0.5))); + const height = 20; + + const motionName = (`ToolbarBouncing[${ iconName }]`); + + if(!Motions.getMotionByTag(motionName)) + { + Motions.runMotion(new Queue(new Wait((wait + 8)), new DropBounce(target, 400, 12))).tag = motionName; + } + + const motion = new Queue(new EaseOut(new JumpBy(image, wait, ((targetBounds.x - imageBounds.x) + height), (targetBounds.y - imageBounds.y), 100, 1), 1), new Dispose(image)); + + Motions.runMotion(motion); + } + + animationIconToToolbar('icon-inventory', event.image, event.x, event.y); + }); + + return ( + <> + + + + + + + setMeExpanded(!isMeExpanded) }> + + { (getTotalUnseen > 0) && + } + + { isInRoom && + VisitDesktop() } /> } + { !isInRoom && + CreateLinkEvent('navigator/goto/home') } /> } + CreateLinkEvent('navigator/toggle') } /> + { GetConfigurationValue('game.center.enabled') && CreateLinkEvent('games/toggle') } /> } + CreateLinkEvent('catalog/toggle') } /> + CreateLinkEvent('inventory/toggle') }> + { (getFullCount > 0) && + } + + { isInRoom && + CreateLinkEvent('camera/toggle') } /> } + { isMod && + CreateLinkEvent('mod-tools/toggle') } /> } + + + + + + CreateLinkEvent('friends/toggle') }> + { (requests.length > 0) && + } + + { ((iconState === MessengerIconState.SHOW) || (iconState === MessengerIconState.UNREAD)) && + OpenMessengerChat() } /> } + + + + + + ); +} diff --git a/src/components/user-profile/UserProfileVew.scss b/src/components/user-profile/UserProfileVew.scss new file mode 100644 index 0000000..edcfde6 --- /dev/null +++ b/src/components/user-profile/UserProfileVew.scss @@ -0,0 +1,67 @@ +.user-profile { + width: $user-profile-width; + height: $user-profile-height; + + .user-container { + border-right: 1px solid gray; + + .avatar-container { + width: 75px; + height: 120px; + } + } + + .rooms-button-container { + border-top: 1px solid gray; + border-bottom: 1px solid gray; + } + + .user-relationship { + height: 25px; + + .avatar-image-container { + width: 50px; + height: 50px; + + .avatar-image { + top: 20px; + right: -8pxpx; + } + } + } + + .user-relationship-count { + margin-top: 2px; + margin-left: 5px; + color: #939392 !important; + } + + .user-groups-container { + + .layout-grid-item { + width: 50px; + } + } + + .no-group-spritesheet { + background: transparent url('@/assets/images/groups/no-group-spritesheet.png') no-repeat; + + &.image-1 { + width: 95px; + height: 136px; + background-position: -3px -3px; + } + + &.image-2 { + width: 95px; + height: 136px; + background-position: -104px -3px; + } + + &.image-3 { + width: 95px; + height: 136px; + background-position: -205px -3px; + } + } +} diff --git a/src/components/user-profile/UserProfileView.tsx b/src/components/user-profile/UserProfileView.tsx new file mode 100644 index 0000000..64349b2 --- /dev/null +++ b/src/components/user-profile/UserProfileView.tsx @@ -0,0 +1,122 @@ +import { CreateLinkEvent, ExtendedProfileChangedMessageEvent, GetSessionDataManager, RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomEngineObjectEvent, RoomObjectCategory, RoomObjectType, UserCurrentBadgesComposer, UserCurrentBadgesEvent, UserProfileEvent, UserProfileParser, UserRelationshipsComposer } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { GetRoomSession, GetUserProfile, LocalizeText, SendMessageComposer } from '../../api'; +import { Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common'; +import { useMessageEvent, useNitroEvent } from '../../hooks'; +import { BadgesContainerView } from './views/BadgesContainerView'; +import { FriendsContainerView } from './views/FriendsContainerView'; +import { GroupsContainerView } from './views/GroupsContainerView'; +import { UserContainerView } from './views/UserContainerView'; + +export const UserProfileView: FC<{}> = props => +{ + const [ userProfile, setUserProfile ] = useState(null); + const [ userBadges, setUserBadges ] = useState([]); + const [ userRelationships, setUserRelationships ] = useState(null); + + const onClose = () => + { + setUserProfile(null); + setUserBadges([]); + setUserRelationships(null); + } + + const onLeaveGroup = () => + { + if(!userProfile || (userProfile.id !== GetSessionDataManager().userId)) return; + + GetUserProfile(userProfile.id); + } + + useMessageEvent(UserCurrentBadgesEvent, event => + { + const parser = event.getParser(); + + if(!userProfile || (parser.userId !== userProfile.id)) return; + + setUserBadges(parser.badges); + }); + + useMessageEvent(RelationshipStatusInfoEvent, event => + { + const parser = event.getParser(); + + if(!userProfile || (parser.userId !== userProfile.id)) return; + + setUserRelationships(parser); + }); + + useMessageEvent(UserProfileEvent, event => + { + const parser = event.getParser(); + + let isSameProfile = false; + + setUserProfile(prevValue => + { + if(prevValue && prevValue.id) isSameProfile = (prevValue.id === parser.id); + + return parser; + }); + + if(!isSameProfile) + { + setUserBadges([]); + setUserRelationships(null); + } + + SendMessageComposer(new UserCurrentBadgesComposer(parser.id)); + SendMessageComposer(new UserRelationshipsComposer(parser.id)); + }); + + useMessageEvent(ExtendedProfileChangedMessageEvent, event => + { + const parser = event.getParser(); + + if(parser.userId != userProfile?.id) return; + + GetUserProfile(parser.userId); + }); + + useNitroEvent(RoomEngineObjectEvent.SELECTED, event => + { + if(!userProfile) return; + + if(event.category !== RoomObjectCategory.UNIT) return; + + const userData = GetRoomSession().userDataManager.getUserDataByIndex(event.objectId); + + if(userData.type !== RoomObjectType.USER) return; + + GetUserProfile(userData.webID); + }); + + if(!userProfile) return null; + + return ( + + + + + + + + + + + + { userRelationships && + } + + + + CreateLinkEvent(`navigator/search/hotel_view/owner:${ userProfile.username }`) }> + + { LocalizeText('extendedprofile.rooms') } + + + + + + ) +} diff --git a/src/components/user-profile/views/BadgesContainerView.tsx b/src/components/user-profile/views/BadgesContainerView.tsx new file mode 100644 index 0000000..ca59fc2 --- /dev/null +++ b/src/components/user-profile/views/BadgesContainerView.tsx @@ -0,0 +1,25 @@ +import { FC } from 'react'; +import { Column, FlexProps, LayoutBadgeImageView } from '../../../common'; + +interface BadgesContainerViewProps extends FlexProps +{ + badges: string[]; +} + +export const BadgesContainerView: FC = props => +{ + const { badges = null, gap = 1, justifyContent = 'between', ...rest } = props; + + return ( + <> + { badges && (badges.length > 0) && badges.map((badge, index) => + { + return ( + + + + ); + }) } + + ); +} diff --git a/src/components/user-profile/views/FriendsContainerView.tsx b/src/components/user-profile/views/FriendsContainerView.tsx new file mode 100644 index 0000000..f5449d1 --- /dev/null +++ b/src/components/user-profile/views/FriendsContainerView.tsx @@ -0,0 +1,28 @@ +import { RelationshipStatusInfoMessageParser } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { LocalizeText } from '../../../api'; +import { Column, Text } from '../../../common'; +import { RelationshipsContainerView } from './RelationshipsContainerView'; + +interface FriendsContainerViewProps +{ + relationships: RelationshipStatusInfoMessageParser; + friendsCount: number; +} + +export const FriendsContainerView: FC = props => +{ + const { relationships = null, friendsCount = null } = props; + + return ( + + + { LocalizeText('extendedprofile.friends.count') } { friendsCount } + + { LocalizeText('extendedprofile.relstatus') } + + + + + ) +} diff --git a/src/components/user-profile/views/GroupsContainerView.tsx b/src/components/user-profile/views/GroupsContainerView.tsx new file mode 100644 index 0000000..bd598ee --- /dev/null +++ b/src/components/user-profile/views/GroupsContainerView.tsx @@ -0,0 +1,90 @@ +import { GroupInformationComposer, GroupInformationEvent, GroupInformationParser, HabboGroupEntryData } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { SendMessageComposer, ToggleFavoriteGroup } from '../../../api'; +import { AutoGrid, Base, Column, Flex, Grid, GridProps, LayoutBadgeImageView, LayoutGridItem } from '../../../common'; +import { useMessageEvent } from '../../../hooks'; +import { GroupInformationView } from '../../groups/views/GroupInformationView'; + +interface GroupsContainerViewProps extends GridProps +{ + itsMe: boolean; + groups: HabboGroupEntryData[]; + onLeaveGroup: () => void; +} + +export const GroupsContainerView: FC = props => +{ + const { itsMe = null, groups = null, onLeaveGroup = null, overflow = 'hidden', gap = 2, ...rest } = props; + const [ selectedGroupId, setSelectedGroupId ] = useState(null); + const [ groupInformation, setGroupInformation ] = useState(null); + + useMessageEvent(GroupInformationEvent, event => + { + const parser = event.getParser(); + + if(!selectedGroupId || (selectedGroupId !== parser.id) || parser.flag) return; + + setGroupInformation(parser); + }); + + useEffect(() => + { + if(!selectedGroupId) return; + + SendMessageComposer(new GroupInformationComposer(selectedGroupId, false)); + }, [ selectedGroupId ]); + + useEffect(() => + { + setGroupInformation(null); + + if(groups.length > 0) + { + setSelectedGroupId(prevValue => + { + if(prevValue === groups[0].groupId) + { + SendMessageComposer(new GroupInformationComposer(groups[0].groupId, false)); + } + + return groups[0].groupId; + }); + } + }, [ groups ]); + + if(!groups || !groups.length) + { + return ( + + + + + + + + ); + } + + return ( + + + + { groups.map((group, index) => + { + return ( + setSelectedGroupId(group.groupId) } className="p-1"> + { itsMe && + ToggleFavoriteGroup(group) } /> } + + + ) + }) } + + + + { groupInformation && + } + + + ); +} diff --git a/src/components/user-profile/views/RelationshipsContainerView.tsx b/src/components/user-profile/views/RelationshipsContainerView.tsx new file mode 100644 index 0000000..9b698ec --- /dev/null +++ b/src/components/user-profile/views/RelationshipsContainerView.tsx @@ -0,0 +1,62 @@ +import { RelationshipStatusEnum, RelationshipStatusInfoMessageParser } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { GetUserProfile, LocalizeText } from '../../../api'; +import { Column, Flex, LayoutAvatarImageView, Text } from '../../../common'; + +interface RelationshipsContainerViewProps +{ + relationships: RelationshipStatusInfoMessageParser; +} + +interface RelationshipsContainerRelationshipViewProps +{ + type: number; +} + +export const RelationshipsContainerView: FC = props => +{ + const { relationships = null } = props; + + const RelationshipComponent = ({ type }: RelationshipsContainerRelationshipViewProps) => + { + const relationshipInfo = (relationships && relationships.relationshipStatusMap.hasKey(type)) ? relationships.relationshipStatusMap.getValue(type) : null; + const relationshipName = RelationshipStatusEnum.RELATIONSHIP_NAMES[type].toLocaleLowerCase(); + + return ( + + + + + + + (relationshipInfo && (relationshipInfo.randomFriendId >= 1) && GetUserProfile(relationshipInfo.randomFriendId)) }> + { (!relationshipInfo || (relationshipInfo.friendCount === 0)) && + LocalizeText('extendedprofile.add.friends') } + { (relationshipInfo && (relationshipInfo.friendCount >= 1)) && + relationshipInfo.randomFriendName } + + { (relationshipInfo && (relationshipInfo.friendCount >= 1)) && + + + } + + + { (!relationshipInfo || (relationshipInfo.friendCount === 0)) && + LocalizeText('extendedprofile.no.friends.in.this.category') } + { (relationshipInfo && (relationshipInfo.friendCount > 1)) && + LocalizeText(`extendedprofile.relstatus.others.${ relationshipName }`, [ 'count' ], [ (relationshipInfo.friendCount - 1).toString() ]) } +   + + + + ); + } + + return ( + <> + + + + + ); +} diff --git a/src/components/user-profile/views/UserContainerView.tsx b/src/components/user-profile/views/UserContainerView.tsx new file mode 100644 index 0000000..a83fb96 --- /dev/null +++ b/src/components/user-profile/views/UserContainerView.tsx @@ -0,0 +1,74 @@ +import { GetSessionDataManager, RequestFriendComposer, UserProfileParser } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { FriendlyTime, LocalizeText, SendMessageComposer } from '../../../api'; +import { Column, Flex, LayoutAvatarImageView, Text } from '../../../common'; + +interface UserContainerViewProps +{ + userProfile: UserProfileParser; +} + +export const UserContainerView: FC = props => +{ + const { userProfile = null } = props; + const [ requestSent, setRequestSent ] = useState(userProfile.requestSent); + const isOwnProfile = (userProfile.id === GetSessionDataManager().userId); + const canSendFriendRequest = !requestSent && (!isOwnProfile && !userProfile.isMyFriend && !userProfile.requestSent); + + const addFriend = () => + { + setRequestSent(true); + + SendMessageComposer(new RequestFriendComposer(userProfile.username)); + } + + useEffect(() => + { + setRequestSent(userProfile.requestSent); + }, [ userProfile ]) + + return ( + + + + + + + { userProfile.username } + { userProfile.motto }  + + + + { LocalizeText('extendedprofile.created') } { userProfile.registration } + + + { LocalizeText('extendedprofile.last.login') } { FriendlyTime.format(userProfile.secondsSinceLastVisit, '.ago', 2) } + + + { LocalizeText('extendedprofile.achievementscore') } { userProfile.achievementPoints } + + + + { userProfile.isOnline && + } + { !userProfile.isOnline && + } + + { canSendFriendRequest && + { LocalizeText('extendedprofile.addasafriend') } } + { !canSendFriendRequest && + <> + + { isOwnProfile && + { LocalizeText('extendedprofile.me') } } + { userProfile.isMyFriend && + { LocalizeText('extendedprofile.friend') } } + { (requestSent || userProfile.requestSent) && + { LocalizeText('extendedprofile.friendrequestsent') } } + } + + + + + ) +} diff --git a/src/components/user-settings/UserSettingsView.tsx b/src/components/user-settings/UserSettingsView.tsx new file mode 100644 index 0000000..43a4f51 --- /dev/null +++ b/src/components/user-settings/UserSettingsView.tsx @@ -0,0 +1,187 @@ +import { AddLinkEventTracker, ILinkEventTracker, NitroSettingsEvent, RemoveLinkEventTracker, UserSettingsCameraFollowComposer, UserSettingsEvent, UserSettingsOldChatComposer, UserSettingsRoomInvitesComposer, UserSettingsSoundComposer } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { FaVolumeDown, FaVolumeMute, FaVolumeUp } from 'react-icons/fa'; +import { DispatchMainEvent, DispatchUiEvent, LocalizeText, SendMessageComposer } from '../../api'; +import { Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text, classNames } from '../../common'; +import { useCatalogPlaceMultipleItems, useCatalogSkipPurchaseConfirmation, useMessageEvent } from '../../hooks'; + +export const UserSettingsView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ userSettings, setUserSettings ] = useState(null); + const [ catalogPlaceMultipleObjects, setCatalogPlaceMultipleObjects ] = useCatalogPlaceMultipleItems(); + const [ catalogSkipPurchaseConfirmation, setCatalogSkipPurchaseConfirmation ] = useCatalogSkipPurchaseConfirmation(); + + const processAction = (type: string, value?: boolean | number | string) => + { + let doUpdate = true; + + const clone = userSettings.clone(); + + switch(type) + { + case 'close_view': + setIsVisible(false); + doUpdate = false; + return; + case 'oldchat': + clone.oldChat = value as boolean; + SendMessageComposer(new UserSettingsOldChatComposer(clone.oldChat)); + break; + case 'room_invites': + clone.roomInvites = value as boolean; + SendMessageComposer(new UserSettingsRoomInvitesComposer(clone.roomInvites)); + break; + case 'camera_follow': + clone.cameraFollow = value as boolean; + SendMessageComposer(new UserSettingsCameraFollowComposer(clone.cameraFollow)); + break; + case 'system_volume': + clone.volumeSystem = value as number; + clone.volumeSystem = Math.max(0, clone.volumeSystem); + clone.volumeSystem = Math.min(100, clone.volumeSystem); + break; + case 'furni_volume': + clone.volumeFurni = value as number; + clone.volumeFurni = Math.max(0, clone.volumeFurni); + clone.volumeFurni = Math.min(100, clone.volumeFurni); + break; + case 'trax_volume': + clone.volumeTrax = value as number; + clone.volumeTrax = Math.max(0, clone.volumeTrax); + clone.volumeTrax = Math.min(100, clone.volumeTrax); + break; + } + + if(doUpdate) setUserSettings(clone); + + DispatchMainEvent(clone) + } + + const saveRangeSlider = (type: string) => + { + switch(type) + { + case 'volume': + SendMessageComposer(new UserSettingsSoundComposer(Math.round(userSettings.volumeSystem), Math.round(userSettings.volumeFurni), Math.round(userSettings.volumeTrax))); + break; + } + } + + useMessageEvent(UserSettingsEvent, event => + { + const parser = event.getParser(); + const settingsEvent = new NitroSettingsEvent(); + + settingsEvent.volumeSystem = parser.volumeSystem; + settingsEvent.volumeFurni = parser.volumeFurni; + settingsEvent.volumeTrax = parser.volumeTrax; + settingsEvent.oldChat = parser.oldChat; + settingsEvent.roomInvites = parser.roomInvites; + settingsEvent.cameraFollow = parser.cameraFollow; + settingsEvent.flags = parser.flags; + settingsEvent.chatType = parser.chatType; + + setUserSettings(settingsEvent); + DispatchMainEvent(settingsEvent); + }); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'show': + setIsVisible(true); + return; + case 'hide': + setIsVisible(false); + return; + case 'toggle': + setIsVisible(prevValue => !prevValue); + return; + } + }, + eventUrlPrefix: 'user-settings/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + useEffect(() => + { + if(!userSettings) return; + + DispatchUiEvent(userSettings); + }, [ userSettings ]); + + if(!isVisible || !userSettings) return null; + + return ( + + processAction('close_view') } /> + + + + processAction('oldchat', event.target.checked) } /> + { LocalizeText('memenu.settings.chat.prefer.old.chat') } + + + processAction('room_invites', event.target.checked) } /> + { LocalizeText('memenu.settings.other.ignore.room.invites') } + + + processAction('camera_follow', event.target.checked) } /> + { LocalizeText('memenu.settings.other.disable.room.camera.follow') } + + + setCatalogPlaceMultipleObjects(event.target.checked) } /> + { LocalizeText('memenu.settings.other.place.multiple.objects') } + + + setCatalogSkipPurchaseConfirmation(event.target.checked) } /> + { LocalizeText('memenu.settings.other.skip.purchase.confirmation') } + + + + { LocalizeText('widget.memenu.settings.volume') } + + { LocalizeText('widget.memenu.settings.volume.ui') } + + { (userSettings.volumeSystem === 0) && = 50) && 'text-muted', 'fa-icon') } /> } + { (userSettings.volumeSystem > 0) && = 50) && 'text-muted', 'fa-icon') } /> } + processAction('system_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') }/> + + + + + { LocalizeText('widget.memenu.settings.volume.furni') } + + { (userSettings.volumeFurni === 0) && = 50) && 'text-muted', 'fa-icon') } /> } + { (userSettings.volumeFurni > 0) && = 50) && 'text-muted', 'fa-icon') } /> } + processAction('furni_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') }/> + + + + + { LocalizeText('widget.memenu.settings.volume.trax') } + + { (userSettings.volumeTrax === 0) && = 50) && 'text-muted', 'fa-icon') } /> } + { (userSettings.volumeTrax > 0) && = 50) && 'text-muted', 'fa-icon') } /> } + processAction('trax_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') }/> + + + + + + + ); +} diff --git a/src/components/wired/WiredView.scss b/src/components/wired/WiredView.scss new file mode 100644 index 0000000..06569ad --- /dev/null +++ b/src/components/wired/WiredView.scss @@ -0,0 +1,175 @@ +.nitro-wired { + width: 300px; + + .icon { + background-repeat: no-repeat; + background-position: center; + + &.icon-mv-1 { + width: 16px; + height: 9px; + background-image: url('@/assets/images/wired/icon_wired_around.png'); + } + &.icon-mv-2 { + width: 16px; + height: 9px; + background-image: url('@/assets/images/wired/icon_wired_up_down.png'); + } + &.icon-mv-3 { + width: 16px; + height: 9px; + background-image: url('@/assets/images/wired/icon_wired_left_right.png'); + } + &.icon-ne { + width: 16px; + height: 9px; + background-image: url('@/assets/images/wired/icon_wired_north_east.png'); + } + &.icon-se { + width: 16px; + height: 9px; + background-image: url('@/assets/images/wired/icon_wired_south_east.png'); + } + &.icon-sw { + width: 16px; + height: 9px; + background-image: url('@/assets/images/wired/icon_wired_south_west.png'); + } + &.icon-nw { + width: 16px; + height: 9px; + background-image: url('@/assets/images/wired/icon_wired_north_west.png'); + } + &.icon-rot-1 { + width: 16px; + height: 9px; + background-image: url('@/assets/images/wired/icon_wired_rotate_clockwise.png'); + } + &.icon-rot-2 { + width: 16px; + height: 9px; + background-image: url('@/assets/images/wired/icon_wired_rotate_counter_clockwise.png'); + } + } + + .nitro-wired-header { + color: #000; + margin-bottom:3px; + + .nitro-wired-title, .nitro-wired-close { + border:1px solid rgba($black,.8); + background-image: linear-gradient(45deg, #00d9cb 25%, #00bdb0 25%, #00bdb0 50%, #00d9cb 50%, #00d9cb 75%, #00bdb0 75%, #00bdb0 100%); + background-size: 197.99px 197.99px; + animation: wiredSlider 3s linear infinite; + text-align: center; + box-shadow:inset 0 0 0 2px rgba($white,.6), 0 2px rgba($black,.4); + } + + .nitro-wired-title { + margin-right:3px; + } + + .nitro-wired-close { + min-width: 23px; + } + } + + &.nitro-wired-trigger { + background-color: #3b2516 !important; + border: 1px solid #000 !important; + box-shadow: inset 0px -2px #50321f, + inset 0px -3px #86583b, + inset 0 0 0 1px #86583b, + inset 0 0 0 3px #644029, + inset 0 0 0 4px rgba($black,.4) !important; + + .bg-light,.bg-primary { + background-color: transparent !important; + } + + .bg-dark { + background-color: #000 !important; + } + } + + &.nitro-wired-action { + background-color: #686868 !important; + border: 1px solid #000 !important; + box-shadow: inset 0px -2px #9d9d9d, + inset 0px -3px #c5c5c5, + inset 0 0 0 1px #c5c5c5, + inset 0 0 0 3px #9d9d9d, + inset 0 0 0 4px rgba($black,.4) !important; + + .bg-light,.bg-primary { + background-color: transparent !important; + } + + .bg-dark { + background-color: #000 !important; + } + + &::before, + &::after, + .content-area::before, + .content-area::after { + content: ''; + height: 6px; + width: 6px; + position: absolute; + background-image: url('@/assets/images/wired/card-action-corners.png'); + } + + &::before { + background-position: 0 0; + top: 0; + left: 0; + } + + &::after { + background-position: 6px 0; + top: 0; + right: 0; + } + + .content-area { + &::before { + background-position: 0 6px; + bottom: 0; + left: 0; + } + + &::after { + background-position: 6px 6px; + bottom: 0; + right: 0; + } + } + } + + &.nitro-wired-condition { + background-color: #cfd2dd !important; + border: 1px solid #000 !important; + box-shadow: inset 0 0 0 3px #efefef, inset 4px 4px #abaeb9 !important; + color: #000; + + .bg-light,.bg-primary { + background-color: transparent !important; + } + + .bg-dark { + background-color: #000 !important; + } + } +} + + +@keyframes wiredSlider { + 0% { + background-position: 0 0; + } + + 100% { + background-position: 0 -197.99px; + } +} diff --git a/src/components/wired/WiredView.tsx b/src/components/wired/WiredView.tsx new file mode 100644 index 0000000..cb3b4ba --- /dev/null +++ b/src/components/wired/WiredView.tsx @@ -0,0 +1,21 @@ +import { ConditionDefinition, TriggerDefinition, WiredActionDefinition } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { useWired } from '../../hooks'; +import { WiredActionLayoutView } from './views/actions/WiredActionLayoutView'; +import { WiredConditionLayoutView } from './views/conditions/WiredConditionLayoutView'; +import { WiredTriggerLayoutView } from './views/triggers/WiredTriggerLayoutView'; + +export const WiredView: FC<{}> = props => +{ + const { trigger = null } = useWired(); + + if(!trigger) return null; + + if(trigger instanceof WiredActionDefinition) return WiredActionLayoutView(trigger.code); + + if(trigger instanceof TriggerDefinition) return WiredTriggerLayoutView(trigger.code); + + if(trigger instanceof ConditionDefinition) return WiredConditionLayoutView(trigger.code); + + return null; +}; diff --git a/src/components/wired/views/WiredBaseView.tsx b/src/components/wired/views/WiredBaseView.tsx new file mode 100644 index 0000000..df5adc1 --- /dev/null +++ b/src/components/wired/views/WiredBaseView.tsx @@ -0,0 +1,114 @@ +import { GetSessionDataManager } from '@nitrots/nitro-renderer'; +import { FC, PropsWithChildren, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../api'; +import { Button, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; +import { useWired } from '../../../hooks'; +import { WiredFurniSelectorView } from './WiredFurniSelectorView'; + +export interface WiredBaseViewProps +{ + wiredType: string; + requiresFurni: number; + hasSpecialInput: boolean; + save: () => void; + validate?: () => boolean; +} + +export const WiredBaseView: FC> = props => +{ + const { wiredType = '', requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_NONE, save = null, validate = null, children = null, hasSpecialInput = false } = props; + const [ wiredName, setWiredName ] = useState(null); + const [ wiredDescription, setWiredDescription ] = useState(null); + const [ needsSave, setNeedsSave ] = useState(false); + const { trigger = null, setTrigger = null, setIntParams = null, setStringParam = null, setFurniIds = null, setAllowsFurni = null, saveWired = null } = useWired(); + + const onClose = () => setTrigger(null); + + const onSave = () => + { + if(validate && !validate()) return; + + if(save) save(); + + setNeedsSave(true); + } + + useEffect(() => + { + if(!needsSave) return; + + saveWired(); + + setNeedsSave(false); + }, [ needsSave, saveWired ]); + + useEffect(() => + { + if(!trigger) return; + + const spriteId = (trigger.spriteId || -1); + const furniData = GetSessionDataManager().getFloorItemData(spriteId); + + if(!furniData) + { + setWiredName(('NAME: ' + spriteId)); + setWiredDescription(('NAME: ' + spriteId)); + } + else + { + setWiredName(furniData.name); + setWiredDescription(furniData.description); + } + + if(hasSpecialInput) + { + setIntParams(trigger.intData); + setStringParam(trigger.stringData); + } + + if(requiresFurni > WiredFurniType.STUFF_SELECTION_OPTION_NONE) + { + setFurniIds(prevValue => + { + if(prevValue && prevValue.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prevValue); + + if(trigger.selectedItems && trigger.selectedItems.length) + { + WiredSelectionVisualizer.applySelectionShaderToFurni(trigger.selectedItems); + + return trigger.selectedItems; + } + + return []; + }); + } + + setAllowsFurni(requiresFurni); + }, [ trigger, hasSpecialInput, requiresFurni, setIntParams, setStringParam, setFurniIds, setAllowsFurni ]); + + return ( + + + + + + + { wiredName } + + { wiredDescription } + + { !!children &&
} + { children } + { (requiresFurni > WiredFurniType.STUFF_SELECTION_OPTION_NONE) && + <> +
+ + } + + + + +
+
+ ); +} diff --git a/src/components/wired/views/WiredFurniSelectorView.tsx b/src/components/wired/views/WiredFurniSelectorView.tsx new file mode 100644 index 0000000..9b38f97 --- /dev/null +++ b/src/components/wired/views/WiredFurniSelectorView.tsx @@ -0,0 +1,16 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../api'; +import { Column, Text } from '../../../common'; +import { useWired } from '../../../hooks'; + +export const WiredFurniSelectorView: FC<{}> = props => +{ + const { trigger = null, furniIds = [] } = useWired(); + + return ( + + { LocalizeText('wiredfurni.pickfurnis.caption', [ 'count', 'limit' ], [ furniIds.length.toString(), trigger.maximumItemSelectionCount.toString() ]) } + { LocalizeText('wiredfurni.pickfurnis.desc') } + + ); +} diff --git a/src/components/wired/views/actions/WiredActionBaseView.tsx b/src/components/wired/views/actions/WiredActionBaseView.tsx new file mode 100644 index 0000000..6c7a86e --- /dev/null +++ b/src/components/wired/views/actions/WiredActionBaseView.tsx @@ -0,0 +1,41 @@ +import { WiredActionDefinition } from '@nitrots/nitro-renderer'; +import { FC, PropsWithChildren, useEffect } from 'react'; +import ReactSlider from 'react-slider'; +import { GetWiredTimeLocale, LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredBaseView } from '../WiredBaseView'; + +export interface WiredActionBaseViewProps +{ + hasSpecialInput: boolean; + requiresFurni: number; + save: () => void; +} + +export const WiredActionBaseView: FC> = props => +{ + const { requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_NONE, save = null, hasSpecialInput = false, children = null } = props; + const { trigger = null, actionDelay = 0, setActionDelay = null } = useWired(); + + useEffect(() => + { + setActionDelay((trigger as WiredActionDefinition).delayInPulses); + }, [ trigger, setActionDelay ]); + + return ( + + { children } + { !!children &&
} + + { LocalizeText('wiredfurni.params.delay', [ 'seconds' ], [ GetWiredTimeLocale(actionDelay) ]) } + setActionDelay(event) } /> + +
+ ); +} diff --git a/src/components/wired/views/actions/WiredActionBotChangeFigureView.tsx b/src/components/wired/views/actions/WiredActionBotChangeFigureView.tsx new file mode 100644 index 0000000..783e00a --- /dev/null +++ b/src/components/wired/views/actions/WiredActionBotChangeFigureView.tsx @@ -0,0 +1,38 @@ +import { GetSessionDataManager } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WIRED_STRING_DELIMETER, WiredFurniType } from '../../../../api'; +import { Button, Column, Flex, LayoutAvatarImageView, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +const DEFAULT_FIGURE: string = 'hd-180-1.ch-210-66.lg-270-82.sh-290-81'; + +export const WiredActionBotChangeFigureView: FC<{}> = props => +{ + const [ botName, setBotName ] = useState(''); + const [ figure, setFigure ] = useState(''); + const { trigger = null, setStringParam = null } = useWired(); + + const save = () => setStringParam((botName + WIRED_STRING_DELIMETER + figure)); + + useEffect(() => + { + const data = trigger.stringData.split(WIRED_STRING_DELIMETER); + + if(data.length > 0) setBotName(data[0]); + if(data.length > 1) setFigure(data[1].length > 0 ? data[1] : DEFAULT_FIGURE); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> + + + + + + + ); +} diff --git a/src/components/wired/views/actions/WiredActionBotFollowAvatarView.tsx b/src/components/wired/views/actions/WiredActionBotFollowAvatarView.tsx new file mode 100644 index 0000000..2acb5cc --- /dev/null +++ b/src/components/wired/views/actions/WiredActionBotFollowAvatarView.tsx @@ -0,0 +1,43 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Flex, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionBotFollowAvatarView: FC<{}> = props => +{ + const [ botName, setBotName ] = useState(''); + const [ followMode, setFollowMode ] = useState(-1); + const { trigger = null, setStringParam = null, setIntParams = null } = useWired(); + + const save = () => + { + setStringParam(botName); + setIntParams([ followMode ]); + } + + useEffect(() => + { + setBotName(trigger.stringData); + setFollowMode((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> + + + + setFollowMode(1) } /> + { LocalizeText('wiredfurni.params.start.following') } + + + setFollowMode(0) } /> + { LocalizeText('wiredfurni.params.stop.following') } + + + + ); +} diff --git a/src/components/wired/views/actions/WiredActionBotGiveHandItemView.tsx b/src/components/wired/views/actions/WiredActionBotGiveHandItemView.tsx new file mode 100644 index 0000000..c93d0de --- /dev/null +++ b/src/components/wired/views/actions/WiredActionBotGiveHandItemView.tsx @@ -0,0 +1,42 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +const ALLOWED_HAND_ITEM_IDS: number[] = [ 2, 5, 7, 8, 9, 10, 27 ]; + +export const WiredActionBotGiveHandItemView: FC<{}> = props => +{ + const [ botName, setBotName ] = useState(''); + const [ handItemId, setHandItemId ] = useState(-1); + const { trigger = null, setStringParam = null, setIntParams = null } = useWired(); + + const save = () => + { + setStringParam(botName); + setIntParams([ handItemId ]); + } + + useEffect(() => + { + setBotName(trigger.stringData); + setHandItemId((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> + + + { LocalizeText('wiredfurni.params.handitem') } + + + + ); +} diff --git a/src/components/wired/views/actions/WiredActionBotMoveView.tsx b/src/components/wired/views/actions/WiredActionBotMoveView.tsx new file mode 100644 index 0000000..d09a359 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionBotMoveView.tsx @@ -0,0 +1,27 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionBotMoveView: FC<{}> = props => +{ + const [ botName, setBotName ] = useState(''); + const { trigger = null, setStringParam = null } = useWired(); + + const save = () => setStringParam(botName); + + useEffect(() => + { + setBotName(trigger.stringData); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> + + + ); +} diff --git a/src/components/wired/views/actions/WiredActionBotTalkToAvatarView.tsx b/src/components/wired/views/actions/WiredActionBotTalkToAvatarView.tsx new file mode 100644 index 0000000..d32cc17 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionBotTalkToAvatarView.tsx @@ -0,0 +1,52 @@ +import { FC, useEffect, useState } from 'react'; +import { GetConfigurationValue, LocalizeText, WIRED_STRING_DELIMETER, WiredFurniType } from '../../../../api'; +import { Column, Flex, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionBotTalkToAvatarView: FC<{}> = props => +{ + const [ botName, setBotName ] = useState(''); + const [ message, setMessage ] = useState(''); + const [ talkMode, setTalkMode ] = useState(-1); + const { trigger = null, setStringParam = null, setIntParams = null } = useWired(); + + const save = () => + { + setStringParam(botName + WIRED_STRING_DELIMETER + message); + setIntParams([ talkMode ]); + } + + useEffect(() => + { + const data = trigger.stringData.split(WIRED_STRING_DELIMETER); + + if(data.length > 0) setBotName(data[0]); + if(data.length > 1) setMessage(data[1].length > 0 ? data[1] : ''); + + setTalkMode((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> + + + { LocalizeText('wiredfurni.params.message') } + ('wired.action.bot.talk.to.avatar.max.length', 64) } value={ message } onChange={ event => setMessage(event.target.value) } /> + + + + setTalkMode(0) } /> + { LocalizeText('wiredfurni.params.talk') } + + + setTalkMode(1) } /> + { LocalizeText('wiredfurni.params.whisper') } + + + + ); +} diff --git a/src/components/wired/views/actions/WiredActionBotTalkView.tsx b/src/components/wired/views/actions/WiredActionBotTalkView.tsx new file mode 100644 index 0000000..18e6467 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionBotTalkView.tsx @@ -0,0 +1,52 @@ +import { FC, useEffect, useState } from 'react'; +import { GetConfigurationValue, LocalizeText, WIRED_STRING_DELIMETER, WiredFurniType } from '../../../../api'; +import { Column, Flex, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionBotTalkView: FC<{}> = props => +{ + const [ botName, setBotName ] = useState(''); + const [ message, setMessage ] = useState(''); + const [ talkMode, setTalkMode ] = useState(-1); + const { trigger = null, setStringParam = null, setIntParams = null } = useWired(); + + const save = () => + { + setStringParam(botName + WIRED_STRING_DELIMETER + message); + setIntParams([ talkMode ]); + } + + useEffect(() => + { + const data = trigger.stringData.split(WIRED_STRING_DELIMETER); + + if(data.length > 0) setBotName(data[0]); + if(data.length > 1) setMessage(data[1].length > 0 ? data[1] : ''); + + setTalkMode((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> + + + { LocalizeText('wiredfurni.params.message') } + ('wired.action.bot.talk.max.length', 64) } value={ message } onChange={ event => setMessage(event.target.value) } /> + + + + setTalkMode(0) } /> + { LocalizeText('wiredfurni.params.talk') } + + + setTalkMode(1) } /> + { LocalizeText('wiredfurni.params.shout') } + + + + ); +} diff --git a/src/components/wired/views/actions/WiredActionBotTeleportView.tsx b/src/components/wired/views/actions/WiredActionBotTeleportView.tsx new file mode 100644 index 0000000..5d767b1 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionBotTeleportView.tsx @@ -0,0 +1,27 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionBotTeleportView: FC<{}> = props => +{ + const [ botName, setBotName ] = useState(''); + const { trigger = null, setStringParam = null } = useWired(); + + const save = () => setStringParam(botName); + + useEffect(() => + { + setBotName(trigger.stringData); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> + + + ); +} diff --git a/src/components/wired/views/actions/WiredActionCallAnotherStackView.tsx b/src/components/wired/views/actions/WiredActionCallAnotherStackView.tsx new file mode 100644 index 0000000..ee6d547 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionCallAnotherStackView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionCallAnotherStackView: FC<{}> = props => +{ + return ; +} diff --git a/src/components/wired/views/actions/WiredActionChaseView.tsx b/src/components/wired/views/actions/WiredActionChaseView.tsx new file mode 100644 index 0000000..c494503 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionChaseView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionChaseView: FC<{}> = props => +{ + return ; +} diff --git a/src/components/wired/views/actions/WiredActionChatView.tsx b/src/components/wired/views/actions/WiredActionChatView.tsx new file mode 100644 index 0000000..62d9519 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionChatView.tsx @@ -0,0 +1,27 @@ +import { FC, useEffect, useState } from 'react'; +import { GetConfigurationValue, LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionChatView: FC<{}> = props => +{ + const [ message, setMessage ] = useState(''); + const { trigger = null, setStringParam = null } = useWired(); + + const save = () => setStringParam(message); + + useEffect(() => + { + setMessage(trigger.stringData); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.message') } + setMessage(event.target.value) } maxLength={ GetConfigurationValue('wired.action.chat.max.length', 100) } /> + + + ); +} diff --git a/src/components/wired/views/actions/WiredActionFleeView.tsx b/src/components/wired/views/actions/WiredActionFleeView.tsx new file mode 100644 index 0000000..a80ab43 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionFleeView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionFleeView: FC<{}> = props => +{ + return ; +} diff --git a/src/components/wired/views/actions/WiredActionGiveRewardView.tsx b/src/components/wired/views/actions/WiredActionGiveRewardView.tsx new file mode 100644 index 0000000..a255fe3 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionGiveRewardView.tsx @@ -0,0 +1,160 @@ +import { FC, useEffect, useState } from 'react'; +import { FaPlus, FaTrash } from 'react-icons/fa'; +import ReactSlider from 'react-slider'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Button, Column, Flex, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionGiveRewardView: FC<{}> = props => +{ + const [ limitEnabled, setLimitEnabled ] = useState(false); + const [ rewardTime, setRewardTime ] = useState(1); + const [ uniqueRewards, setUniqueRewards ] = useState(false); + const [ rewardsLimit, setRewardsLimit ] = useState(1); + const [ limitationInterval, setLimitationInterval ] = useState(1); + const [ rewards, setRewards ] = useState<{ isBadge: boolean, itemCode: string, probability: number }[]>([]); + const { trigger = null, setIntParams = null, setStringParam = null } = useWired(); + + const addReward = () => setRewards(rewards => [ ...rewards, { isBadge: false, itemCode: '', probability: null } ]); + + const removeReward = (index: number) => + { + setRewards(prevValue => + { + const newValues = Array.from(prevValue); + + newValues.splice(index, 1); + + return newValues; + }); + } + + const updateReward = (index: number, isBadge: boolean, itemCode: string, probability: number) => + { + const rewardsClone = Array.from(rewards); + const reward = rewardsClone[index]; + + if(!reward) return; + + reward.isBadge = isBadge; + reward.itemCode = itemCode; + reward.probability = probability; + + setRewards(rewardsClone); + } + + const save = () => + { + let stringRewards = []; + + for(const reward of rewards) + { + if(!reward.itemCode) continue; + + const rewardsString = [ reward.isBadge ? '0' : '1', reward.itemCode, reward.probability.toString() ]; + stringRewards.push(rewardsString.join(',')); + } + + if(stringRewards.length > 0) + { + setStringParam(stringRewards.join(';')); + setIntParams([ rewardTime, uniqueRewards ? 1 : 0, rewardsLimit, limitationInterval ]); + } + } + + useEffect(() => + { + const readRewards: { isBadge: boolean, itemCode: string, probability: number }[] = []; + + if(trigger.stringData.length > 0 && trigger.stringData.includes(';')) + { + const splittedRewards = trigger.stringData.split(';'); + + for(const rawReward of splittedRewards) + { + const reward = rawReward.split(','); + + if(reward.length !== 3) continue; + + readRewards.push({ isBadge: reward[0] === '0', itemCode: reward[1], probability: Number(reward[2]) }); + } + } + + if(readRewards.length === 0) readRewards.push({ isBadge: false, itemCode: '', probability: null }); + + setRewardTime((trigger.intData.length > 0) ? trigger.intData[0] : 0); + setUniqueRewards((trigger.intData.length > 1) ? (trigger.intData[1] === 1) : false); + setRewardsLimit((trigger.intData.length > 2) ? trigger.intData[2] : 0); + setLimitationInterval((trigger.intData.length > 3) ? trigger.intData[3] : 0); + setLimitEnabled((trigger.intData.length > 3) ? trigger.intData[3] > 0 : false); + setRewards(readRewards); + }, [ trigger ]); + + return ( + + + setLimitEnabled(event.target.checked) } /> + { LocalizeText('wiredfurni.params.prizelimit', [ 'amount' ], [ limitEnabled ? rewardsLimit.toString() : '' ]) } + + { !limitEnabled && + + Reward limit not set. Make sure rewards are badges or non-tradeable items. + } + { limitEnabled && + setRewardsLimit(event) } /> } +
+ + How often can a user be rewarded? + + + { (rewardTime > 0) && setLimitationInterval(Number(event.target.value)) } /> } + + +
+ + setUniqueRewards(e.target.checked) } /> + Unique rewards + + + If checked each reward will be given once to each user. This will disable the probabilities option. + +
+ + Rewards + + + + { rewards && rewards.map((reward, index) => + { + return ( + + + updateReward(index, e.target.checked, reward.itemCode, reward.probability) } /> + Badge? + + updateReward(index, reward.isBadge, e.target.value, reward.probability) } placeholder="Item Code" /> + updateReward(index, reward.isBadge, reward.itemCode, Number(e.target.value)) } placeholder="Probability" /> + { (index > 0) && + } + + ) + }) } + +
+ ); +} diff --git a/src/components/wired/views/actions/WiredActionGiveScoreToPredefinedTeamView.tsx b/src/components/wired/views/actions/WiredActionGiveScoreToPredefinedTeamView.tsx new file mode 100644 index 0000000..43ab240 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionGiveScoreToPredefinedTeamView.tsx @@ -0,0 +1,67 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Flex, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionGiveScoreToPredefinedTeamView: FC<{}> = props => +{ + const [ points, setPoints ] = useState(1); + const [ time, setTime ] = useState(1); + const [ selectedTeam, setSelectedTeam ] = useState(1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ points, time, selectedTeam ]); + + useEffect(() => + { + if(trigger.intData.length >= 2) + { + setPoints(trigger.intData[0]); + setTime(trigger.intData[1]); + setSelectedTeam(trigger.intData[2]); + } + else + { + setPoints(1); + setTime(1); + setSelectedTeam(1); + } + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.setpoints', [ 'points' ], [ points.toString() ]) } + setPoints(event) } /> + + + { LocalizeText('wiredfurni.params.settimesingame', [ 'times' ], [ time.toString() ]) } + setTime(event) } /> + + + { LocalizeText('wiredfurni.params.team') } + { [ 1, 2, 3, 4 ].map(value => + { + return ( + + setSelectedTeam(value) } /> + { LocalizeText('wiredfurni.params.team.' + value) } + + ); + }) } + + + ); +} diff --git a/src/components/wired/views/actions/WiredActionGiveScoreView.tsx b/src/components/wired/views/actions/WiredActionGiveScoreView.tsx new file mode 100644 index 0000000..28bfb0b --- /dev/null +++ b/src/components/wired/views/actions/WiredActionGiveScoreView.tsx @@ -0,0 +1,52 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionGiveScoreView: FC<{}> = props => +{ + const [ points, setPoints ] = useState(1); + const [ time, setTime ] = useState(1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ points, time ]); + + useEffect(() => + { + if(trigger.intData.length >= 2) + { + setPoints(trigger.intData[0]); + setTime(trigger.intData[1]); + } + else + { + setPoints(1); + setTime(1); + } + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.setpoints', [ 'points' ], [ points.toString() ]) } + setPoints(event) } /> + + + { LocalizeText('wiredfurni.params.settimesingame', [ 'times' ], [ time.toString() ]) } + setTime(event) } /> + + + ); +} diff --git a/src/components/wired/views/actions/WiredActionJoinTeamView.tsx b/src/components/wired/views/actions/WiredActionJoinTeamView.tsx new file mode 100644 index 0000000..7580564 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionJoinTeamView.tsx @@ -0,0 +1,35 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Flex, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionJoinTeamView: FC<{}> = props => +{ + const [ selectedTeam, setSelectedTeam ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ selectedTeam ]); + + useEffect(() => + { + setSelectedTeam((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.team') } + { [ 1, 2, 3, 4 ].map(team => + { + return ( + + setSelectedTeam(team) } /> + { LocalizeText(`wiredfurni.params.team.${ team }`) } + + ) + }) } + + + ); +} diff --git a/src/components/wired/views/actions/WiredActionKickFromRoomView.tsx b/src/components/wired/views/actions/WiredActionKickFromRoomView.tsx new file mode 100644 index 0000000..ba6033f --- /dev/null +++ b/src/components/wired/views/actions/WiredActionKickFromRoomView.tsx @@ -0,0 +1,27 @@ +import { FC, useEffect, useState } from 'react'; +import { GetConfigurationValue, LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionKickFromRoomView: FC<{}> = props => +{ + const [ message, setMessage ] = useState(''); + const { trigger = null, setStringParam = null } = useWired(); + + const save = () => setStringParam(message); + + useEffect(() => + { + setMessage(trigger.stringData); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.message') } + setMessage(event.target.value) } maxLength={ GetConfigurationValue('wired.action.kick.from.room.max.length', 100) } /> + + + ); +} diff --git a/src/components/wired/views/actions/WiredActionLayoutView.tsx b/src/components/wired/views/actions/WiredActionLayoutView.tsx new file mode 100644 index 0000000..f43666a --- /dev/null +++ b/src/components/wired/views/actions/WiredActionLayoutView.tsx @@ -0,0 +1,85 @@ +import { WiredActionLayoutCode } from '../../../../api'; +import { WiredActionBotChangeFigureView } from './WiredActionBotChangeFigureView'; +import { WiredActionBotFollowAvatarView } from './WiredActionBotFollowAvatarView'; +import { WiredActionBotGiveHandItemView } from './WiredActionBotGiveHandItemView'; +import { WiredActionBotMoveView } from './WiredActionBotMoveView'; +import { WiredActionBotTalkToAvatarView } from './WiredActionBotTalkToAvatarView'; +import { WiredActionBotTalkView } from './WiredActionBotTalkView'; +import { WiredActionBotTeleportView } from './WiredActionBotTeleportView'; +import { WiredActionCallAnotherStackView } from './WiredActionCallAnotherStackView'; +import { WiredActionChaseView } from './WiredActionChaseView'; +import { WiredActionChatView } from './WiredActionChatView'; +import { WiredActionFleeView } from './WiredActionFleeView'; +import { WiredActionGiveRewardView } from './WiredActionGiveRewardView'; +import { WiredActionGiveScoreToPredefinedTeamView } from './WiredActionGiveScoreToPredefinedTeamView'; +import { WiredActionGiveScoreView } from './WiredActionGiveScoreView'; +import { WiredActionJoinTeamView } from './WiredActionJoinTeamView'; +import { WiredActionKickFromRoomView } from './WiredActionKickFromRoomView'; +import { WiredActionLeaveTeamView } from './WiredActionLeaveTeamView'; +import { WiredActionMoveAndRotateFurniView } from './WiredActionMoveAndRotateFurniView'; +import { WiredActionMoveFurniToView } from './WiredActionMoveFurniToView'; +import { WiredActionMoveFurniView } from './WiredActionMoveFurniView'; +import { WiredActionMuteUserView } from './WiredActionMuteUserView'; +import { WiredActionResetView } from './WiredActionResetView'; +import { WiredActionSetFurniStateToView } from './WiredActionSetFurniStateToView'; +import { WiredActionTeleportView } from './WiredActionTeleportView'; +import { WiredActionToggleFurniStateView } from './WiredActionToggleFurniStateView'; + +export const WiredActionLayoutView = (code: number) => +{ + switch(code) + { + case WiredActionLayoutCode.BOT_CHANGE_FIGURE: + return ; + case WiredActionLayoutCode.BOT_FOLLOW_AVATAR: + return ; + case WiredActionLayoutCode.BOT_GIVE_HAND_ITEM: + return ; + case WiredActionLayoutCode.BOT_MOVE: + return ; + case WiredActionLayoutCode.BOT_TALK: + return ; + case WiredActionLayoutCode.BOT_TALK_DIRECT_TO_AVTR: + return ; + case WiredActionLayoutCode.BOT_TELEPORT: + return ; + case WiredActionLayoutCode.CALL_ANOTHER_STACK: + return ; + case WiredActionLayoutCode.CHASE: + return ; + case WiredActionLayoutCode.CHAT: + return ; + case WiredActionLayoutCode.FLEE: + return ; + case WiredActionLayoutCode.GIVE_REWARD: + return ; + case WiredActionLayoutCode.GIVE_SCORE: + return ; + case WiredActionLayoutCode.GIVE_SCORE_TO_PREDEFINED_TEAM: + return ; + case WiredActionLayoutCode.JOIN_TEAM: + return ; + case WiredActionLayoutCode.KICK_FROM_ROOM: + return ; + case WiredActionLayoutCode.LEAVE_TEAM: + return ; + case WiredActionLayoutCode.MOVE_FURNI: + return ; + case WiredActionLayoutCode.MOVE_AND_ROTATE_FURNI: + return ; + case WiredActionLayoutCode.MOVE_FURNI_TO: + return ; + case WiredActionLayoutCode.MUTE_USER: + return ; + case WiredActionLayoutCode.RESET: + return ; + case WiredActionLayoutCode.SET_FURNI_STATE: + return ; + case WiredActionLayoutCode.TELEPORT: + return ; + case WiredActionLayoutCode.TOGGLE_FURNI_STATE: + return ; + } + + return null; +} diff --git a/src/components/wired/views/actions/WiredActionLeaveTeamView.tsx b/src/components/wired/views/actions/WiredActionLeaveTeamView.tsx new file mode 100644 index 0000000..eaa834f --- /dev/null +++ b/src/components/wired/views/actions/WiredActionLeaveTeamView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionLeaveTeamView: FC<{}> = props => +{ + return ; +} diff --git a/src/components/wired/views/actions/WiredActionMoveAndRotateFurniView.tsx b/src/components/wired/views/actions/WiredActionMoveAndRotateFurniView.tsx new file mode 100644 index 0000000..62801af --- /dev/null +++ b/src/components/wired/views/actions/WiredActionMoveAndRotateFurniView.tsx @@ -0,0 +1,82 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Flex, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +const directionOptions: { value: number, icon: string }[] = [ + { + value: 0, + icon: 'ne' + }, + { + value: 2, + icon: 'se' + }, + { + value: 4, + icon: 'sw' + }, + { + value: 6, + icon: 'nw' + } +]; + +const rotationOptions: number[] = [ 0, 1, 2, 3, 4, 5, 6 ]; + +export const WiredActionMoveAndRotateFurniView: FC<{}> = props => +{ + const [ movement, setMovement ] = useState(-1); + const [ rotation, setRotation ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ movement, rotation ]); + + useEffect(() => + { + if(trigger.intData.length >= 2) + { + setMovement(trigger.intData[0]); + setRotation(trigger.intData[1]); + } + else + { + setMovement(-1); + setRotation(-1); + } + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.startdir') } + + { directionOptions.map(option => + { + return ( + + setMovement(option.value) } /> + + + + + ) + }) } + + + + { LocalizeText('wiredfurni.params.turn') } + { rotationOptions.map(option => + { + return ( + + setRotation(option) } /> + { LocalizeText(`wiredfurni.params.turn.${ option }`) } + + ) + }) } + + + ); +} diff --git a/src/components/wired/views/actions/WiredActionMoveFurniToView.tsx b/src/components/wired/views/actions/WiredActionMoveFurniToView.tsx new file mode 100644 index 0000000..56bdb6b --- /dev/null +++ b/src/components/wired/views/actions/WiredActionMoveFurniToView.tsx @@ -0,0 +1,76 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Flex, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +const directionOptions: { value: number, icon: string }[] = [ + { + value: 0, + icon: 'ne' + }, + { + value: 2, + icon: 'se' + }, + { + value: 4, + icon: 'sw' + }, + { + value: 6, + icon: 'nw' + } +]; + +export const WiredActionMoveFurniToView: FC<{}> = props => +{ + const [ spacing, setSpacing ] = useState(-1); + const [ movement, setMovement ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ movement, spacing ]); + + useEffect(() => + { + if(trigger.intData.length >= 2) + { + setSpacing(trigger.intData[1]); + setMovement(trigger.intData[0]); + } + else + { + setSpacing(-1); + setMovement(-1); + } + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.emptytiles', [ 'tiles' ], [ spacing.toString() ]) } + setSpacing(event) } /> + + + { LocalizeText('wiredfurni.params.startdir') } + + { directionOptions.map(value => + { + return ( + + setMovement(value.value) } /> + + + ) + }) } + + + + ); +} diff --git a/src/components/wired/views/actions/WiredActionMoveFurniView.tsx b/src/components/wired/views/actions/WiredActionMoveFurniView.tsx new file mode 100644 index 0000000..b5a0888 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionMoveFurniView.tsx @@ -0,0 +1,100 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Flex, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +const directionOptions: { value: number, icon: string }[] = [ + { + value: 4, + icon: 'ne' + }, + { + value: 5, + icon: 'se' + }, + { + value: 6, + icon: 'sw' + }, + { + value: 7, + icon: 'nw' + }, + { + value: 2, + icon: 'mv-2' + }, + { + value: 3, + icon: 'mv-3' + }, + { + value: 1, + icon: 'mv-1' + } +]; + +const rotationOptions: number[] = [ 0, 1, 2, 3 ]; + +export const WiredActionMoveFurniView: FC<{}> = props => +{ + const [ movement, setMovement ] = useState(-1); + const [ rotation, setRotation ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ movement, rotation ]); + + useEffect(() => + { + if(trigger.intData.length >= 2) + { + setMovement(trigger.intData[0]); + setRotation(trigger.intData[1]); + } + else + { + setMovement(-1); + setRotation(-1); + } + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.movefurni') } + + setMovement(0) } /> + { LocalizeText('wiredfurni.params.movefurni.0') } + + + { directionOptions.map(option => + { + return ( + + setMovement(option.value) } /> + + + ) + }) } +
+ + + + { LocalizeText('wiredfurni.params.rotatefurni') } + { rotationOptions.map(option => + { + return ( + + setRotation(option) } /> + + { [ 1, 2 ].includes(option) && } + { LocalizeText(`wiredfurni.params.rotatefurni.${ option }`) } + + + ) + }) } + + + ); +} diff --git a/src/components/wired/views/actions/WiredActionMuteUserView.tsx b/src/components/wired/views/actions/WiredActionMuteUserView.tsx new file mode 100644 index 0000000..15ecfd5 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionMuteUserView.tsx @@ -0,0 +1,43 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { GetConfigurationValue, LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionMuteUserView: FC<{}> = props => +{ + const [ time, setTime ] = useState(-1); + const [ message, setMessage ] = useState(''); + const { trigger = null, setIntParams = null, setStringParam = null } = useWired(); + + const save = () => + { + setIntParams([ time ]); + setStringParam(message); + } + + useEffect(() => + { + setTime((trigger.intData.length > 0) ? trigger.intData[0] : 0); + setMessage(trigger.stringData); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.length.minutes', [ 'minutes' ], [ time.toString() ]) } + setTime(event) } /> + + + { LocalizeText('wiredfurni.params.message') } + setMessage(event.target.value) } maxLength={ GetConfigurationValue('wired.action.mute.user.max.length', 100) } /> + + + ); +} diff --git a/src/components/wired/views/actions/WiredActionResetView.tsx b/src/components/wired/views/actions/WiredActionResetView.tsx new file mode 100644 index 0000000..9245c67 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionResetView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionResetView: FC<{}> = props => +{ + return ; +} diff --git a/src/components/wired/views/actions/WiredActionSetFurniStateToView.tsx b/src/components/wired/views/actions/WiredActionSetFurniStateToView.tsx new file mode 100644 index 0000000..587254c --- /dev/null +++ b/src/components/wired/views/actions/WiredActionSetFurniStateToView.tsx @@ -0,0 +1,42 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Flex, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionSetFurniStateToView: FC<{}> = props => +{ + const [ stateFlag, setStateFlag ] = useState(0); + const [ directionFlag, setDirectionFlag ] = useState(0); + const [ positionFlag, setPositionFlag ] = useState(0); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ stateFlag, directionFlag, positionFlag ]); + + useEffect(() => + { + setStateFlag(trigger.getBoolean(0) ? 1 : 0); + setDirectionFlag(trigger.getBoolean(1) ? 1 : 0); + setPositionFlag(trigger.getBoolean(2) ? 1 : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.conditions') } + + setStateFlag(event.target.checked ? 1 : 0) } /> + { LocalizeText('wiredfurni.params.condition.state') } + + + setDirectionFlag(event.target.checked ? 1 : 0) } /> + { LocalizeText('wiredfurni.params.condition.direction') } + + + setPositionFlag(event.target.checked ? 1 : 0) } /> + { LocalizeText('wiredfurni.params.condition.position') } + + + + ); +} diff --git a/src/components/wired/views/actions/WiredActionTeleportView.tsx b/src/components/wired/views/actions/WiredActionTeleportView.tsx new file mode 100644 index 0000000..652da61 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionTeleportView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionTeleportView: FC<{}> = props => +{ + return ; +} diff --git a/src/components/wired/views/actions/WiredActionToggleFurniStateView.tsx b/src/components/wired/views/actions/WiredActionToggleFurniStateView.tsx new file mode 100644 index 0000000..37b5d2e --- /dev/null +++ b/src/components/wired/views/actions/WiredActionToggleFurniStateView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionToggleFurniStateView: FC<{}> = props => +{ + return ; +} diff --git a/src/components/wired/views/conditions/WiredConditionActorHasHandItem.tsx b/src/components/wired/views/conditions/WiredConditionActorHasHandItem.tsx new file mode 100644 index 0000000..24e89ed --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionActorHasHandItem.tsx @@ -0,0 +1,34 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +const ALLOWED_HAND_ITEM_IDS: number[] = [ 2, 5, 7, 8, 9, 10, 27 ]; + +export const WiredConditionActorHasHandItemView: FC<{}> = props => +{ + const [ handItemId, setHandItemId ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ handItemId ]); + + useEffect(() => + { + setHandItemId((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.handitem') } + + + + ); +} diff --git a/src/components/wired/views/conditions/WiredConditionActorIsGroupMemberView.tsx b/src/components/wired/views/conditions/WiredConditionActorIsGroupMemberView.tsx new file mode 100644 index 0000000..e278eab --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionActorIsGroupMemberView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionActorIsGroupMemberView: FC<{}> = props => +{ + return ; +} diff --git a/src/components/wired/views/conditions/WiredConditionActorIsOnFurniView.tsx b/src/components/wired/views/conditions/WiredConditionActorIsOnFurniView.tsx new file mode 100644 index 0000000..10cddf5 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionActorIsOnFurniView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionActorIsOnFurniView: FC<{}> = props => +{ + return ; +} diff --git a/src/components/wired/views/conditions/WiredConditionActorIsTeamMemberView.tsx b/src/components/wired/views/conditions/WiredConditionActorIsTeamMemberView.tsx new file mode 100644 index 0000000..20db3ac --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionActorIsTeamMemberView.tsx @@ -0,0 +1,37 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Flex, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +const teamIds: number[] = [ 1, 2, 3, 4 ]; + +export const WiredConditionActorIsTeamMemberView: FC<{}> = props => +{ + const [ selectedTeam, setSelectedTeam ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ selectedTeam ]); + + useEffect(() => + { + setSelectedTeam((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.team') } + { teamIds.map(value => + { + return ( + + setSelectedTeam(value) } /> + { LocalizeText(`wiredfurni.params.team.${ value }`) } + + ) + }) } + + + ); +} diff --git a/src/components/wired/views/conditions/WiredConditionActorIsWearingBadgeView.tsx b/src/components/wired/views/conditions/WiredConditionActorIsWearingBadgeView.tsx new file mode 100644 index 0000000..01e6d3b --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionActorIsWearingBadgeView.tsx @@ -0,0 +1,27 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionActorIsWearingBadgeView: FC<{}> = props => +{ + const [ badge, setBadge ] = useState(''); + const { trigger = null, setStringParam = null } = useWired(); + + const save = () => setStringParam(badge); + + useEffect(() => + { + setBadge(trigger.stringData); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.badgecode') } + setBadge(event.target.value) } /> + + + ); +} diff --git a/src/components/wired/views/conditions/WiredConditionActorIsWearingEffectView.tsx b/src/components/wired/views/conditions/WiredConditionActorIsWearingEffectView.tsx new file mode 100644 index 0000000..00d7fa6 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionActorIsWearingEffectView.tsx @@ -0,0 +1,27 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionActorIsWearingEffectView: FC<{}> = props => +{ + const [ effect, setEffect ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ effect ]); + + useEffect(() => + { + setEffect(trigger?.intData[0] ?? 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.tooltip.effectid') } + setEffect(parseInt(event.target.value)) } /> + + + ); +} diff --git a/src/components/wired/views/conditions/WiredConditionBaseView.tsx b/src/components/wired/views/conditions/WiredConditionBaseView.tsx new file mode 100644 index 0000000..fc49215 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionBaseView.tsx @@ -0,0 +1,23 @@ +import { FC, PropsWithChildren } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredBaseView } from '../WiredBaseView'; + +export interface WiredConditionBaseViewProps +{ + hasSpecialInput: boolean; + requiresFurni: number; + save: () => void; +} + +export const WiredConditionBaseView: FC> = props => +{ + const { requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_NONE, save = null, hasSpecialInput = false, children = null } = props; + + const onSave = () => (save && save()); + + return ( + + { children } + + ); +} diff --git a/src/components/wired/views/conditions/WiredConditionDateRangeView.tsx b/src/components/wired/views/conditions/WiredConditionDateRangeView.tsx new file mode 100644 index 0000000..36832e1 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionDateRangeView.tsx @@ -0,0 +1,58 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredDateToString, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionDateRangeView: FC<{}> = props => +{ + const [ startDate, setStartDate ] = useState(''); + const [ endDate, setEndDate ] = useState(''); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => + { + let startDateMili = 0; + let endDateMili = 0; + + const startDateInstance = new Date(startDate); + const endDateInstance = new Date(endDate); + + if(startDateInstance && endDateInstance) + { + startDateMili = startDateInstance.getTime() / 1000; + endDateMili = endDateInstance.getTime() / 1000; + } + + setIntParams([ startDateMili, endDateMili ]); + } + + useEffect(() => + { + if(trigger.intData.length >= 2) + { + let startDate = new Date(); + let endDate = new Date(); + + if(trigger.intData[0] > 0) startDate = new Date((trigger.intData[0] * 1000)); + + if(trigger.intData[1] > 0) endDate = new Date((trigger.intData[1] * 1000)); + + setStartDate(WiredDateToString(startDate)); + setEndDate(WiredDateToString(endDate)); + } + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.startdate') } + setStartDate(e.target.value) } /> + + + { LocalizeText('wiredfurni.params.enddate') } + setEndDate(e.target.value) } /> + + + ); +} diff --git a/src/components/wired/views/conditions/WiredConditionFurniHasAvatarOnView.tsx b/src/components/wired/views/conditions/WiredConditionFurniHasAvatarOnView.tsx new file mode 100644 index 0000000..aa3ba14 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionFurniHasAvatarOnView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionFurniHasAvatarOnView: FC<{}> = props => +{ + return ; +} diff --git a/src/components/wired/views/conditions/WiredConditionFurniHasFurniOnView.tsx b/src/components/wired/views/conditions/WiredConditionFurniHasFurniOnView.tsx new file mode 100644 index 0000000..1a9d0ef --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionFurniHasFurniOnView.tsx @@ -0,0 +1,35 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Flex, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionFurniHasFurniOnView: FC<{}> = props => +{ + const [ requireAll, setRequireAll ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ requireAll ]); + + useEffect(() => + { + setRequireAll((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.requireall') } + { [ 0, 1 ].map(value => + { + return ( + + setRequireAll(value) } /> + { LocalizeText('wiredfurni.params.requireall.' + value) } + + ) + }) } + + + ); +} diff --git a/src/components/wired/views/conditions/WiredConditionFurniHasNotFurniOnView.tsx b/src/components/wired/views/conditions/WiredConditionFurniHasNotFurniOnView.tsx new file mode 100644 index 0000000..031ec08 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionFurniHasNotFurniOnView.tsx @@ -0,0 +1,35 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Flex, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionFurniHasNotFurniOnView: FC<{}> = props => +{ + const [ requireAll, setRequireAll ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ requireAll ]); + + useEffect(() => + { + setRequireAll((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.not_requireall') } + { [ 0, 1 ].map(value => + { + return ( + + setRequireAll(value) } /> + { LocalizeText(`wiredfurni.params.not_requireall.${ value }`) } + + ) + }) } + + + ); +} diff --git a/src/components/wired/views/conditions/WiredConditionFurniIsOfTypeView.tsx b/src/components/wired/views/conditions/WiredConditionFurniIsOfTypeView.tsx new file mode 100644 index 0000000..2b0ddf4 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionFurniIsOfTypeView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionFurniIsOfTypeView: FC<{}> = props => +{ + return ; +} diff --git a/src/components/wired/views/conditions/WiredConditionFurniMatchesSnapshotView.tsx b/src/components/wired/views/conditions/WiredConditionFurniMatchesSnapshotView.tsx new file mode 100644 index 0000000..47a555e --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionFurniMatchesSnapshotView.tsx @@ -0,0 +1,42 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Flex, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionFurniMatchesSnapshotView: FC<{}> = props => +{ + const [ stateFlag, setStateFlag ] = useState(0); + const [ directionFlag, setDirectionFlag ] = useState(0); + const [ positionFlag, setPositionFlag ] = useState(0); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ stateFlag, directionFlag, positionFlag ]); + + useEffect(() => + { + setStateFlag(trigger.getBoolean(0) ? 1 : 0); + setDirectionFlag(trigger.getBoolean(1) ? 1 : 0); + setPositionFlag(trigger.getBoolean(2) ? 1 : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.conditions') } + + setStateFlag(event.target.checked ? 1 : 0) } /> + { LocalizeText('wiredfurni.params.condition.state') } + + + setDirectionFlag(event.target.checked ? 1 : 0) } /> + { LocalizeText('wiredfurni.params.condition.direction') } + + + setPositionFlag(event.target.checked ? 1 : 0) } /> + { LocalizeText('wiredfurni.params.condition.position') } + + + + ); +} diff --git a/src/components/wired/views/conditions/WiredConditionLayoutView.tsx b/src/components/wired/views/conditions/WiredConditionLayoutView.tsx new file mode 100644 index 0000000..c7c49b2 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionLayoutView.tsx @@ -0,0 +1,64 @@ +import { WiredConditionlayout } from '../../../../api'; +import { WiredConditionActorHasHandItemView } from './WiredConditionActorHasHandItem'; +import { WiredConditionActorIsGroupMemberView } from './WiredConditionActorIsGroupMemberView'; +import { WiredConditionActorIsOnFurniView } from './WiredConditionActorIsOnFurniView'; +import { WiredConditionActorIsTeamMemberView } from './WiredConditionActorIsTeamMemberView'; +import { WiredConditionActorIsWearingBadgeView } from './WiredConditionActorIsWearingBadgeView'; +import { WiredConditionActorIsWearingEffectView } from './WiredConditionActorIsWearingEffectView'; +import { WiredConditionDateRangeView } from './WiredConditionDateRangeView'; +import { WiredConditionFurniHasAvatarOnView } from './WiredConditionFurniHasAvatarOnView'; +import { WiredConditionFurniHasFurniOnView } from './WiredConditionFurniHasFurniOnView'; +import { WiredConditionFurniHasNotFurniOnView } from './WiredConditionFurniHasNotFurniOnView'; +import { WiredConditionFurniIsOfTypeView } from './WiredConditionFurniIsOfTypeView'; +import { WiredConditionFurniMatchesSnapshotView } from './WiredConditionFurniMatchesSnapshotView'; +import { WiredConditionTimeElapsedLessView } from './WiredConditionTimeElapsedLessView'; +import { WiredConditionTimeElapsedMoreView } from './WiredConditionTimeElapsedMoreView'; +import { WiredConditionUserCountInRoomView } from './WiredConditionUserCountInRoomView'; + +export const WiredConditionLayoutView = (code: number) => +{ + switch(code) + { + case WiredConditionlayout.ACTOR_HAS_HANDITEM: + return ; + case WiredConditionlayout.ACTOR_IS_GROUP_MEMBER: + case WiredConditionlayout.NOT_ACTOR_IN_GROUP: + return ; + case WiredConditionlayout.ACTOR_IS_ON_FURNI: + case WiredConditionlayout.NOT_ACTOR_ON_FURNI: + return ; + case WiredConditionlayout.ACTOR_IS_IN_TEAM: + case WiredConditionlayout.NOT_ACTOR_IN_TEAM: + return ; + case WiredConditionlayout.ACTOR_IS_WEARING_BADGE: + case WiredConditionlayout.NOT_ACTOR_WEARS_BADGE: + return ; + case WiredConditionlayout.ACTOR_IS_WEARING_EFFECT: + case WiredConditionlayout.NOT_ACTOR_WEARING_EFFECT: + return ; + case WiredConditionlayout.DATE_RANGE_ACTIVE: + return ; + case WiredConditionlayout.FURNIS_HAVE_AVATARS: + case WiredConditionlayout.FURNI_NOT_HAVE_HABBO: + return ; + case WiredConditionlayout.HAS_STACKED_FURNIS: + return ; + case WiredConditionlayout.NOT_HAS_STACKED_FURNIS: + return ; + case WiredConditionlayout.STUFF_TYPE_MATCHES: + case WiredConditionlayout.NOT_FURNI_IS_OF_TYPE: + return ; + case WiredConditionlayout.STATES_MATCH: + case WiredConditionlayout.NOT_STATES_MATCH: + return ; + case WiredConditionlayout.TIME_ELAPSED_LESS: + return ; + case WiredConditionlayout.TIME_ELAPSED_MORE: + return ; + case WiredConditionlayout.USER_COUNT_IN: + case WiredConditionlayout.NOT_USER_COUNT_IN: + return ; + } + + return null; +} diff --git a/src/components/wired/views/conditions/WiredConditionTimeElapsedLessView.tsx b/src/components/wired/views/conditions/WiredConditionTimeElapsedLessView.tsx new file mode 100644 index 0000000..31d6d79 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionTimeElapsedLessView.tsx @@ -0,0 +1,33 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { GetWiredTimeLocale, LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionTimeElapsedLessView: FC<{}> = props => +{ + const [ time, setTime ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ time ]); + + useEffect(() => + { + setTime((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.allowbefore', [ 'seconds' ], [ GetWiredTimeLocale(time) ]) } + setTime(event) } /> + + + ); +} diff --git a/src/components/wired/views/conditions/WiredConditionTimeElapsedMoreView.tsx b/src/components/wired/views/conditions/WiredConditionTimeElapsedMoreView.tsx new file mode 100644 index 0000000..cb158e6 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionTimeElapsedMoreView.tsx @@ -0,0 +1,33 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { GetWiredTimeLocale, LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionTimeElapsedMoreView: FC<{}> = props => +{ + const [ time, setTime ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ time ]); + + useEffect(() => + { + setTime((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.allowafter', [ 'seconds' ], [ GetWiredTimeLocale(time) ]) } + setTime(event) } /> + + + ); +} diff --git a/src/components/wired/views/conditions/WiredConditionUserCountInRoomView.tsx b/src/components/wired/views/conditions/WiredConditionUserCountInRoomView.tsx new file mode 100644 index 0000000..8ef5914 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionUserCountInRoomView.tsx @@ -0,0 +1,52 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionUserCountInRoomView: FC<{}> = props => +{ + const [ min, setMin ] = useState(1); + const [ max, setMax ] = useState(0); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ min, max ]); + + useEffect(() => + { + if(trigger.intData.length >= 2) + { + setMin(trigger.intData[0]); + setMax(trigger.intData[1]); + } + else + { + setMin(1); + setMax(0); + } + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.usercountmin', [ 'value' ], [ min.toString() ]) } + setMin(event) } /> + + + { LocalizeText('wiredfurni.params.usercountmax', [ 'value' ], [ max.toString() ]) } + setMax(event) } /> + + + ); +} diff --git a/src/components/wired/views/triggers/WiredTriggerAvatarEnterRoomView.tsx b/src/components/wired/views/triggers/WiredTriggerAvatarEnterRoomView.tsx new file mode 100644 index 0000000..b14eafb --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerAvatarEnterRoomView.tsx @@ -0,0 +1,38 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Flex, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerAvatarEnterRoomView: FC<{}> = props => +{ + const [ username, setUsername ] = useState(''); + const [ avatarMode, setAvatarMode ] = useState(0); + const { trigger = null, setStringParam = null } = useWired(); + + const save = () => setStringParam((avatarMode === 1) ? username : ''); + + useEffect(() => + { + setUsername(trigger.stringData); + setAvatarMode(trigger.stringData ? 1 : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.picktriggerer') } + + setAvatarMode(0) } /> + { LocalizeText('wiredfurni.params.anyavatar') } + + + setAvatarMode(1) } /> + { LocalizeText('wiredfurni.params.certainavatar') } + + { (avatarMode === 1) && + setUsername(event.target.value) } /> } + + + ); +} diff --git a/src/components/wired/views/triggers/WiredTriggerAvatarSaysSomethingView.tsx b/src/components/wired/views/triggers/WiredTriggerAvatarSaysSomethingView.tsx new file mode 100644 index 0000000..4aaf8fe --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerAvatarSaysSomethingView.tsx @@ -0,0 +1,45 @@ +import { GetSessionDataManager } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Flex, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerAvatarSaysSomethingView: FC<{}> = props => +{ + const [ message, setMessage ] = useState(''); + const [ triggererAvatar, setTriggererAvatar ] = useState(-1); + const { trigger = null, setStringParam = null, setIntParams = null } = useWired(); + + const save = () => + { + setStringParam(message); + setIntParams([ triggererAvatar ]); + } + + useEffect(() => + { + setMessage(trigger.stringData); + setTriggererAvatar((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.whatissaid') } + setMessage(event.target.value) } /> + + + { LocalizeText('wiredfurni.params.picktriggerer') } + + setTriggererAvatar(0) } /> + { LocalizeText('wiredfurni.params.anyavatar') } + + + setTriggererAvatar(1) } /> + { GetSessionDataManager().userName } + + + + ); +} diff --git a/src/components/wired/views/triggers/WiredTriggerAvatarWalksOffFurniView.tsx b/src/components/wired/views/triggers/WiredTriggerAvatarWalksOffFurniView.tsx new file mode 100644 index 0000000..0b98a56 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerAvatarWalksOffFurniView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerAvatarWalksOffFurniView: FC<{}> = props => +{ + return ; +} diff --git a/src/components/wired/views/triggers/WiredTriggerAvatarWalksOnFurni.tsx b/src/components/wired/views/triggers/WiredTriggerAvatarWalksOnFurni.tsx new file mode 100644 index 0000000..25572e7 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerAvatarWalksOnFurni.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerAvatarWalksOnFurniView: FC<{}> = props => +{ + return ; +} diff --git a/src/components/wired/views/triggers/WiredTriggerBaseView.tsx b/src/components/wired/views/triggers/WiredTriggerBaseView.tsx new file mode 100644 index 0000000..f8f1575 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerBaseView.tsx @@ -0,0 +1,23 @@ +import { FC, PropsWithChildren } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredBaseView } from '../WiredBaseView'; + +export interface WiredTriggerBaseViewProps +{ + hasSpecialInput: boolean; + requiresFurni: number; + save: () => void; +} + +export const WiredTriggerBaseView: FC> = props => +{ + const { requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_NONE, save = null, hasSpecialInput = false, children = null } = props; + + const onSave = () => (save && save()); + + return ( + + { children } + + ); +} diff --git a/src/components/wired/views/triggers/WiredTriggerBotReachedAvatarView.tsx b/src/components/wired/views/triggers/WiredTriggerBotReachedAvatarView.tsx new file mode 100644 index 0000000..6984dc6 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerBotReachedAvatarView.tsx @@ -0,0 +1,27 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerBotReachedAvatarView: FC<{}> = props => +{ + const [ botName, setBotName ] = useState(''); + const { trigger = null, setStringParam = null } = useWired(); + + const save = () => setStringParam(botName); + + useEffect(() => + { + setBotName(trigger.stringData); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> + + + ); +} diff --git a/src/components/wired/views/triggers/WiredTriggerBotReachedStuffView.tsx b/src/components/wired/views/triggers/WiredTriggerBotReachedStuffView.tsx new file mode 100644 index 0000000..9cde173 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerBotReachedStuffView.tsx @@ -0,0 +1,27 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerBotReachedStuffView: FC<{}> = props => +{ + const [ botName, setBotName ] = useState(''); + const { trigger = null, setStringParam = null } = useWired(); + + const save = () => setStringParam(botName); + + useEffect(() => + { + setBotName(trigger.stringData); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> + + + ); +} diff --git a/src/components/wired/views/triggers/WiredTriggerCollisionView.tsx b/src/components/wired/views/triggers/WiredTriggerCollisionView.tsx new file mode 100644 index 0000000..be23a6b --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerCollisionView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerCollisionView: FC<{}> = props => +{ + return ; +} diff --git a/src/components/wired/views/triggers/WiredTriggerExecuteOnceView.tsx b/src/components/wired/views/triggers/WiredTriggerExecuteOnceView.tsx new file mode 100644 index 0000000..ea3d61b --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerExecuteOnceView.tsx @@ -0,0 +1,33 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { GetWiredTimeLocale, LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggeExecuteOnceView: FC<{}> = props => +{ + const [ time, setTime ] = useState(1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ time ]); + + useEffect(() => + { + setTime((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.settime', [ 'seconds' ], [ GetWiredTimeLocale(time) ]) } + setTime(event) } /> + + + ); +} diff --git a/src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyLongView.tsx b/src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyLongView.tsx new file mode 100644 index 0000000..5a698dd --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyLongView.tsx @@ -0,0 +1,33 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { FriendlyTime, LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggeExecutePeriodicallyLongView: FC<{}> = props => +{ + const [ time, setTime ] = useState(1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ time ]); + + useEffect(() => + { + setTime((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.setlongtime', [ 'time' ], [ FriendlyTime.format(time * 5).toString() ]) } + setTime(event) } /> + + + ); +} diff --git a/src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyView.tsx b/src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyView.tsx new file mode 100644 index 0000000..b77e642 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyView.tsx @@ -0,0 +1,33 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { GetWiredTimeLocale, LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggeExecutePeriodicallyView: FC<{}> = props => +{ + const [ time, setTime ] = useState(1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ time ]); + + useEffect(() => + { + setTime((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.settime', [ 'seconds' ], [ GetWiredTimeLocale(time) ]) } + setTime(event) } /> + + + ); +} diff --git a/src/components/wired/views/triggers/WiredTriggerGameEndsView.tsx b/src/components/wired/views/triggers/WiredTriggerGameEndsView.tsx new file mode 100644 index 0000000..c6f1c67 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerGameEndsView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerGameEndsView: FC<{}> = props => +{ + return ; +} diff --git a/src/components/wired/views/triggers/WiredTriggerGameStartsView.tsx b/src/components/wired/views/triggers/WiredTriggerGameStartsView.tsx new file mode 100644 index 0000000..6e43a2c --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerGameStartsView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerGameStartsView: FC<{}> = props => +{ + return ; +} diff --git a/src/components/wired/views/triggers/WiredTriggerLayoutView.tsx b/src/components/wired/views/triggers/WiredTriggerLayoutView.tsx new file mode 100644 index 0000000..e45b2ea --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerLayoutView.tsx @@ -0,0 +1,52 @@ +import { WiredTriggerLayout } from '../../../../api'; +import { WiredTriggerAvatarEnterRoomView } from './WiredTriggerAvatarEnterRoomView'; +import { WiredTriggerAvatarSaysSomethingView } from './WiredTriggerAvatarSaysSomethingView'; +import { WiredTriggerAvatarWalksOffFurniView } from './WiredTriggerAvatarWalksOffFurniView'; +import { WiredTriggerAvatarWalksOnFurniView } from './WiredTriggerAvatarWalksOnFurni'; +import { WiredTriggerBotReachedAvatarView } from './WiredTriggerBotReachedAvatarView'; +import { WiredTriggerBotReachedStuffView } from './WiredTriggerBotReachedStuffView'; +import { WiredTriggerCollisionView } from './WiredTriggerCollisionView'; +import { WiredTriggeExecuteOnceView } from './WiredTriggerExecuteOnceView'; +import { WiredTriggeExecutePeriodicallyLongView } from './WiredTriggerExecutePeriodicallyLongView'; +import { WiredTriggeExecutePeriodicallyView } from './WiredTriggerExecutePeriodicallyView'; +import { WiredTriggerGameEndsView } from './WiredTriggerGameEndsView'; +import { WiredTriggerGameStartsView } from './WiredTriggerGameStartsView'; +import { WiredTriggeScoreAchievedView } from './WiredTriggerScoreAchievedView'; +import { WiredTriggerToggleFurniView } from './WiredTriggerToggleFurniView'; + +export const WiredTriggerLayoutView = (code: number) => +{ + switch(code) + { + case WiredTriggerLayout.AVATAR_ENTERS_ROOM: + return ; + case WiredTriggerLayout.AVATAR_SAYS_SOMETHING: + return ; + case WiredTriggerLayout.AVATAR_WALKS_OFF_FURNI: + return ; + case WiredTriggerLayout.AVATAR_WALKS_ON_FURNI: + return ; + case WiredTriggerLayout.BOT_REACHED_AVATAR: + return ; + case WiredTriggerLayout.BOT_REACHED_STUFF: + return ; + case WiredTriggerLayout.COLLISION: + return ; + case WiredTriggerLayout.EXECUTE_ONCE: + return ; + case WiredTriggerLayout.EXECUTE_PERIODICALLY: + return ; + case WiredTriggerLayout.EXECUTE_PERIODICALLY_LONG: + return ; + case WiredTriggerLayout.GAME_ENDS: + return ; + case WiredTriggerLayout.GAME_STARTS: + return ; + case WiredTriggerLayout.SCORE_ACHIEVED: + return ; + case WiredTriggerLayout.TOGGLE_FURNI: + return ; + } + + return null; +} diff --git a/src/components/wired/views/triggers/WiredTriggerScoreAchievedView.tsx b/src/components/wired/views/triggers/WiredTriggerScoreAchievedView.tsx new file mode 100644 index 0000000..883503b --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerScoreAchievedView.tsx @@ -0,0 +1,33 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Column, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggeScoreAchievedView: FC<{}> = props => +{ + const [ points, setPoints ] = useState(1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ points ]); + + useEffect(() => + { + setPoints((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + + + { LocalizeText('wiredfurni.params.setscore', [ 'points' ], [ points.toString() ]) } + setPoints(event) } /> + + + ); +} diff --git a/src/components/wired/views/triggers/WiredTriggerToggleFurniView.tsx b/src/components/wired/views/triggers/WiredTriggerToggleFurniView.tsx new file mode 100644 index 0000000..865006b --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerToggleFurniView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerToggleFurniView: FC<{}> = props => +{ + return ; +} diff --git a/src/events/catalog/CatalogEvent.ts b/src/events/catalog/CatalogEvent.ts new file mode 100644 index 0000000..893775a --- /dev/null +++ b/src/events/catalog/CatalogEvent.ts @@ -0,0 +1,14 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; + +export class CatalogEvent extends NitroEvent +{ + public static SHOW_CATALOG: string = 'CE_SHOW_CATALOG'; + public static HIDE_CATALOG: string = 'CE_HIDE_CATALOG'; + public static TOGGLE_CATALOG: string = 'CE_TOGGLE_CATALOG'; + public static SOLD_OUT: string = 'CE_SOLD_OUT'; + public static APPROVE_NAME_RESULT: string = 'CE_APPROVE_NAME_RESULT'; + public static PURCHASE_APPROVED: string = 'CE_PURCHASE_APPROVED'; + public static INIT_GIFT: string = 'CE_INIT_GIFT'; + public static CATALOG_RESET: string = 'CE_RESET'; + public static CATALOG_INVISIBLE_PAGE_VISITED: string = 'CE_CATALOG_INVISIBLE_PAGE_VISITED'; +} diff --git a/src/events/catalog/CatalogInitGiftEvent.ts b/src/events/catalog/CatalogInitGiftEvent.ts new file mode 100644 index 0000000..fd9c926 --- /dev/null +++ b/src/events/catalog/CatalogInitGiftEvent.ts @@ -0,0 +1,32 @@ +import { CatalogEvent } from './CatalogEvent'; + +export class CatalogInitGiftEvent extends CatalogEvent +{ + private _pageId: number; + private _offerId: number; + private _extraData: string; + + constructor(pageId: number, offerId: number, extraData: string) + { + super(CatalogEvent.INIT_GIFT); + + this._pageId = pageId; + this._offerId = offerId; + this._extraData = extraData; + } + + public get pageId(): number + { + return this._pageId; + } + + public get offerId(): number + { + return this._offerId; + } + + public get extraData(): string + { + return this._extraData; + } +} diff --git a/src/events/catalog/CatalogPostMarketplaceOfferEvent.ts b/src/events/catalog/CatalogPostMarketplaceOfferEvent.ts new file mode 100644 index 0000000..d54c6c4 --- /dev/null +++ b/src/events/catalog/CatalogPostMarketplaceOfferEvent.ts @@ -0,0 +1,20 @@ +import { CatalogEvent } from '.'; +import { FurnitureItem } from '../../api'; + +export class CatalogPostMarketplaceOfferEvent extends CatalogEvent +{ + public static readonly POST_MARKETPLACE = 'CE_POST_MARKETPLACE'; + + private _item: FurnitureItem; + + constructor(item: FurnitureItem) + { + super(CatalogPostMarketplaceOfferEvent.POST_MARKETPLACE); + this._item = item; + } + + public get item(): FurnitureItem + { + return this._item; + } +} diff --git a/src/events/catalog/CatalogPurchaseFailureEvent.ts b/src/events/catalog/CatalogPurchaseFailureEvent.ts new file mode 100644 index 0000000..59f0633 --- /dev/null +++ b/src/events/catalog/CatalogPurchaseFailureEvent.ts @@ -0,0 +1,20 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; + +export class CatalogPurchaseFailureEvent extends NitroEvent +{ + public static PURCHASE_FAILED: string = 'CPFE_PURCHASE_FAILED'; + + private _code: number; + + constructor(code: number) + { + super(CatalogPurchaseFailureEvent.PURCHASE_FAILED); + + this._code = code; + } + + public get code(): number + { + return this._code; + } +} diff --git a/src/events/catalog/CatalogPurchaseNotAllowedEvent.ts b/src/events/catalog/CatalogPurchaseNotAllowedEvent.ts new file mode 100644 index 0000000..78d012b --- /dev/null +++ b/src/events/catalog/CatalogPurchaseNotAllowedEvent.ts @@ -0,0 +1,20 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; + +export class CatalogPurchaseNotAllowedEvent extends NitroEvent +{ + public static NOT_ALLOWED: string = 'CPNAE_NOT_ALLOWED'; + + private _code: number; + + constructor(code: number) + { + super(CatalogPurchaseNotAllowedEvent.NOT_ALLOWED); + + this._code = code; + } + + public get code(): number + { + return this._code; + } +} diff --git a/src/events/catalog/CatalogPurchaseOverrideEvent.ts b/src/events/catalog/CatalogPurchaseOverrideEvent.ts new file mode 100644 index 0000000..7c0b8b5 --- /dev/null +++ b/src/events/catalog/CatalogPurchaseOverrideEvent.ts @@ -0,0 +1,19 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; +import { CatalogWidgetEvent } from './CatalogWidgetEvent'; + +export class CatalogPurchaseOverrideEvent extends NitroEvent +{ + private _callback: Function; + + constructor(callback: Function) + { + super(CatalogWidgetEvent.PURCHASE_OVERRIDE); + + this._callback = callback; + } + + public get callback(): Function + { + return this._callback; + } +} diff --git a/src/events/catalog/CatalogPurchaseSoldOutEvent.ts b/src/events/catalog/CatalogPurchaseSoldOutEvent.ts new file mode 100644 index 0000000..a3a43a3 --- /dev/null +++ b/src/events/catalog/CatalogPurchaseSoldOutEvent.ts @@ -0,0 +1,11 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; + +export class CatalogPurchaseSoldOutEvent extends NitroEvent +{ + public static SOLD_OUT: string = 'CPSOE_SOLD_OUT'; + + constructor() + { + super(CatalogPurchaseSoldOutEvent.SOLD_OUT); + } +} diff --git a/src/events/catalog/CatalogPurchasedEvent.ts b/src/events/catalog/CatalogPurchasedEvent.ts new file mode 100644 index 0000000..dedba46 --- /dev/null +++ b/src/events/catalog/CatalogPurchasedEvent.ts @@ -0,0 +1,20 @@ +import { NitroEvent, PurchaseOKMessageOfferData } from '@nitrots/nitro-renderer'; + +export class CatalogPurchasedEvent extends NitroEvent +{ + public static PURCHASE_SUCCESS: string = 'CPE_PURCHASE_SUCCESS'; + + private _purchase: PurchaseOKMessageOfferData; + + constructor(purchase: PurchaseOKMessageOfferData) + { + super(CatalogPurchasedEvent.PURCHASE_SUCCESS); + + this._purchase = purchase; + } + + public get purchase(): PurchaseOKMessageOfferData + { + return this._purchase; + } +} diff --git a/src/events/catalog/CatalogSetRoomPreviewerStuffDataEvent.ts b/src/events/catalog/CatalogSetRoomPreviewerStuffDataEvent.ts new file mode 100644 index 0000000..24e91a7 --- /dev/null +++ b/src/events/catalog/CatalogSetRoomPreviewerStuffDataEvent.ts @@ -0,0 +1,19 @@ +import { IObjectData, NitroEvent } from '@nitrots/nitro-renderer'; +import { CatalogWidgetEvent } from './CatalogWidgetEvent'; + +export class CatalogSetRoomPreviewerStuffDataEvent extends NitroEvent +{ + private _stuffData: IObjectData; + + constructor(stuffData: IObjectData) + { + super(CatalogWidgetEvent.SET_PREVIEWER_STUFFDATA); + + this._stuffData = stuffData; + } + + public get stuffData(): IObjectData + { + return this._stuffData; + } +} diff --git a/src/events/catalog/CatalogWidgetEvent.ts b/src/events/catalog/CatalogWidgetEvent.ts new file mode 100644 index 0000000..fd0e602 --- /dev/null +++ b/src/events/catalog/CatalogWidgetEvent.ts @@ -0,0 +1,26 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; + +export class CatalogWidgetEvent extends NitroEvent +{ + public static WIDGETS_INITIALIZED: string = 'CWE_CWE_WIDGETS_INITIALIZED'; + public static SELECT_PRODUCT: string = 'CWE_SELECT_PRODUCT'; + public static SET_EXTRA_PARM: string = 'CWE_CWE_SET_EXTRA_PARM'; + public static PURCHASE: string = 'CWE_PURCHASE'; + public static COLOUR_ARRAY: string = 'CWE_COLOUR_ARRAY'; + public static MULTI_COLOUR_ARRAY: string = 'CWE_MULTI_COLOUR_ARRAY'; + public static COLOUR_INDEX: string = 'CWE_COLOUR_INDEX'; + public static TEXT_INPUT: string = 'CWE_TEXT_INPUT'; + public static DROPMENU_SELECT: string = 'CWE_CWE_DROPMENU_SELECT'; + public static PURCHASE_OVERRIDE: string = 'CWE_PURCHASE_OVERRIDE'; + public static SELLABLE_PET_PALETTES: string = 'CWE_SELLABLE_PET_PALETTES'; + public static UPDATE_ROOM_PREVIEW: string = 'CWE_UPDATE_ROOM_PREVIEW'; + public static GUILD_SELECTED: string = 'CWE_GUILD_SELECTED'; + public static TOTAL_PRICE_WIDGET_INITIALIZED: string = 'CWE_TOTAL_PRICE_WIDGET_INITIALIZED'; + public static PRODUCT_OFFER_UPDATED: string = 'CWE_CWE_PRODUCT_OFFER_UPDATED'; + public static SET_PREVIEWER_STUFFDATA: string = 'CWE_CWE_SET_PREVIEWER_STUFFDATA'; + public static EXTRA_PARAM_REQUIRED_FOR_BUY: string = 'CWE_CWE_EXTRA_PARAM_REQUIRED_FOR_BUY'; + public static TOGGLE: string = 'CWE_CWE_TOGGLE'; + public static BUILDER_SUBSCRIPTION_UPDATED: string = 'CWE_CWE_BUILDER_SUBSCRIPTION_UPDATED'; + public static ROOM_CHANGED: string = 'CWE_CWE_ROOM_CHANGED'; + public static SHOW_WARNING_TEXT: string = 'CWE_CWE_SHOW_WARNING_TEXT'; +} diff --git a/src/events/catalog/SetRoomPreviewerStuffDataEvent.ts b/src/events/catalog/SetRoomPreviewerStuffDataEvent.ts new file mode 100644 index 0000000..5332dfd --- /dev/null +++ b/src/events/catalog/SetRoomPreviewerStuffDataEvent.ts @@ -0,0 +1,28 @@ +import { IObjectData, NitroEvent } from '@nitrots/nitro-renderer'; +import { IPurchasableOffer } from '../../api'; + +export class SetRoomPreviewerStuffDataEvent extends NitroEvent +{ + public static UPDATE_STUFF_DATA: string = 'SRPSA_UPDATE_STUFF_DATA'; + + private _offer: IPurchasableOffer; + private _stuffData: IObjectData; + + constructor(offer: IPurchasableOffer, stuffData: IObjectData) + { + super(SetRoomPreviewerStuffDataEvent.UPDATE_STUFF_DATA); + + this._offer = offer; + this._stuffData = stuffData; + } + + public get offer(): IPurchasableOffer + { + return this._offer; + } + + public get stuffData(): IObjectData + { + return this._stuffData; + } +} diff --git a/src/events/catalog/index.ts b/src/events/catalog/index.ts new file mode 100644 index 0000000..a7c1572 --- /dev/null +++ b/src/events/catalog/index.ts @@ -0,0 +1,11 @@ +export * from './CatalogEvent'; +export * from './CatalogInitGiftEvent'; +export * from './CatalogPostMarketplaceOfferEvent'; +export * from './CatalogPurchasedEvent'; +export * from './CatalogPurchaseFailureEvent'; +export * from './CatalogPurchaseNotAllowedEvent'; +export * from './CatalogPurchaseOverrideEvent'; +export * from './CatalogPurchaseSoldOutEvent'; +export * from './CatalogSetRoomPreviewerStuffDataEvent'; +export * from './CatalogWidgetEvent'; +export * from './SetRoomPreviewerStuffDataEvent'; diff --git a/src/events/guide-tool/GuideToolEvent.ts b/src/events/guide-tool/GuideToolEvent.ts new file mode 100644 index 0000000..f77cec3 --- /dev/null +++ b/src/events/guide-tool/GuideToolEvent.ts @@ -0,0 +1,10 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; + +export class GuideToolEvent extends NitroEvent +{ + public static readonly SHOW_GUIDE_TOOL: string = 'GTE_SHOW_GUIDE_TOOL'; + public static readonly HIDE_GUIDE_TOOL: string = 'GTE_HIDE_GUIDE_TOOL'; + public static readonly TOGGLE_GUIDE_TOOL: string = 'GTE_TOGGLE_GUIDE_TOOL'; + public static readonly CREATE_HELP_REQUEST: string = 'GTE_CREATE_HELP_REQUEST'; + public static readonly CREATE_BULLY_REQUEST: string = 'GTE_CREATE_BULLY_REQUEST'; +} diff --git a/src/events/guide-tool/index.ts b/src/events/guide-tool/index.ts new file mode 100644 index 0000000..6cbff54 --- /dev/null +++ b/src/events/guide-tool/index.ts @@ -0,0 +1 @@ +export * from './GuideToolEvent'; diff --git a/src/events/help/HelpNameChangeEvent.ts b/src/events/help/HelpNameChangeEvent.ts new file mode 100644 index 0000000..44597d0 --- /dev/null +++ b/src/events/help/HelpNameChangeEvent.ts @@ -0,0 +1,6 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; + +export class HelpNameChangeEvent extends NitroEvent +{ + public static INIT: string = 'HC_NAME_CHANGE_INIT'; +} diff --git a/src/events/help/index.ts b/src/events/help/index.ts new file mode 100644 index 0000000..e89f307 --- /dev/null +++ b/src/events/help/index.ts @@ -0,0 +1 @@ +export * from './HelpNameChangeEvent'; diff --git a/src/events/index.ts b/src/events/index.ts new file mode 100644 index 0000000..4c5e757 --- /dev/null +++ b/src/events/index.ts @@ -0,0 +1,6 @@ +export * from './catalog'; +export * from './guide-tool'; +export * from './help'; +export * from './inventory'; +export * from './room-widgets'; +export * from './room-widgets/thumbnail'; diff --git a/src/events/inventory/InventoryFurniAddedEvent.ts b/src/events/inventory/InventoryFurniAddedEvent.ts new file mode 100644 index 0000000..409f0be --- /dev/null +++ b/src/events/inventory/InventoryFurniAddedEvent.ts @@ -0,0 +1,14 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; + +export class InventoryFurniAddedEvent extends NitroEvent +{ + public static FURNI_ADDED: string = 'IFAE_FURNI_ADDED'; + + constructor( + public readonly id: number, + public readonly spriteId: number, + public readonly category: number) + { + super(InventoryFurniAddedEvent.FURNI_ADDED); + } +} diff --git a/src/events/inventory/index.ts b/src/events/inventory/index.ts new file mode 100644 index 0000000..58503ea --- /dev/null +++ b/src/events/inventory/index.ts @@ -0,0 +1 @@ +export * from './InventoryFurniAddedEvent'; diff --git a/src/events/room-widgets/index.ts b/src/events/room-widgets/index.ts new file mode 100644 index 0000000..d4ab7a5 --- /dev/null +++ b/src/events/room-widgets/index.ts @@ -0,0 +1 @@ +export * from './thumbnail'; diff --git a/src/events/room-widgets/thumbnail/RoomWidgetThumbnailEvent.ts b/src/events/room-widgets/thumbnail/RoomWidgetThumbnailEvent.ts new file mode 100644 index 0000000..dc62c6f --- /dev/null +++ b/src/events/room-widgets/thumbnail/RoomWidgetThumbnailEvent.ts @@ -0,0 +1,8 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; + +export class RoomWidgetThumbnailEvent extends NitroEvent +{ + public static SHOW_THUMBNAIL: string = 'NE_SHOW_THUMBNAIL'; + public static HIDE_THUMBNAIL: string = 'NE_HIDE_THUMBNAIL'; + public static TOGGLE_THUMBNAIL: string = 'NE_TOGGLE_THUMBNAIL'; +} diff --git a/src/events/room-widgets/thumbnail/index.ts b/src/events/room-widgets/thumbnail/index.ts new file mode 100644 index 0000000..56f116d --- /dev/null +++ b/src/events/room-widgets/thumbnail/index.ts @@ -0,0 +1 @@ +export * from './RoomWidgetThumbnailEvent'; diff --git a/src/hooks/UseMountEffect.tsx b/src/hooks/UseMountEffect.tsx new file mode 100644 index 0000000..0ead14b --- /dev/null +++ b/src/hooks/UseMountEffect.tsx @@ -0,0 +1,7 @@ +import { EffectCallback, useEffect } from 'react'; + + +// eslint-disable-next-line react-hooks/exhaustive-deps +const useEffectOnce = (effect: EffectCallback) => useEffect(effect, []); + +export const UseMountEffect = (fn: Function) => useEffectOnce(() => fn()); diff --git a/src/hooks/achievements/index.ts b/src/hooks/achievements/index.ts new file mode 100644 index 0000000..1a2a81e --- /dev/null +++ b/src/hooks/achievements/index.ts @@ -0,0 +1 @@ +export * from './useAchievements'; diff --git a/src/hooks/achievements/useAchievements.ts b/src/hooks/achievements/useAchievements.ts new file mode 100644 index 0000000..a067ff7 --- /dev/null +++ b/src/hooks/achievements/useAchievements.ts @@ -0,0 +1,185 @@ +import { AchievementData, AchievementEvent, AchievementsEvent, AchievementsScoreEvent, RequestAchievementsMessageComposer } from '@nitrots/nitro-renderer'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useBetween } from 'use-between'; +import { AchievementCategory, AchievementUtilities, CloneObject, SendMessageComposer } from '../../api'; +import { useMessageEvent } from '../events'; + +const useAchievementsState = () => +{ + const [ needsUpdate, setNeedsUpdate ] = useState(true); + const [ achievementCategories, setAchievementCategories ] = useState([]); + const [ selectedCategoryCode, setSelectedCategoryCode ] = useState(null); + const [ selectedAchievementId, setSelectedAchievementId ] = useState(-1); + const [ achievementScore, setAchievementScore ] = useState(0); + + const getTotalUnseen = useMemo(() => + { + let unseen = 0; + + achievementCategories.forEach(category => unseen += AchievementUtilities.getAchievementCategoryTotalUnseen(category)); + + return unseen; + }, [ achievementCategories ]); + + const getProgress = useMemo(() => + { + let progress = 0; + + achievementCategories.forEach(category => (progress += category.getProgress())); + + return progress; + }, [ achievementCategories ]); + + const getMaxProgress = useMemo(() => + { + let progress = 0; + + achievementCategories.forEach(category => (progress += category.getMaxProgress())); + + return progress; + }, [ achievementCategories ]); + + const scaledProgressPercent = useMemo(() => + { + return ~~((((getProgress - 0) * (100 - 0)) / (getMaxProgress - 0)) + 0); + }, [ getProgress, getMaxProgress ]); + + const selectedCategory = useMemo(() => + { + if(selectedCategoryCode === null) return null; + + return achievementCategories.find(category => (category.code === selectedCategoryCode)); + }, [ achievementCategories, selectedCategoryCode ]); + + const selectedAchievement = useMemo(() => + { + if((selectedAchievementId === -1) || !selectedCategory) return null; + + return selectedCategory.achievements.find(achievement => (achievement.achievementId === selectedAchievementId)); + }, [ selectedCategory, selectedAchievementId ]); + + const setAchievementSeen = useCallback((categoryCode: string, achievementId: number) => + { + setAchievementCategories(prevValue => + { + const newValue = [ ...prevValue ]; + + for(const category of newValue) + { + if(category.code !== categoryCode) continue; + + for(const achievement of category.achievements) + { + if(achievement.achievementId !== achievementId) continue; + + achievement.unseen = 0; + } + } + + return newValue; + }); + }, []); + + useMessageEvent(AchievementEvent, event => + { + const parser = event.getParser(); + const achievement = parser.achievement; + + setAchievementCategories(prevValue => + { + const newValue = [ ...prevValue ]; + const categoryIndex = newValue.findIndex(existing => (existing.code === achievement.category)); + + if(categoryIndex === -1) + { + const category = new AchievementCategory(achievement.category); + + category.achievements.push(achievement); + + newValue.push(category); + } + else + { + const category = CloneObject(newValue[categoryIndex]); + const newAchievements = [ ...category.achievements ]; + const achievementIndex = newAchievements.findIndex(existing => (existing.achievementId === achievement.achievementId)); + let previousAchievement: AchievementData = null; + + if(achievementIndex === -1) + { + newAchievements.push(achievement); + } + else + { + previousAchievement = newAchievements[achievementIndex]; + + newAchievements[achievementIndex] = achievement; + } + + if(!AchievementUtilities.getAchievementIsIgnored(achievement)) + { + achievement.unseen++; + + if(previousAchievement) achievement.unseen += previousAchievement.unseen; + } + + category.achievements = newAchievements; + + newValue[categoryIndex] = category; + } + + return newValue; + }); + }); + + useMessageEvent(AchievementsEvent, event => + { + const parser = event.getParser(); + const categories: AchievementCategory[] = []; + + for(const achievement of parser.achievements) + { + const categoryName = achievement.category; + + let existing = categories.find(category => (category.code === categoryName)); + + if(!existing) + { + existing = new AchievementCategory(categoryName); + + categories.push(existing); + } + + existing.achievements.push(achievement); + } + + setAchievementCategories(categories); + }); + + useMessageEvent(AchievementsScoreEvent, event => + { + const parser = event.getParser(); + + setAchievementScore(parser.score); + }); + + useEffect(() => + { + if(!needsUpdate) return; + + SendMessageComposer(new RequestAchievementsMessageComposer()); + + setNeedsUpdate(false); + }, [ needsUpdate ]); + + useEffect(() => + { + if(!selectedCategoryCode || (selectedAchievementId === -1)) return; + + setAchievementSeen(selectedCategoryCode, selectedAchievementId); + }, [ selectedCategoryCode, selectedAchievementId, setAchievementSeen ]); + + return { achievementCategories, selectedCategoryCode, setSelectedCategoryCode, selectedAchievementId, setSelectedAchievementId, achievementScore, getTotalUnseen, getProgress, getMaxProgress, scaledProgressPercent, selectedCategory, selectedAchievement, setAchievementSeen }; +} + +export const useAchievements = () => useBetween(useAchievementsState); diff --git a/src/hooks/avatar-editor/index.ts b/src/hooks/avatar-editor/index.ts new file mode 100644 index 0000000..ab8a733 --- /dev/null +++ b/src/hooks/avatar-editor/index.ts @@ -0,0 +1,2 @@ +export * from './useAvatarEditor'; +export * from './useFigureData'; diff --git a/src/hooks/avatar-editor/useAvatarEditor.ts b/src/hooks/avatar-editor/useAvatarEditor.ts new file mode 100644 index 0000000..82dd433 --- /dev/null +++ b/src/hooks/avatar-editor/useAvatarEditor.ts @@ -0,0 +1,244 @@ +import { AvatarEditorFigureCategory, FigureSetIdsMessageEvent, GetAvatarRenderManager, GetSessionDataManager, IFigurePartSet, IPartColor } from '@nitrots/nitro-renderer'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useBetween } from 'use-between'; +import { AvatarEditorThumbnailsHelper, FigureData, GetClubMemberLevel, IAvatarEditorCategory, IAvatarEditorCategoryPartItem } from '../../api'; +import { useMessageEvent } from '../events'; +import { useFigureData } from './useFigureData'; + +const MAX_PALETTES: number = 2; + +const useAvatarEditorState = () => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ avatarModels, setAvatarModels ] = useState<{ [index: string]: IAvatarEditorCategory[] }>({}); + const [ activeModelKey, setActiveModelKey ] = useState(''); + const [ maxPaletteCount, setMaxPaletteCount ] = useState(1); + const [ figureSetIds, setFigureSetIds ] = useState([]); + const [ boundFurnitureNames, setBoundFurnitureNames ] = useState([]); + const { gender, selectedParts, selectedColors, loadAvatarData, selectPart, selectColor, getFigureStringWithFace } = useFigureData(); + + const activeModel = useMemo(() => (avatarModels[activeModelKey] ?? null), [ activeModelKey, avatarModels ]); + + const selectedColorParts = useMemo(() => + { + const colorSets: { [index: string]: IPartColor[] } = {}; + + for(const setType of Object.keys(selectedColors)) + { + if(!selectedColors[setType]) continue; + + const parts: IPartColor[] = []; + + for(const paletteId of Object.keys(selectedColors[setType])) + { + const partColor = activeModel.find(category => (category.setType === setType))?.colorItems[paletteId]?.find(partColor => (partColor.id === selectedColors[setType][paletteId])); + + if(partColor) parts.push(partColor); + } + + colorSets[setType] = parts; + } + + return colorSets; + + }, [ activeModel, selectedColors ]); + + const selectEditorPart = useCallback((setType: string, partId: number) => + { + if(!setType || !setType.length) return; + + const category = activeModel.find(category => (category.setType === setType)); + + if(!category || !category.partItems || !category.partItems.length) return; + + const partItem = category.partItems.find(partItem => partItem.id === partId); + + if(!partItem) return; + + if(partItem.isClear) + { + // clear the part + return; + } + + if(GetClubMemberLevel() < partItem.partSet.clubLevel) return; + + setMaxPaletteCount(partItem.maxPaletteCount || 1); + + selectPart(setType, partId); + }, [ activeModel, selectPart ]); + + const selectEditorColor = useCallback((setType: string, paletteId: number, colorId: number) => + { + if(!setType || !setType.length) return; + + const category = activeModel.find(category => (category.setType === setType)); + + if(!category || !category.colorItems || !category.colorItems.length) return; + + const palette = category.colorItems[paletteId]; + + if(!palette || !palette.length) return; + + const partColor = palette.find(partColor => (partColor.id === colorId)); + + if(!partColor) return; + + if(GetClubMemberLevel() < partColor.clubLevel) return; + + selectColor(setType, paletteId, colorId); + }, [ activeModel, selectColor ]); + + useMessageEvent(FigureSetIdsMessageEvent, event => + { + const parser = event.getParser(); + + setFigureSetIds(parser.figureSetIds); + setBoundFurnitureNames(parser.boundsFurnitureNames); + }); + + useEffect(() => + { + AvatarEditorThumbnailsHelper.clearCache(); + }, [ selectedColorParts ]); + + useEffect(() => + { + if(!isVisible) return; + + const newAvatarModels: { [index: string]: IAvatarEditorCategory[] } = {}; + + const buildCategory = (setType: string) => + { + const partItems: IAvatarEditorCategoryPartItem[] = []; + const colorItems: IPartColor[][] = []; + + for(let i = 0; i < MAX_PALETTES; i++) colorItems.push([]); + + const set = GetAvatarRenderManager().structureData.getSetType(setType); + const palette = GetAvatarRenderManager().structureData.getPalette(set.paletteID); + + if(!set || !palette) return null; + + for(const partColor of palette.colors.getValues()) + { + if(!partColor || !partColor.isSelectable) continue; + + for(let i = 0; i < MAX_PALETTES; i++) colorItems[i].push(partColor); + + // TODO - check what this does + /* if(setType !== FigureData.FACE) + { + let i = 0; + + while(i < colorIds.length) + { + if(partColor.id === colorIds[i]) partColors[i] = partColor; + + i++; + } + } */ + } + + let mandatorySetIds: string[] = GetAvatarRenderManager().getMandatoryAvatarPartSetIds(gender, GetClubMemberLevel()); + + const isntMandatorySet = (mandatorySetIds.indexOf(setType) === -1); + + if(isntMandatorySet) partItems.push({ id: -1, isClear: true }); + + const usesColor = (setType !== FigureData.FACE); + const partSets = set.partSets; + + for(let i = (partSets.length); i >= 0; i--) + { + const partSet = partSets.getWithIndex(i); + + if(!partSet || !partSet.isSelectable || ((partSet.gender !== gender) && (partSet.gender !== FigureData.UNISEX))) continue; + + if(partSet.isSellable && figureSetIds.indexOf(partSet.id) === -1) continue; + + let maxPaletteCount = 0; + + for(const part of partSet.parts) maxPaletteCount = Math.max(maxPaletteCount, part.colorLayerIndex); + + partItems.push({ id: partSet.id, partSet, usesColor, maxPaletteCount }); + } + + partItems.sort(partSorter(false)); + + for(let i = 0; i < MAX_PALETTES; i++) colorItems[i].sort(colorSorter); + + return { setType, partItems, colorItems }; + } + + newAvatarModels[AvatarEditorFigureCategory.GENERIC] = [ FigureData.FACE ].map(setType => buildCategory(setType)); + newAvatarModels[AvatarEditorFigureCategory.HEAD] = [ FigureData.HAIR, FigureData.HAT, FigureData.HEAD_ACCESSORIES, FigureData.EYE_ACCESSORIES, FigureData.FACE_ACCESSORIES ].map(setType => buildCategory(setType)); + newAvatarModels[AvatarEditorFigureCategory.TORSO] = [ FigureData.SHIRT, FigureData.CHEST_PRINTS, FigureData.JACKET, FigureData.CHEST_ACCESSORIES ].map(setType => buildCategory(setType)); + newAvatarModels[AvatarEditorFigureCategory.LEGS] = [ FigureData.TROUSERS, FigureData.SHOES, FigureData.TROUSER_ACCESSORIES ].map(setType => buildCategory(setType)); + newAvatarModels[AvatarEditorFigureCategory.WARDROBE] = []; + + setAvatarModels(newAvatarModels); + setActiveModelKey(AvatarEditorFigureCategory.GENERIC); + }, [ isVisible, gender, figureSetIds ]); + + useEffect(() => + { + if(!isVisible) return; + + loadAvatarData(GetSessionDataManager().figure, GetSessionDataManager().gender); + }, [ isVisible, loadAvatarData ]); + + return { isVisible, setIsVisible, avatarModels, activeModelKey, setActiveModelKey, selectedParts, selectedColors, maxPaletteCount, selectedColorParts, selectEditorPart, selectEditorColor, getFigureStringWithFace }; +} + +export const useAvatarEditor = () => useBetween(useAvatarEditorState); + +const partSorter = (hcFirst: boolean) => +{ + return (a: { partSet: IFigurePartSet, usesColor: boolean, isClear?: boolean }, b: { partSet: IFigurePartSet, usesColor: boolean, isClear?: boolean }) => + { + const clubLevelA = (!a.partSet ? -1 : a.partSet.clubLevel); + const clubLevelB = (!b.partSet ? -1 : b.partSet.clubLevel); + const isSellableA = (!a.partSet ? false : a.partSet.isSellable); + const isSellableB = (!b.partSet ? false : b.partSet.isSellable); + + if(isSellableA && !isSellableB) return 1; + + if(isSellableB && !isSellableA) return -1; + + if(hcFirst) + { + if(clubLevelA > clubLevelB) return -1; + + if(clubLevelA < clubLevelB) return 1; + } + else + { + if(clubLevelA < clubLevelB) return -1; + + if(clubLevelA > clubLevelB) return 1; + } + + if(a.partSet.id < b.partSet.id) return -1; + + if(a.partSet.id > b.partSet.id) return 1; + + return 0; + } +} + +const colorSorter = (a: IPartColor, b: IPartColor) => +{ + const clubLevelA = (!a ? -1 : a.clubLevel); + const clubLevelB = (!b ? -1 : b.clubLevel); + + if(clubLevelA < clubLevelB) return -1; + + if(clubLevelA > clubLevelB) return 1; + + if(a.index < b.index) return -1; + + if(a.index > b.index) return 1; + + return 0; +} diff --git a/src/hooks/avatar-editor/useFigureData.ts b/src/hooks/avatar-editor/useFigureData.ts new file mode 100644 index 0000000..5d68730 --- /dev/null +++ b/src/hooks/avatar-editor/useFigureData.ts @@ -0,0 +1,114 @@ +import { useCallback, useState } from 'react'; +import { FigureData } from '../../api'; + +const useFigureDataState = () => +{ + const [ selectedParts, setSelectedParts ] = useState<{ [index: string]: number }>({}); + const [ selectedColors, setSelectedColors ] = useState<{ [index: string]: number[] }>({}); + const [ gender, setGender ] = useState(FigureData.MALE); + + const loadAvatarData = useCallback((figureString: string, gender: string) => + { + const parse = (figure: string) => + { + const sets = figure.split('.'); + + if(!sets || !sets.length) return; + + const partSets: { [index: string]: number } = {}; + const colorSets: { [index: string]: number[] } = {}; + + for(const set of sets) + { + const parts = set.split('-'); + + if(!parts.length) continue; + + const setType = parts[0]; + const partId = parseInt(parts[1]); + const colorIds: number[] = []; + + let offset = 2; + + while(offset < parts.length) + { + colorIds.push(parseInt(parts[offset])); + + offset++; + } + + if(!colorIds.length) colorIds.push(0); + + if(partId >= 0) partSets[setType] = partId; + + if(colorIds.length) colorSets[setType] = colorIds; + } + + return { partSets, colorSets }; + } + + const { partSets, colorSets } = parse(figureString); + + setSelectedParts(partSets); + setSelectedColors(colorSets); + setGender(gender); + }, []); + + const selectPart = useCallback((setType: string, partId: number) => + { + if(!setType || !setType.length) return; + + setSelectedParts(prevValue => + { + const newValue = { ...prevValue }; + + newValue[setType] = partId; + + return newValue; + }); + }, []); + + const selectColor = useCallback((setType: string, paletteId: number, colorId: number) => + { + if(!setType || !setType.length) return; + + setSelectedColors(prevValue => + { + const newValue = { ...prevValue }; + + if(!newValue[setType]) newValue[setType] = []; + + if(!newValue[setType][paletteId]) newValue[setType][paletteId] = 0; + + newValue[setType][paletteId] = colorId; + + return newValue; + }) + }, []); + + const getFigureStringWithFace = useCallback((overridePartId: number, override: boolean = true) => + { + const figureSets = [ FigureData.FACE ].map(setType => + { + // Determine the part ID, with an option to override if the set type matches. + let partId = (setType === FigureData.FACE && override) ? overridePartId : selectedParts[setType]; + const colors = selectedColors[setType] || []; + + // Construct the figure set string, including the type, part ID, and any colors. + let figureSet = `${ setType }-${ partId }`; + if (partId >= 0) + { + figureSet += colors.map(color => `-${ color }`).join(''); + } + + return figureSet; + }); + + // Join all figure sets with '.', ensuring to only add '.' between items, not at the end. + return figureSets.join('.'); + }, [ selectedParts, selectedColors ]); + + return { selectedParts, selectedColors, gender, loadAvatarData, selectPart, selectColor, getFigureStringWithFace }; +} + +export const useFigureData = useFigureDataState; diff --git a/src/hooks/camera/index.ts b/src/hooks/camera/index.ts new file mode 100644 index 0000000..20bea9c --- /dev/null +++ b/src/hooks/camera/index.ts @@ -0,0 +1 @@ +export * from './useCamera'; diff --git a/src/hooks/camera/useCamera.ts b/src/hooks/camera/useCamera.ts new file mode 100644 index 0000000..78b2eb4 --- /dev/null +++ b/src/hooks/camera/useCamera.ts @@ -0,0 +1,42 @@ +import { GetRoomCameraWidgetManager, InitCameraMessageEvent, IRoomCameraWidgetEffect, RequestCameraConfigurationComposer, RoomCameraWidgetManagerEvent } from '@nitrots/nitro-renderer'; +import { useEffect, useState } from 'react'; +import { useBetween } from 'use-between'; +import { CameraPicture, SendMessageComposer } from '../../api'; +import { useMessageEvent, useNitroEvent } from '../events'; + +const useCameraState = () => +{ + const [ availableEffects, setAvailableEffects ] = useState([]); + const [ cameraRoll, setCameraRoll ] = useState([]); + const [ selectedPictureIndex, setSelectedPictureIndex ] = useState(-1); + const [ myLevel, setMyLevel ] = useState(10); + const [ price, setPrice ] = useState<{ credits: number, duckets: number, publishDucketPrice: number }>(null); + + useNitroEvent(RoomCameraWidgetManagerEvent.INITIALIZED, event => + { + setAvailableEffects(Array.from(GetRoomCameraWidgetManager().effects.values())); + }); + + useMessageEvent(InitCameraMessageEvent, event => + { + const parser = event.getParser(); + + setPrice({ credits: parser.creditPrice, duckets: parser.ducketPrice, publishDucketPrice: parser.publishDucketPrice }); + }); + + useEffect(() => + { + if(!GetRoomCameraWidgetManager().isLoaded) + { + GetRoomCameraWidgetManager().init(); + + SendMessageComposer(new RequestCameraConfigurationComposer()); + + return; + } + }, []); + + return { availableEffects, cameraRoll, setCameraRoll, selectedPictureIndex, setSelectedPictureIndex, myLevel, price }; +} + +export const useCamera = () => useBetween(useCameraState); diff --git a/src/hooks/catalog/index.ts b/src/hooks/catalog/index.ts new file mode 100644 index 0000000..75d2984 --- /dev/null +++ b/src/hooks/catalog/index.ts @@ -0,0 +1,3 @@ +export * from './useCatalog'; +export * from './useCatalogPlaceMultipleItems'; +export * from './useCatalogSkipPurchaseConfirmation'; diff --git a/src/hooks/catalog/useCatalog.ts b/src/hooks/catalog/useCatalog.ts new file mode 100644 index 0000000..2b9d209 --- /dev/null +++ b/src/hooks/catalog/useCatalog.ts @@ -0,0 +1,913 @@ +import { BuildersClubFurniCountMessageEvent, BuildersClubPlaceRoomItemMessageComposer, BuildersClubPlaceWallItemMessageComposer, BuildersClubQueryFurniCountMessageComposer, BuildersClubSubscriptionStatusMessageEvent, CatalogPageMessageEvent, CatalogPagesListEvent, CatalogPublishedMessageEvent, ClubGiftInfoEvent, CreateLinkEvent, FrontPageItem, FurniturePlaceComposer, FurniturePlacePaintComposer, GetCatalogIndexComposer, GetCatalogPageComposer, GetClubGiftInfo, GetGiftWrappingConfigurationComposer, GetRoomEngine, GetTickerTime, GiftWrappingConfigurationEvent, GuildMembershipsMessageEvent, HabboClubOffersMessageEvent, LegacyDataType, LimitedEditionSoldOutEvent, MarketplaceMakeOfferResult, NodeData, ProductOfferEvent, PurchaseErrorMessageEvent, PurchaseFromCatalogComposer, PurchaseNotAllowedMessageEvent, PurchaseOKMessageEvent, RoomControllerLevel, RoomEngineObjectPlacedEvent, RoomObjectCategory, RoomObjectPlacementSource, RoomObjectType, RoomObjectVariable, RoomPreviewer, SellablePetPalettesMessageEvent, Vector3d } from '@nitrots/nitro-renderer'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useBetween } from 'use-between'; +import { BuilderFurniPlaceableStatus, CatalogNode, CatalogPage, CatalogPetPalette, CatalogType, DispatchUiEvent, FurniCategory, GetFurnitureData, GetProductDataForLocalization, GetRoomSession, GiftWrappingConfiguration, ICatalogNode, ICatalogOptions, ICatalogPage, IPageLocalization, IProduct, IPurchasableOffer, IPurchaseOptions, LocalizeText, NotificationAlertType, Offer, PageLocalization, PlacedObjectPurchaseData, PlaySound, Product, ProductTypeEnum, RequestedPage, SearchResult, SendMessageComposer, SoundNames } from '../../api'; +import { CatalogPurchaseFailureEvent, CatalogPurchaseNotAllowedEvent, CatalogPurchaseSoldOutEvent, CatalogPurchasedEvent, InventoryFurniAddedEvent } from '../../events'; +import { useMessageEvent, useNitroEvent, useUiEvent } from '../events'; +import { useNotification } from '../notification'; +import { useCatalogPlaceMultipleItems } from './useCatalogPlaceMultipleItems'; +import { useCatalogSkipPurchaseConfirmation } from './useCatalogSkipPurchaseConfirmation'; + +const DUMMY_PAGE_ID_FOR_OFFER_SEARCH = -12345678; +const DRAG_AND_DROP_ENABLED = true; + +const useCatalogState = () => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ isBusy, setIsBusy ] = useState(false); + const [ pageId, setPageId ] = useState(-1); + const [ previousPageId, setPreviousPageId ] = useState(-1); + const [ currentType, setCurrentType ] = useState(CatalogType.NORMAL); + const [ rootNode, setRootNode ] = useState(null); + const [ offersToNodes, setOffersToNodes ] = useState>(null); + const [ currentPage, setCurrentPage ] = useState(null); + const [ currentOffer, setCurrentOffer ] = useState(null); + const [ activeNodes, setActiveNodes ] = useState([]); + const [ searchResult, setSearchResult ] = useState(null); + const [ frontPageItems, setFrontPageItems ] = useState([]); + const [ roomPreviewer, setRoomPreviewer ] = useState(null); + const [ navigationHidden, setNavigationHidden ] = useState(false); + const [ purchaseOptions, setPurchaseOptions ] = useState({ quantity: 1, extraData: null, extraParamRequired: false, previewStuffData: null }); + const [ catalogOptions, setCatalogOptions ] = useState({}); + const [ objectMoverRequested, setObjectMoverRequested ] = useState(false); + const [ catalogPlaceMultipleObjects, setCatalogPlaceMultipleObjects ] = useCatalogPlaceMultipleItems(); + const [ catalogSkipPurchaseConfirmation, setCatalogSkipPurchaseConfirmation ] = useCatalogSkipPurchaseConfirmation(); + const [ purchasableOffer, setPurchaseableOffer ] = useState(null); + const [ placedObjectPurchaseData, setPlacedObjectPurchaseData ] = useState(null); + const [ furniCount, setFurniCount ] = useState(0); + const [ furniLimit, setFurniLimit ] = useState(0); + const [ maxFurniLimit, setMaxFurniLimit ] = useState(0); + const [ secondsLeft, setSecondsLeft ] = useState(0); + const [ updateTime, setUpdateTime ] = useState(0); + const [ secondsLeftWithGrace, setSecondsLeftWithGrace ] = useState(0); + const { simpleAlert = null } = useNotification(); + const requestedPage = useRef(new RequestedPage()); + + const resetState = useCallback(() => + { + setPageId(-1); + setPreviousPageId(-1); + setRootNode(null); + setOffersToNodes(null); + setCurrentPage(null); + setCurrentOffer(null); + setActiveNodes([]); + setSearchResult(null); + setFrontPageItems([]); + setIsVisible(false); + }, []); + + const getBuilderFurniPlaceableStatus = useCallback((offer: IPurchasableOffer) => + { + if(!offer) return BuilderFurniPlaceableStatus.MISSING_OFFER; + + if((furniCount < 0) || (furniCount >= furniLimit)) return BuilderFurniPlaceableStatus.FURNI_LIMIT_REACHED; + + const roomSession = GetRoomSession(); + + if(!roomSession) return BuilderFurniPlaceableStatus.NOT_IN_ROOM; + + if(!roomSession.isRoomOwner) return BuilderFurniPlaceableStatus.NOT_ROOM_OWNER; + + if(secondsLeft <= 0) + { + const roomEngine = GetRoomEngine(); + + let objectCount = roomEngine.getRoomObjectCount(roomSession.roomId, RoomObjectCategory.UNIT); + + while(objectCount > 0) + { + const roomObject = roomEngine.getRoomObjectByIndex(roomSession.roomId, objectCount, RoomObjectCategory.UNIT); + const userData = roomSession.userDataManager.getUserDataByIndex(roomObject.id); + + if(userData && (userData.type === RoomObjectType.USER) && (userData.roomIndex !== roomSession.ownRoomIndex) && !userData.isModerator) return BuilderFurniPlaceableStatus.VISITORS_IN_ROOM; + + objectCount--; + } + } + + return BuilderFurniPlaceableStatus.OKAY; + }, [ furniCount, furniLimit, secondsLeft ]); + + const isDraggable = useCallback((offer: IPurchasableOffer) => + { + const roomSession = GetRoomSession(); + + if(((DRAG_AND_DROP_ENABLED && roomSession && offer.page && (offer.page.layoutCode !== 'sold_ltd_items') && (currentType === CatalogType.NORMAL) && (roomSession.isRoomOwner || (roomSession.isGuildRoom && (roomSession.controllerLevel >= RoomControllerLevel.GUILD_MEMBER)))) || ((currentType === CatalogType.BUILDER) && (getBuilderFurniPlaceableStatus(offer) === BuilderFurniPlaceableStatus.OKAY))) && (offer.pricingModel !== Offer.PRICING_MODEL_BUNDLE) && (offer.product.productType !== ProductTypeEnum.EFFECT) && (offer.product.productType !== ProductTypeEnum.HABBO_CLUB)) return true; + + return false; + }, [ currentType, getBuilderFurniPlaceableStatus ]); + + const requestOfferToMover = useCallback((offer: IPurchasableOffer) => + { + if(!isDraggable(offer)) return; + + const product = offer.product; + + if(!product) return; + + let category = 0; + + switch(product.productType) + { + case ProductTypeEnum.FLOOR: + category = RoomObjectCategory.FLOOR; + break; + case ProductTypeEnum.WALL: + category = RoomObjectCategory.WALL; + break; + } + + if(GetRoomEngine().processRoomObjectPlacement(RoomObjectPlacementSource.CATALOG, -(offer.offerId), category, product.productClassId, product.extraParam)) + { + setPurchaseableOffer(offer); + setObjectMoverRequested(true); + + setIsVisible(false); + } + }, [ isDraggable ]); + + const resetRoomPaint = useCallback((planeType: string, type: string) => + { + const roomEngine = GetRoomEngine(); + + let wallType = roomEngine.getRoomInstanceVariable(roomEngine.activeRoomId, RoomObjectVariable.ROOM_WALL_TYPE); + let floorType = roomEngine.getRoomInstanceVariable(roomEngine.activeRoomId, RoomObjectVariable.ROOM_FLOOR_TYPE); + let landscapeType = roomEngine.getRoomInstanceVariable(roomEngine.activeRoomId, RoomObjectVariable.ROOM_LANDSCAPE_TYPE); + + wallType = (wallType && wallType.length) ? wallType : '101'; + floorType = (floorType && floorType.length) ? floorType : '101'; + landscapeType = (landscapeType && landscapeType.length) ? landscapeType : '1.1'; + + switch(planeType) + { + case 'floor': + roomEngine.updateRoomInstancePlaneType(roomEngine.activeRoomId, type, wallType, landscapeType, true); + return; + case 'wallpaper': + roomEngine.updateRoomInstancePlaneType(roomEngine.activeRoomId, floorType, type, landscapeType, true); + return; + case 'landscape': + roomEngine.updateRoomInstancePlaneType(roomEngine.activeRoomId, floorType, wallType, type, true); + return; + default: + roomEngine.updateRoomInstancePlaneType(roomEngine.activeRoomId, floorType, wallType, landscapeType, true); + return; + } + }, []); + + const cancelObjectMover = useCallback(() => + { + if(!purchasableOffer) return; + + GetRoomEngine().cancelRoomObjectInsert(); + + setObjectMoverRequested(false); + setPurchaseableOffer(null); + }, [ purchasableOffer ]); + + const resetObjectMover = useCallback((flag: boolean = true) => + { + setObjectMoverRequested(prevValue => + { + if(prevValue && flag) + { + CreateLinkEvent('catalog/open'); + } + + return false; + }); + }, []); + + const resetPlacedOfferData = useCallback((flag: boolean = false) => + { + if(!flag) resetObjectMover(); + + setPlacedObjectPurchaseData(prevValue => + { + if(prevValue) + { + switch(prevValue.category) + { + case RoomObjectCategory.FLOOR: + GetRoomEngine().removeRoomObjectFloor(prevValue.roomId, prevValue.objectId); + break; + case RoomObjectCategory.WALL: { + + switch(prevValue.furniData.className) + { + case 'floor': + case 'wallpaper': + case 'landscape': + resetRoomPaint('reset', ''); + break; + default: + GetRoomEngine().removeRoomObjectWall(prevValue.roomId, prevValue.objectId); + break; + } + break; + } + default: + GetRoomEngine().deleteRoomObject(prevValue.objectId, prevValue.category); + break; + } + } + + return null; + }); + }, [ resetObjectMover, resetRoomPaint ]); + + const getNodeById = useCallback((id: number, node: ICatalogNode) => + { + if((node.pageId === id) && (node !== rootNode)) return node; + + for(const child of node.children) + { + const found = (getNodeById(id, child) as ICatalogNode); + + if(found) return found; + } + + return null; + }, [ rootNode ]); + + const getNodeByName = useCallback((name: string, node: ICatalogNode) => + { + if((node.pageName === name) && (node !== rootNode)) return node; + + for(const child of node.children) + { + const found = (getNodeByName(name, child) as ICatalogNode); + + if(found) return found; + } + + return null; + }, [ rootNode ]); + + const getNodesByOfferId = useCallback((offerId: number, flag: boolean = false) => + { + if(!offersToNodes || !offersToNodes.size) return null; + + if(flag) + { + const nodes: ICatalogNode[] = []; + const offers = offersToNodes.get(offerId); + + if(offers && offers.length) for(const offer of offers) (offer.isVisible && nodes.push(offer)); + + if(nodes.length) return nodes; + } + + return offersToNodes.get(offerId); + }, [ offersToNodes ]); + + const loadCatalogPage = useCallback((pageId: number, offerId: number) => + { + if(pageId < 0) return; + + setIsBusy(true); + setPageId(pageId); + + if(pageId > -1) SendMessageComposer(new GetCatalogPageComposer(pageId, offerId, currentType)); + }, [ currentType ]); + + const showCatalogPage = useCallback((pageId: number, layoutCode: string, localization: IPageLocalization, offers: IPurchasableOffer[], offerId: number, acceptSeasonCurrencyAsCredits: boolean) => + { + const catalogPage = (new CatalogPage(pageId, layoutCode, localization, offers, acceptSeasonCurrencyAsCredits) as ICatalogPage); + + setCurrentPage(catalogPage); + setPreviousPageId(prevValue => ((pageId !== -1) ? pageId : prevValue)); + setNavigationHidden(false); + + if((offerId > -1) && catalogPage.offers.length) + { + for(const offer of catalogPage.offers) + { + if(offer.offerId !== offerId) continue; + + setCurrentOffer(offer) + + break; + } + } + }, []); + + const activateNode = useCallback((targetNode: ICatalogNode, offerId: number = -1) => + { + cancelObjectMover(); + + if(targetNode.parent.pageName === 'root') + { + if(targetNode.children.length) + { + for(const child of targetNode.children) + { + if(!child.isVisible) continue; + + targetNode = child; + + break; + } + } + } + + const nodes: ICatalogNode[] = []; + + let node = targetNode; + + while(node && (node.pageName !== 'root')) + { + nodes.push(node); + + node = node.parent; + } + + nodes.reverse(); + + setActiveNodes(prevValue => + { + const isActive = (prevValue.indexOf(targetNode) >= 0); + const isOpen = targetNode.isOpen; + + for(const existing of prevValue) + { + existing.deactivate(); + + if(nodes.indexOf(existing) === -1) existing.close(); + } + + for(const n of nodes) + { + n.activate(); + + if(n.parent) n.open(); + + if((n === targetNode.parent) && n.children.length) n.open(); + } + + if(isActive && isOpen) targetNode.close(); + else targetNode.open(); + + return nodes; + }); + + if(targetNode.pageId > -1) loadCatalogPage(targetNode.pageId, offerId); + }, [ setActiveNodes, loadCatalogPage, cancelObjectMover ]); + + const openPageById = useCallback((id: number) => + { + if(id !== -1) setSearchResult(null); + + if(!isVisible) + { + requestedPage.current.requestById = id; + + setIsVisible(true); + } + else + { + const node = getNodeById(id, rootNode); + + if(node) activateNode(node); + } + }, [ isVisible, rootNode, getNodeById, activateNode ]); + + const openPageByName = useCallback((name: string) => + { + setSearchResult(null); + + if(!isVisible) + { + requestedPage.current.requestByName = name; + + setIsVisible(true); + } + else + { + const node = getNodeByName(name, rootNode); + + if(node) activateNode(node); + } + }, [ isVisible, rootNode, getNodeByName, activateNode ]); + + const openPageByOfferId = useCallback((offerId: number) => + { + setSearchResult(null); + + if(!isVisible) + { + requestedPage.current.requestedByOfferId = offerId; + + setIsVisible(true); + } + else + { + const nodes = getNodesByOfferId(offerId); + + if(!nodes || !nodes.length) return; + + activateNode(nodes[0], offerId); + } + }, [ isVisible, getNodesByOfferId, activateNode ]); + + const refreshBuilderStatus = useCallback(() => + { + + }, []); + + useMessageEvent(CatalogPagesListEvent, event => + { + const parser = event.getParser(); + const offers: Map = new Map(); + + const getCatalogNode = (node: NodeData, depth: number, parent: ICatalogNode) => + { + const catalogNode = (new CatalogNode(node, depth, parent) as ICatalogNode); + + for(const offerId of catalogNode.offerIds) + { + if(offers.has(offerId)) offers.get(offerId).push(catalogNode); + else offers.set(offerId, [ catalogNode ]); + } + + depth++; + + for(const child of node.children) catalogNode.addChild(getCatalogNode(child, depth, catalogNode)); + + return catalogNode; + } + + setRootNode(getCatalogNode(parser.root, 0, null)); + setOffersToNodes(offers); + }); + + useMessageEvent(CatalogPageMessageEvent, event => + { + const parser = event.getParser(); + + if(parser.catalogType !== currentType) return; + + const purchasableOffers: IPurchasableOffer[] = []; + + for(const offer of parser.offers) + { + const products: IProduct[] = []; + const productData = GetProductDataForLocalization(offer.localizationId); + + for(const product of offer.products) + { + const furnitureData = GetFurnitureData(product.furniClassId, product.productType); + + products.push(new Product(product.productType, product.furniClassId, product.extraParam, product.productCount, productData, furnitureData, product.uniqueLimitedItem, product.uniqueLimitedSeriesSize, product.uniqueLimitedItemsLeft)); + } + + if(!products.length) continue; + + const purchasableOffer = new Offer(offer.offerId, offer.localizationId, offer.rent, offer.priceCredits, offer.priceActivityPoints, offer.priceActivityPointsType, offer.giftable, offer.clubLevel, products, offer.bundlePurchaseAllowed); + + if((currentType === CatalogType.NORMAL) || ((purchasableOffer.pricingModel !== Offer.PRICING_MODEL_BUNDLE) && (purchasableOffer.pricingModel !== Offer.PRICING_MODEL_MULTI))) purchasableOffers.push(purchasableOffer); + } + + if(parser.frontPageItems && parser.frontPageItems.length) setFrontPageItems(parser.frontPageItems); + + setIsBusy(false); + + if(pageId === parser.pageId) + { + showCatalogPage(parser.pageId, parser.layoutCode, new PageLocalization(parser.localization.images.concat(), parser.localization.texts.concat()), purchasableOffers, parser.offerId, parser.acceptSeasonCurrencyAsCredits); + } + }); + + useMessageEvent(PurchaseOKMessageEvent, event => + { + const parser = event.getParser(); + + DispatchUiEvent(new CatalogPurchasedEvent(parser.offer)); + }); + + useMessageEvent(PurchaseErrorMessageEvent, event => + { + const parser = event.getParser(); + + DispatchUiEvent(new CatalogPurchaseFailureEvent(parser.code)); + }); + + useMessageEvent(PurchaseNotAllowedMessageEvent, event => + { + const parser = event.getParser(); + + DispatchUiEvent(new CatalogPurchaseNotAllowedEvent(parser.code)); + }); + + useMessageEvent(LimitedEditionSoldOutEvent, event => + { + const parser = event.getParser(); + + DispatchUiEvent(new CatalogPurchaseSoldOutEvent()); + }); + + useMessageEvent(ProductOfferEvent, event => + { + const parser = event.getParser(); + const offerData = parser.offer; + + if(!offerData || !offerData.products.length) return; + + const offerProductData = offerData.products[0]; + + if(offerProductData.uniqueLimitedItem) + { + // update unique + } + + const products: IProduct[] = []; + const productData = GetProductDataForLocalization(offerData.localizationId); + + for(const product of offerData.products) + { + const furnitureData = GetFurnitureData(product.furniClassId, product.productType); + + products.push(new Product(product.productType, product.furniClassId, product.extraParam, product.productCount, productData, furnitureData, product.uniqueLimitedItem, product.uniqueLimitedSeriesSize, product.uniqueLimitedItemsLeft)); + } + + const offer = new Offer(offerData.offerId, offerData.localizationId, offerData.rent, offerData.priceCredits, offerData.priceActivityPoints, offerData.priceActivityPointsType, offerData.giftable, offerData.clubLevel, products, offerData.bundlePurchaseAllowed); + + if(!((currentType === CatalogType.NORMAL) || ((offer.pricingModel !== Offer.PRICING_MODEL_BUNDLE) && (offer.pricingModel !== Offer.PRICING_MODEL_MULTI)))) return; + + offer.page = currentPage; + + setCurrentOffer(offer); + + if(offer.product && (offer.product.productType === ProductTypeEnum.WALL)) + { + setPurchaseOptions(prevValue => + { + const newValue = { ...prevValue }; + + newValue.extraData =( offer.product.extraParam || null); + + return newValue; + }); + } + + // (this._isObjectMoverRequested) && (this._purchasableOffer) + }); + + useMessageEvent(SellablePetPalettesMessageEvent, event => + { + const parser = event.getParser(); + const petPalette = new CatalogPetPalette(parser.productCode, parser.palettes.slice()); + + setCatalogOptions(prevValue => + { + const petPalettes = []; + + if(prevValue.petPalettes) petPalettes.push(...prevValue.petPalettes); + + for(let i = 0; i < petPalettes.length; i++) + { + const palette = petPalettes[i]; + + if(palette.breed === petPalette.breed) + { + petPalettes.splice(i, 1); + + break; + } + } + + petPalettes.push(petPalette); + + return { ...prevValue, petPalettes }; + }); + }); + + useMessageEvent(HabboClubOffersMessageEvent, event => + { + const parser = event.getParser(); + + setCatalogOptions(prevValue => + { + const clubOffers = parser.offers; + + return { ...prevValue, clubOffers }; + }); + }); + + useMessageEvent(GuildMembershipsMessageEvent, event => + { + const parser = event.getParser(); + + setCatalogOptions(prevValue => + { + const groups = parser.groups; + + return { ...prevValue, groups }; + }); + }); + + useMessageEvent(GiftWrappingConfigurationEvent, event => + { + const parser = event.getParser(); + + setCatalogOptions(prevValue => + { + const giftConfiguration = new GiftWrappingConfiguration(parser); + + return { ...prevValue, giftConfiguration }; + }); + }); + + useMessageEvent(MarketplaceMakeOfferResult, event => + { + const parser = event.getParser(); + + if(!parser) return; + + let title = ''; + if(parser.result === 1) + { + title = LocalizeText('inventory.marketplace.result.title.success'); + } + else + { + title = LocalizeText('inventory.marketplace.result.title.failure'); + } + + const message = LocalizeText(`inventory.marketplace.result.${ parser.result }`); + + simpleAlert(message, NotificationAlertType.DEFAULT, null, null, title); + }); + + useMessageEvent(ClubGiftInfoEvent, event => + { + const parser = event.getParser(); + + setCatalogOptions(prevValue => + { + const clubGifts = parser; + + return { ...prevValue, clubGifts }; + }); + }); + + useMessageEvent(CatalogPublishedMessageEvent, event => + { + const wasVisible = isVisible; + + resetState(); + + if(wasVisible) simpleAlert(LocalizeText('catalog.alert.published.description'), NotificationAlertType.ALERT, null, null, LocalizeText('catalog.alert.published.title')); + }); + + useMessageEvent(BuildersClubFurniCountMessageEvent, event => + { + const parser = event.getParser(); + + setFurniCount(parser.furniCount); + + refreshBuilderStatus(); + }); + + useMessageEvent(BuildersClubSubscriptionStatusMessageEvent, event => + { + const parser = event.getParser(); + + setFurniLimit(parser.furniLimit); + setMaxFurniLimit(parser.maxFurniLimit); + setSecondsLeft(parser.secondsLeft); + setUpdateTime(GetTickerTime()); + setSecondsLeftWithGrace(parser.secondsLeftWithGrace); + + refreshBuilderStatus(); + }); + + useUiEvent(CatalogPurchasedEvent.PURCHASE_SUCCESS, event => PlaySound(SoundNames.CREDITS)); + + useNitroEvent(RoomEngineObjectPlacedEvent.PLACED, event => + { + if(!objectMoverRequested || (event.type !== RoomEngineObjectPlacedEvent.PLACED)) return; + + resetPlacedOfferData(true); + + if(!purchasableOffer) + { + resetObjectMover(); + + return; + } + + let placed = false; + + const product = purchasableOffer.product; + + if(event.category === RoomObjectCategory.WALL) + { + switch(product.furnitureData.className) + { + case 'floor': + case 'wallpaper': + case 'landscape': + placed = (event.placedOnFloor || event.placedOnWall); + break; + default: + placed = event.placedInRoom; + break; + } + } + else + { + placed = event.placedInRoom; + } + + if(!placed) + { + resetObjectMover(); + + return; + } + + setPlacedObjectPurchaseData(new PlacedObjectPurchaseData(event.roomId, event.objectId, event.category, event.wallLocation, event.x, event.y, event.direction, purchasableOffer)); + + switch(currentType) + { + case CatalogType.NORMAL: { + switch(event.category) + { + case RoomObjectCategory.FLOOR: + GetRoomEngine().addFurnitureFloor(event.roomId, event.objectId, product.productClassId, new Vector3d(event.x, event.y, event.z), new Vector3d(event.direction), 0, new LegacyDataType()); + break; + case RoomObjectCategory.WALL: { + switch(product.furnitureData.className) + { + case 'floor': + case 'wallpaper': + case 'landscape': + resetRoomPaint(product.furnitureData.className, product.extraParam); + break; + default: + GetRoomEngine().addFurnitureWall(event.roomId, event.objectId, product.productClassId, new Vector3d(event.x, event.y, event.z), new Vector3d(event.direction * 45), 0, event.instanceData, 0); + break; + } + } + } + + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + + if(roomObject) roomObject.model.setValue(RoomObjectVariable.FURNITURE_ALPHA_MULTIPLIER, 0.5); + + if(catalogSkipPurchaseConfirmation) + { + SendMessageComposer(new PurchaseFromCatalogComposer(pageId, purchasableOffer.offerId, product.extraParam, 1)); + + if(catalogPlaceMultipleObjects) requestOfferToMover(purchasableOffer); + } + else + { + // confirm + + if(catalogPlaceMultipleObjects) requestOfferToMover(purchasableOffer); + } + break; + } + case CatalogType.BUILDER: { + let pageId = purchasableOffer.page.pageId; + + if(pageId === DUMMY_PAGE_ID_FOR_OFFER_SEARCH) + { + pageId = -1; + } + + switch(event.category) + { + case RoomObjectCategory.FLOOR: + SendMessageComposer(new BuildersClubPlaceRoomItemMessageComposer(pageId, purchasableOffer.offerId, product.extraParam, event.x, event.y, event.direction)); + break; + case RoomObjectCategory.WALL: + SendMessageComposer(new BuildersClubPlaceWallItemMessageComposer(pageId, purchasableOffer.offerId, product.extraParam, event.wallLocation)); + break; + } + + if(catalogPlaceMultipleObjects) requestOfferToMover(purchasableOffer); + break; + } + } + }); + + useUiEvent(InventoryFurniAddedEvent.FURNI_ADDED, event => + { + const roomEngine = GetRoomEngine(); + + if(!placedObjectPurchaseData || (placedObjectPurchaseData.productClassId !== event.spriteId) || (placedObjectPurchaseData.roomId !== roomEngine.activeRoomId)) return; + + switch(event.category) + { + case FurniCategory.FLOOR: { + const floorType = roomEngine.getRoomInstanceVariable(roomEngine.activeRoomId, RoomObjectVariable.ROOM_FLOOR_TYPE); + + if(placedObjectPurchaseData.extraParam !== floorType) SendMessageComposer(new FurniturePlacePaintComposer(event.id)); + break; + } + case FurniCategory.WALL_PAPER: { + const wallType = roomEngine.getRoomInstanceVariable(roomEngine.activeRoomId, RoomObjectVariable.ROOM_WALL_TYPE); + + if(placedObjectPurchaseData.extraParam !== wallType) SendMessageComposer(new FurniturePlacePaintComposer(event.id)); + break; + } + case FurniCategory.LANDSCAPE: { + const landscapeType = roomEngine.getRoomInstanceVariable(roomEngine.activeRoomId, RoomObjectVariable.ROOM_LANDSCAPE_TYPE); + + if(placedObjectPurchaseData.extraParam !== landscapeType) SendMessageComposer(new FurniturePlacePaintComposer(event.id)); + break; + } + default: + SendMessageComposer(new FurniturePlaceComposer(event.id, placedObjectPurchaseData.category, placedObjectPurchaseData.wallLocation, placedObjectPurchaseData.x, placedObjectPurchaseData.y, placedObjectPurchaseData.direction)); + } + + if(!catalogPlaceMultipleObjects) resetPlacedOfferData(); + }); + + useEffect(() => + { + return () => setCurrentOffer(null); + }, [ currentPage ]); + + useEffect(() => + { + if(!isVisible || !rootNode || !offersToNodes || !requestedPage.current) return; + + switch(requestedPage.current.requestType) + { + case RequestedPage.REQUEST_TYPE_NONE: + if(currentPage) return; + + if(rootNode.isBranch) + { + for(const child of rootNode.children) + { + if(child && child.isVisible) + { + activateNode(child); + + return; + } + } + } + return; + case RequestedPage.REQUEST_TYPE_ID: + openPageById(requestedPage.current.requestById); + requestedPage.current.resetRequest(); + return; + case RequestedPage.REQUEST_TYPE_OFFER: + openPageByOfferId(requestedPage.current.requestedByOfferId); + requestedPage.current.resetRequest(); + return; + case RequestedPage.REQUEST_TYPE_NAME: + openPageByName(requestedPage.current.requestByName); + requestedPage.current.resetRequest(); + return; + } + }, [ isVisible, rootNode, offersToNodes, currentPage, activateNode, openPageById, openPageByOfferId, openPageByName ]); + + useEffect(() => + { + if(!searchResult && currentPage && (currentPage.pageId === -1)) openPageById(previousPageId); + }, [ searchResult, currentPage, previousPageId, openPageById ]); + + useEffect(() => + { + if(!currentOffer) return; + + setPurchaseOptions({ quantity: 1, extraData: null, extraParamRequired: false, previewStuffData: null }); + }, [ currentOffer ]); + + useEffect(() => + { + if(!isVisible || rootNode) return; + + SendMessageComposer(new GetGiftWrappingConfigurationComposer()); + SendMessageComposer(new GetClubGiftInfo()); + SendMessageComposer(new GetCatalogIndexComposer(currentType)); + SendMessageComposer(new BuildersClubQueryFurniCountMessageComposer()); + }, [ isVisible, rootNode, currentType ]); + + useEffect(() => + { + setRoomPreviewer(new RoomPreviewer(GetRoomEngine(), ++RoomPreviewer.PREVIEW_COUNTER)); + + return () => + { + setRoomPreviewer(prevValue => + { + prevValue.dispose(); + + return null; + }); + } + }, []); + + return { isVisible, setIsVisible, isBusy, pageId, previousPageId, currentType, rootNode, offersToNodes, currentPage, setCurrentPage, currentOffer, setCurrentOffer, activeNodes, searchResult, setSearchResult, frontPageItems, roomPreviewer, navigationHidden, setNavigationHidden, purchaseOptions, setPurchaseOptions, catalogOptions, setCatalogOptions, getNodeById, getNodeByName, activateNode, openPageById, openPageByName, openPageByOfferId, requestOfferToMover }; +} + +export const useCatalog = () => useBetween(useCatalogState); diff --git a/src/hooks/catalog/useCatalogPlaceMultipleItems.ts b/src/hooks/catalog/useCatalogPlaceMultipleItems.ts new file mode 100644 index 0000000..39cfd28 --- /dev/null +++ b/src/hooks/catalog/useCatalogPlaceMultipleItems.ts @@ -0,0 +1,7 @@ +import { useBetween } from 'use-between'; +import { LocalStorageKeys } from '../../api'; +import { useLocalStorage } from '../useLocalStorage'; + +const useCatalogPlaceMultipleItemsState = () => useLocalStorage(LocalStorageKeys.CATALOG_PLACE_MULTIPLE_OBJECTS, false); + +export const useCatalogPlaceMultipleItems = () => useBetween(useCatalogPlaceMultipleItemsState); diff --git a/src/hooks/catalog/useCatalogSkipPurchaseConfirmation.ts b/src/hooks/catalog/useCatalogSkipPurchaseConfirmation.ts new file mode 100644 index 0000000..b2d69a2 --- /dev/null +++ b/src/hooks/catalog/useCatalogSkipPurchaseConfirmation.ts @@ -0,0 +1,7 @@ +import { useBetween } from 'use-between'; +import { LocalStorageKeys } from '../../api'; +import { useLocalStorage } from '../useLocalStorage'; + +const useCatalogSkipPurchaseConfirmationState = () => useLocalStorage(LocalStorageKeys.CATALOG_SKIP_PURCHASE_CONFIRMATION, false); + +export const useCatalogSkipPurchaseConfirmation = () => useBetween(useCatalogSkipPurchaseConfirmationState); diff --git a/src/hooks/chat-history/index.ts b/src/hooks/chat-history/index.ts new file mode 100644 index 0000000..970d90f --- /dev/null +++ b/src/hooks/chat-history/index.ts @@ -0,0 +1 @@ +export * from './useChatHistory'; diff --git a/src/hooks/chat-history/useChatHistory.ts b/src/hooks/chat-history/useChatHistory.ts new file mode 100644 index 0000000..b8545a0 --- /dev/null +++ b/src/hooks/chat-history/useChatHistory.ts @@ -0,0 +1,104 @@ +import { GetGuestRoomResultEvent, NewConsoleMessageEvent, RoomInviteEvent, RoomSessionEvent } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { useBetween } from 'use-between'; +import { ChatEntryType, ChatHistoryCurrentDate, IChatEntry, IRoomHistoryEntry, MessengerHistoryCurrentDate } from '../../api'; +import { useMessageEvent, useNitroEvent } from '../events'; + +const CHAT_HISTORY_MAX = 1000; +const ROOM_HISTORY_MAX = 10; +const MESSENGER_HISTORY_MAX = 1000; + +let CHAT_HISTORY_COUNTER: number = 0; +let MESSENGER_HISTORY_COUNTER: number = 0; + +const useChatHistoryState = () => +{ + const [ chatHistory, setChatHistory ] = useState([]); + const [ roomHistory, setRoomHistory ] = useState([]); + const [ messengerHistory, setMessengerHistory ] = useState([]); + const [ needsRoomInsert, setNeedsRoomInsert ] = useState(false); + + const addChatEntry = (entry: IChatEntry) => + { + entry.id = CHAT_HISTORY_COUNTER++; + + setChatHistory(prevValue => + { + const newValue = [ ...prevValue ]; + + newValue.push(entry); + + if(newValue.length > CHAT_HISTORY_MAX) newValue.shift(); + + return newValue; + }); + } + + const addRoomHistoryEntry = (entry: IRoomHistoryEntry) => + { + setRoomHistory(prevValue => + { + const newValue = [ ...prevValue ]; + + newValue.push(entry); + + if(newValue.length > ROOM_HISTORY_MAX) newValue.shift(); + + return newValue; + }); + } + + const addMessengerEntry = (entry: IChatEntry) => + { + entry.id = MESSENGER_HISTORY_COUNTER++; + + setMessengerHistory(prevValue => + { + const newValue = [ ...prevValue ]; + + newValue.push(entry); + + if(newValue.length > MESSENGER_HISTORY_MAX) newValue.shift(); + + return newValue; + }); + } + + useNitroEvent(RoomSessionEvent.STARTED, event => setNeedsRoomInsert(true)); + + useMessageEvent(GetGuestRoomResultEvent, event => + { + if(!needsRoomInsert) return; + + const parser = event.getParser(); + + if(roomHistory.length) + { + if(roomHistory[(roomHistory.length - 1)].id === parser.data.roomId) return; + } + + addChatEntry({ id: -1, webId: -1, entityId: -1, name: parser.data.roomName, timestamp: ChatHistoryCurrentDate(), type: ChatEntryType.TYPE_ROOM_INFO, roomId: parser.data.roomId }); + + addRoomHistoryEntry({ id: parser.data.roomId, name: parser.data.roomName }); + + setNeedsRoomInsert(false); + }); + + useMessageEvent(NewConsoleMessageEvent, event => + { + const parser = event.getParser(); + + addMessengerEntry({ id: -1, webId: parser.senderId, entityId: -1, name: '', message: parser.messageText, roomId: -1, timestamp: MessengerHistoryCurrentDate(parser.secondsSinceSent), type: ChatEntryType.TYPE_IM }); + }); + + useMessageEvent(RoomInviteEvent, event => + { + const parser = event.getParser(); + + addMessengerEntry({ id: -1, webId: parser.senderId, entityId: -1, name: '', message: parser.messageText, roomId: -1, timestamp: MessengerHistoryCurrentDate(), type: ChatEntryType.TYPE_IM }); + }); + + return { addChatEntry, chatHistory, roomHistory, messengerHistory }; +} + +export const useChatHistory = () => useBetween(useChatHistoryState); diff --git a/src/hooks/events/index.ts b/src/hooks/events/index.ts new file mode 100644 index 0000000..6d9903b --- /dev/null +++ b/src/hooks/events/index.ts @@ -0,0 +1,4 @@ +export * from './useEventDispatcher'; +export * from './useMessageEvent'; +export * from './useNitroEvent'; +export * from './useUiEvent'; diff --git a/src/hooks/events/useEventDispatcher.tsx b/src/hooks/events/useEventDispatcher.tsx new file mode 100644 index 0000000..60555f2 --- /dev/null +++ b/src/hooks/events/useEventDispatcher.tsx @@ -0,0 +1,31 @@ +import { IEventDispatcher, NitroEvent } from '@nitrots/nitro-renderer'; +import { useEffect } from 'react'; + +export const useEventDispatcher = (type: string | string[], eventDispatcher: IEventDispatcher, handler: (event: T) => void, enabled: boolean = true) => +{ + useEffect(() => + { + if(!enabled) return; + + if(Array.isArray(type)) + { + type.map(name => eventDispatcher.addEventListener(name, handler)); + } + else + { + eventDispatcher.addEventListener(type, handler); + } + + return () => + { + if(Array.isArray(type)) + { + type.map(name => eventDispatcher.removeEventListener(name, handler)); + } + else + { + eventDispatcher.removeEventListener(type, handler); + } + } + }, [ type, eventDispatcher, enabled, handler ]); +} diff --git a/src/hooks/events/useMessageEvent.tsx b/src/hooks/events/useMessageEvent.tsx new file mode 100644 index 0000000..9c4b031 --- /dev/null +++ b/src/hooks/events/useMessageEvent.tsx @@ -0,0 +1,15 @@ +import { GetCommunication, IMessageEvent, MessageEvent } from '@nitrots/nitro-renderer'; +import { useEffect } from 'react'; + +export const useMessageEvent = (eventType: typeof MessageEvent, handler: (event: T) => void) => +{ + useEffect(() => + { + //@ts-ignore + const event = new eventType(handler); + + GetCommunication().registerMessageEvent(event); + + return () => GetCommunication().removeMessageEvent(event); + }, [ eventType, handler ]); +} diff --git a/src/hooks/events/useNitroEvent.tsx b/src/hooks/events/useNitroEvent.tsx new file mode 100644 index 0000000..d8cf60b --- /dev/null +++ b/src/hooks/events/useNitroEvent.tsx @@ -0,0 +1,4 @@ +import { GetEventDispatcher, NitroEvent } from '@nitrots/nitro-renderer'; +import { useEventDispatcher } from './useEventDispatcher'; + +export const useNitroEvent = (type: string | string[], handler: (event: T) => void, enabled = true) => useEventDispatcher(type, GetEventDispatcher(), handler, enabled); diff --git a/src/hooks/events/useUiEvent.tsx b/src/hooks/events/useUiEvent.tsx new file mode 100644 index 0000000..02c0aff --- /dev/null +++ b/src/hooks/events/useUiEvent.tsx @@ -0,0 +1,5 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; +import { UI_EVENT_DISPATCHER } from '../../api'; +import { useEventDispatcher } from './useEventDispatcher'; + +export const useUiEvent = (type: string | string[], handler: (event: T) => void, enabled: boolean = true) => useEventDispatcher(type, UI_EVENT_DISPATCHER, handler, enabled); diff --git a/src/hooks/friends/index.ts b/src/hooks/friends/index.ts new file mode 100644 index 0000000..45c983f --- /dev/null +++ b/src/hooks/friends/index.ts @@ -0,0 +1,2 @@ +export * from './useFriends'; +export * from './useMessenger'; diff --git a/src/hooks/friends/useFriends.ts b/src/hooks/friends/useFriends.ts new file mode 100644 index 0000000..603dc54 --- /dev/null +++ b/src/hooks/friends/useFriends.ts @@ -0,0 +1,252 @@ +import { AcceptFriendMessageComposer, DeclineFriendMessageComposer, FollowFriendMessageComposer, FriendListFragmentEvent, FriendListUpdateComposer, FriendListUpdateEvent, FriendParser, FriendRequestsEvent, GetFriendRequestsComposer, GetSessionDataManager, MessengerInitComposer, MessengerInitEvent, NewFriendRequestEvent, RequestFriendComposer, SetRelationshipStatusComposer } from '@nitrots/nitro-renderer'; +import { useEffect, useMemo, useState } from 'react'; +import { useBetween } from 'use-between'; +import { CloneObject, MessengerFriend, MessengerRequest, MessengerSettings, SendMessageComposer } from '../../api'; +import { useMessageEvent } from '../events'; + +const useFriendsState = () => +{ + const [ friends, setFriends ] = useState([]); + const [ requests, setRequests ] = useState([]); + const [ sentRequests, setSentRequests ] = useState([]); + const [ dismissedRequestIds, setDismissedRequestIds ] = useState([]); + const [ settings, setSettings ] = useState(null); + + const onlineFriends = useMemo(() => + { + const onlineFriends = friends.filter(friend => friend.online); + + onlineFriends.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())); + + return onlineFriends; + }, [ friends ]); + + const offlineFriends = useMemo(() => + { + const offlineFriends = friends.filter(friend => !friend.online); + + offlineFriends.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())); + + return offlineFriends; + }, [ friends ]); + + const followFriend = (friend: MessengerFriend) => SendMessageComposer(new FollowFriendMessageComposer(friend.id)); + + const updateRelationship = (friend: MessengerFriend, type: number) => ((type !== friend.relationshipStatus) && SendMessageComposer(new SetRelationshipStatusComposer(friend.id, type))); + + const getFriend = (userId: number) => + { + for(const friend of friends) + { + if(friend.id === userId) return friend; + } + + return null; + } + + const canRequestFriend = (userId: number) => + { + if(userId === GetSessionDataManager().userId) return false; + + if(getFriend(userId)) return false; + + if(requests.find(request => (request.requesterUserId === userId))) return false; + + if(sentRequests.indexOf(userId) >= 0) return false; + + return true; + } + + const requestFriend = (userId: number, userName: string) => + { + if(!canRequestFriend(userId)) return false; + + setSentRequests(prevValue => + { + const newSentRequests = [ ...prevValue ]; + + newSentRequests.push(userId); + + return newSentRequests; + }); + + SendMessageComposer(new RequestFriendComposer(userName)); + } + + const requestResponse = (requestId: number, flag: boolean) => + { + if(requestId === -1 && !flag) + { + SendMessageComposer(new DeclineFriendMessageComposer(true)); + + setRequests([]); + } + else + { + setRequests(prevValue => + { + const newRequests = [ ...prevValue ]; + const index = newRequests.findIndex(request => (request.id === requestId)); + + if(index === -1) return prevValue; + + if(flag) + { + SendMessageComposer(new AcceptFriendMessageComposer(newRequests[index].id)); + } + else + { + SendMessageComposer(new DeclineFriendMessageComposer(false, newRequests[index].id)); + } + + newRequests.splice(index, 1); + + return newRequests; + }); + } + } + + useMessageEvent(MessengerInitEvent, event => + { + const parser = event.getParser(); + + setSettings(new MessengerSettings( + parser.userFriendLimit, + parser.normalFriendLimit, + parser.extendedFriendLimit, + parser.categories)); + + SendMessageComposer(new GetFriendRequestsComposer()); + }); + + useMessageEvent(FriendListFragmentEvent, event => + { + const parser = event.getParser(); + + setFriends(prevValue => + { + const newValue = [ ...prevValue ]; + + for(const friend of parser.fragment) + { + const index = newValue.findIndex(existingFriend => (existingFriend.id === friend.id)); + const newFriend = new MessengerFriend(); + newFriend.populate(friend); + + if(index > -1) newValue[index] = newFriend; + else newValue.push(newFriend); + } + + return newValue; + }); + }); + + useMessageEvent(FriendListUpdateEvent, event => + { + const parser = event.getParser(); + + setFriends(prevValue => + { + const newValue = [ ...prevValue ]; + + const processUpdate = (friend: FriendParser) => + { + const index = newValue.findIndex(existingFriend => (existingFriend.id === friend.id)); + + if(index === -1) + { + const newFriend = new MessengerFriend(); + newFriend.populate(friend); + + newValue.unshift(newFriend); + } + else + { + newValue[index].populate(friend); + } + } + + for(const friend of parser.addedFriends) processUpdate(friend); + + for(const friend of parser.updatedFriends) processUpdate(friend); + + for(const removedFriendId of parser.removedFriendIds) + { + const index = newValue.findIndex(existingFriend => (existingFriend.id === removedFriendId)); + + if(index > -1) newValue.splice(index, 1); + } + + return newValue; + }); + }); + + useMessageEvent(FriendRequestsEvent, event => + { + const parser = event.getParser(); + + setRequests(prevValue => + { + const newValue = [ ...prevValue ]; + + for(const request of parser.requests) + { + const index = newValue.findIndex(existing => (existing.requesterUserId === request.requesterUserId)); + + if(index > 0) + { + newValue[index] = CloneObject(newValue[index]); + newValue[index].populate(request); + } + else + { + const newRequest = new MessengerRequest(); + newRequest.populate(request); + + newValue.push(newRequest); + } + } + + return newValue; + }); + }); + + useMessageEvent(NewFriendRequestEvent, event => + { + const parser = event.getParser(); + const request = parser.request; + + setRequests(prevValue => + { + const newRequests = [ ...prevValue ]; + + const index = newRequests.findIndex(existing => (existing.requesterUserId === request.requesterUserId)); + + if(index === -1) + { + const newRequest = new MessengerRequest(); + newRequest.populate(request); + + newRequests.push(newRequest); + } + + return newRequests; + }); + }); + + useEffect(() => + { + SendMessageComposer(new MessengerInitComposer()); + + const interval = setInterval(() => SendMessageComposer(new FriendListUpdateComposer()), 120000); + + return () => + { + clearInterval(interval); + } + }, []); + + return { friends, requests, sentRequests, dismissedRequestIds, setDismissedRequestIds, settings, onlineFriends, offlineFriends, getFriend, canRequestFriend, requestFriend, requestResponse, followFriend, updateRelationship }; +} + +export const useFriends = () => useBetween(useFriendsState); diff --git a/src/hooks/friends/useMessenger.ts b/src/hooks/friends/useMessenger.ts new file mode 100644 index 0000000..2b668d5 --- /dev/null +++ b/src/hooks/friends/useMessenger.ts @@ -0,0 +1,187 @@ +import { GetSessionDataManager, NewConsoleMessageEvent, RoomInviteErrorEvent, RoomInviteEvent, SendMessageComposer as SendMessageComposerPacket } from '@nitrots/nitro-renderer'; +import { useEffect, useMemo, useState } from 'react'; +import { useBetween } from 'use-between'; +import { CloneObject, LocalizeText, MessengerIconState, MessengerThread, MessengerThreadChat, NotificationAlertType, PlaySound, SendMessageComposer, SoundNames } from '../../api'; +import { useMessageEvent } from '../events'; +import { useNotification } from '../notification'; +import { useFriends } from './useFriends'; + +const useMessengerState = () => +{ + const [ messageThreads, setMessageThreads ] = useState([]); + const [ activeThreadId, setActiveThreadId ] = useState(-1); + const [ hiddenThreadIds, setHiddenThreadIds ] = useState([]); + const [ iconState, setIconState ] = useState(MessengerIconState.HIDDEN); + const { getFriend = null } = useFriends(); + const { simpleAlert = null } = useNotification(); + + const visibleThreads = useMemo(() => messageThreads.filter(thread => (hiddenThreadIds.indexOf(thread.threadId) === -1)), [ messageThreads, hiddenThreadIds ]); + const activeThread = useMemo(() => ((activeThreadId > 0) && visibleThreads.find(thread => (thread.threadId === activeThreadId) || null)), [ activeThreadId, visibleThreads ]); + + const getMessageThread = (userId: number) => + { + let thread = messageThreads.find(thread => (thread.participant && (thread.participant.id === userId))); + + if(!thread) + { + const friend = getFriend(userId); + + if(!friend) return null; + + thread = new MessengerThread(friend); + + thread.addMessage(null, LocalizeText('messenger.moderationinfo'), 0, null, MessengerThreadChat.SECURITY_NOTIFICATION); + + thread.setRead(); + + setMessageThreads(prevValue => + { + const newValue = [ ...prevValue ]; + + newValue.push(thread); + + return newValue; + }); + } + else + { + const hiddenIndex = hiddenThreadIds.indexOf(thread.threadId); + + if(hiddenIndex >= 0) + { + setHiddenThreadIds(prevValue => + { + const newValue = [ ...prevValue ]; + + newValue.splice(hiddenIndex, 1); + + return newValue; + }) + } + } + + return thread; + } + + const closeThread = (threadId: number) => + { + setHiddenThreadIds(prevValue => + { + const newValue = [ ...prevValue ]; + + if(newValue.indexOf(threadId) >= 0) return prevValue; + + newValue.push(threadId); + + return newValue; + }); + + if(activeThreadId === threadId) setActiveThreadId(-1); + } + + const sendMessage = (thread: MessengerThread, senderId: number, messageText: string, secondsSinceSent: number = 0, extraData: string = null, messageType: number = MessengerThreadChat.CHAT) => + { + if(!thread || !messageText || !messageText.length) return; + + const ownMessage = (senderId === GetSessionDataManager().userId); + + if(ownMessage && (messageText.length <= 255)) SendMessageComposer(new SendMessageComposerPacket(thread.participant.id, messageText)); + + setMessageThreads(prevValue => + { + const newValue = [ ...prevValue ]; + const index = newValue.findIndex(newThread => (newThread.threadId === thread.threadId)); + + if(index === -1) return prevValue; + + thread = CloneObject(newValue[index]); + + if(ownMessage && (thread.groups.length === 1)) PlaySound(SoundNames.MESSENGER_NEW_THREAD); + + thread.addMessage(((messageType === MessengerThreadChat.ROOM_INVITE) ? null : senderId), messageText, secondsSinceSent, extraData, messageType); + + if(activeThreadId === thread.threadId) thread.setRead(); + + newValue[index] = thread; + + if(!ownMessage && thread.unread) PlaySound(SoundNames.MESSENGER_MESSAGE_RECEIVED); + + return newValue; + }); + } + + useMessageEvent(NewConsoleMessageEvent, event => + { + const parser = event.getParser(); + const thread = getMessageThread(parser.senderId); + + if(!thread) return; + + sendMessage(thread, parser.senderId, parser.messageText, parser.secondsSinceSent, parser.extraData); + }); + + useMessageEvent(RoomInviteEvent, event => + { + const parser = event.getParser(); + const thread = getMessageThread(parser.senderId); + + if(!thread) return; + + sendMessage(thread, parser.senderId, parser.messageText, 0, null, MessengerThreadChat.ROOM_INVITE); + }); + + useMessageEvent(RoomInviteErrorEvent, event => + { + const parser = event.getParser(); + + simpleAlert(`Received room invite error: ${ parser.errorCode },recipients: ${ parser.failedRecipients }`, NotificationAlertType.DEFAULT, null, null, LocalizeText('friendlist.alert.title')); + }); + + useEffect(() => + { + if(activeThreadId <= 0) return; + + setMessageThreads(prevValue => + { + const newValue = [ ...prevValue ]; + const index = newValue.findIndex(newThread => (newThread.threadId === activeThreadId)); + + if(index >= 0) + { + newValue[index] = CloneObject(newValue[index]); + + newValue[index].setRead(); + } + + return newValue; + }); + }, [ activeThreadId ]); + + useEffect(() => + { + setIconState(prevValue => + { + if(!visibleThreads.length) return MessengerIconState.HIDDEN; + + let isUnread = false; + + for(const thread of visibleThreads) + { + if(thread.unreadCount > 0) + { + isUnread = true; + + break; + } + } + + if(isUnread) return MessengerIconState.UNREAD; + + return MessengerIconState.SHOW; + }); + }, [ visibleThreads ]); + + return { messageThreads, activeThread, iconState, visibleThreads, getMessageThread, setActiveThreadId, closeThread, sendMessage }; +} + +export const useMessenger = () => useBetween(useMessengerState); diff --git a/src/hooks/game-center/index.ts b/src/hooks/game-center/index.ts new file mode 100644 index 0000000..59f0472 --- /dev/null +++ b/src/hooks/game-center/index.ts @@ -0,0 +1 @@ +export * from './useGameCenter'; diff --git a/src/hooks/game-center/useGameCenter.ts b/src/hooks/game-center/useGameCenter.ts new file mode 100644 index 0000000..0b9dbcb --- /dev/null +++ b/src/hooks/game-center/useGameCenter.ts @@ -0,0 +1,83 @@ +import { Game2AccountGameStatusMessageEvent, Game2AccountGameStatusMessageParser, GameConfigurationData, GameListMessageEvent, GameStatusMessageEvent, GetGameListMessageComposer, LoadGameUrlEvent } from '@nitrots/nitro-renderer'; +import { useEffect, useState } from 'react'; +import { useBetween } from 'use-between'; +import { SendMessageComposer, VisitDesktop } from '../../api'; +import { useMessageEvent } from '../events'; + +const useGameCenterState = () => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ games, setGames ] = useState(null); + const [ selectedGame, setSelectedGame ] = useState(null); + const [ accountStatus, setAccountStatus ] = useState(null); + const [ gameOffline, setGameOffline ] = useState(false); + const [ gameURL, setGameURL ] = useState(null); + + useMessageEvent(GameListMessageEvent, event => + { + let parser = event.getParser(); + + if(!parser || parser && !parser.games.length) return; + + setSelectedGame(parser.games[0]); + + setGames(parser.games); + }); + + useMessageEvent(Game2AccountGameStatusMessageEvent, event => + { + let parser = event.getParser(); + + if(!parser) return; + + setAccountStatus(parser); + }); + + useMessageEvent(GameStatusMessageEvent, event => + { + let parser = event.getParser(); + + if(!parser) return; + + setGameOffline(parser.isInMaintenance); + }) + + useMessageEvent(LoadGameUrlEvent, event => + { + let parser = event.getParser(); + + if(!parser) return; + + switch(parser.gameTypeId) + { + case 2: + return console.log('snowwar') + default: + return setGameURL(parser.url); + } + }); + + useEffect(()=> + { + if(isVisible) + { + SendMessageComposer(new GetGameListMessageComposer()); + VisitDesktop(); + } + else + { + // dispose or wtv + } + },[ isVisible ]); + + return { + isVisible, setIsVisible, + games, + accountStatus, + selectedGame, setSelectedGame, + gameOffline, + gameURL, setGameURL + } +} + +export const useGameCenter = () => useBetween(useGameCenterState); diff --git a/src/hooks/groups/index.ts b/src/hooks/groups/index.ts new file mode 100644 index 0000000..95ef733 --- /dev/null +++ b/src/hooks/groups/index.ts @@ -0,0 +1 @@ +export * from './useGroup'; diff --git a/src/hooks/groups/useGroup.ts b/src/hooks/groups/useGroup.ts new file mode 100644 index 0000000..7a9fd22 --- /dev/null +++ b/src/hooks/groups/useGroup.ts @@ -0,0 +1,55 @@ +import { GroupBadgePartsComposer, GroupBadgePartsEvent } from '@nitrots/nitro-renderer'; +import { useEffect, useState } from 'react'; +import { useBetween } from 'use-between'; +import { IGroupCustomize, SendMessageComposer } from '../../api'; +import { useMessageEvent } from '../events'; + +const useGroupState = () => +{ + const [ groupCustomize, setGroupCustomize ] = useState(null); + + useMessageEvent(GroupBadgePartsEvent, event => + { + const parser = event.getParser(); + + const customize: IGroupCustomize = { + badgeBases: [], + badgeSymbols: [], + badgePartColors: [], + groupColorsA: [], + groupColorsB: [] + }; + + parser.bases.forEach((images, id) => customize.badgeBases.push({ id, images })); + parser.symbols.forEach((images, id) => customize.badgeSymbols.push({ id, images })); + parser.partColors.forEach((color, id) => customize.badgePartColors.push({ id, color })); + parser.colorsA.forEach((color, id) => customize.groupColorsA.push({ id, color })); + parser.colorsB.forEach((color, id) => customize.groupColorsB.push({ id, color })); + + const CompareId = (a: { id: number }, b: { id: number }) => + { + if(a.id < b.id) return -1; + + if(a.id > b.id) return 1; + + return 0; + } + + customize.badgeBases.sort(CompareId); + customize.badgeSymbols.sort(CompareId); + customize.badgePartColors.sort(CompareId); + customize.groupColorsA.sort(CompareId); + customize.groupColorsB.sort(CompareId); + + setGroupCustomize(customize); + }); + + useEffect(() => + { + SendMessageComposer(new GroupBadgePartsComposer()); + }, []); + + return { groupCustomize }; +} + +export const useGroup = () => useBetween(useGroupState); diff --git a/src/hooks/help/index.ts b/src/hooks/help/index.ts new file mode 100644 index 0000000..f03e754 --- /dev/null +++ b/src/hooks/help/index.ts @@ -0,0 +1 @@ +export * from './useHelp'; diff --git a/src/hooks/help/useHelp.ts b/src/hooks/help/useHelp.ts new file mode 100644 index 0000000..1c74b62 --- /dev/null +++ b/src/hooks/help/useHelp.ts @@ -0,0 +1,149 @@ +import { CallForHelpDisabledNotifyMessageEvent, CallForHelpPendingCallsDeletedMessageEvent, CallForHelpPendingCallsMessageEvent, CallForHelpReplyMessageEvent, CallForHelpResultMessageEvent, DeletePendingCallsForHelpMessageComposer, GetPendingCallsForHelpMessageComposer, IssueCloseNotificationMessageEvent, SanctionStatusEvent, SanctionStatusMessageParser } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { useBetween } from 'use-between'; +import { CallForHelpResult, GetCloseReasonKey, IHelpReport, LocalizeText, NotificationAlertType, ReportState, ReportType, SendMessageComposer } from '../../api'; +import { useMessageEvent } from '../events'; +import { useNotification } from '../notification'; + +const useHelpState = () => +{ + const [ activeReport, setActiveReport ] = useState(null); + const [ sanctionInfo, setSanctionInfo ] = useState(null); + const { simpleAlert = null, showConfirm = null } = useNotification(); + + const report = (type: number, options: Partial) => + { + const newReport: IHelpReport = { + reportType: type, + reportedUserId: -1, + reportedChats: [], + cfhCategory: -1, + cfhTopic: -1, + roomId: -1, + roomName: '', + messageId: -1, + threadId: -1, + groupId: -1, + extraData: '', + roomObjectId: -1, + message: '', + currentStep: 0 + }; + + switch(type) + { + case ReportType.BULLY: + case ReportType.EMERGENCY: + case ReportType.IM: + newReport.reportedUserId = options.reportedUserId; + newReport.currentStep = ReportState.SELECT_CHATS; + break; + case ReportType.ROOM: + newReport.roomId = options.roomId; + newReport.roomName = options.roomName; + newReport.currentStep = ReportState.SELECT_TOPICS; + break; + case ReportType.THREAD: + newReport.groupId = options.groupId; + newReport.threadId = options.threadId; + newReport.currentStep = ReportState.SELECT_TOPICS; + break; + case ReportType.MESSAGE: + newReport.groupId = options.groupId; + newReport.threadId = options.threadId; + newReport.messageId = options.messageId; + newReport.currentStep = ReportState.SELECT_TOPICS; + break; + case ReportType.PHOTO: + newReport.extraData = options.extraData; + newReport.roomId = options.roomId; + newReport.reportedUserId = options.reportedUserId; + newReport.roomObjectId = options.roomObjectId; + newReport.currentStep = ReportState.SELECT_TOPICS; + break; + case ReportType.GUIDE: + break; + } + + setActiveReport(newReport); + } + + useMessageEvent(CallForHelpResultMessageEvent, event => + { + const parser = event.getParser(); + + let message = parser.messageText; + + switch(parser.resultType) + { + case CallForHelpResult.TOO_MANY_PENDING_CALLS_CODE: + SendMessageComposer(new GetPendingCallsForHelpMessageComposer()); + simpleAlert(LocalizeText('help.cfh.error.pending'), NotificationAlertType.MODERATION, null, null, LocalizeText('help.cfh.error.title')); + break; + case CallForHelpResult.HAS_ABUSIVE_CALL_CODE: + simpleAlert(LocalizeText('help.cfh.error.abusive'), NotificationAlertType.MODERATION, null, null, LocalizeText('help.cfh.error.title')); + break; + default: + if(message.trim().length === 0) + { + message = LocalizeText('help.cfh.sent.text'); + } + + simpleAlert(message, NotificationAlertType.MODERATION, null, null, LocalizeText('help.cfh.sent.title')); + } + }); + + useMessageEvent(IssueCloseNotificationMessageEvent, event => + { + const parser = event.getParser(); + + const message = parser.messageText.length === 0 ? LocalizeText('help.cfh.closed.' + GetCloseReasonKey(parser.closeReason)) : parser.messageText; + + simpleAlert(message, NotificationAlertType.MODERATION, null, null, LocalizeText('mod.alert.title')); + }); + + useMessageEvent(CallForHelpPendingCallsMessageEvent, event => + { + const parser = event.getParser(); + + if(parser.count > 0) + { + showConfirm(LocalizeText('help.emergency.pending.title') + '\n' + parser.pendingCalls[0].message, () => + { + SendMessageComposer(new DeletePendingCallsForHelpMessageComposer()); + }, null, LocalizeText('help.emergency.pending.button.discard'), LocalizeText('help.emergency.pending.button.keep'), LocalizeText('help.emergency.pending.message.subtitle')); + } + }); + + useMessageEvent(CallForHelpPendingCallsDeletedMessageEvent, event => + { + const message = 'Your pending calls were deleted'; // todo: add localization + + simpleAlert(message, NotificationAlertType.MODERATION, null, null, LocalizeText('mod.alert.title')); + }); + + useMessageEvent(CallForHelpReplyMessageEvent, event => + { + const parser = event.getParser(); + + simpleAlert(parser.message, NotificationAlertType.MODERATION, null, null, LocalizeText('help.cfh.reply.title')); + }); + + useMessageEvent(CallForHelpDisabledNotifyMessageEvent, event => + { + const parser = event.getParser(); + + simpleAlert(LocalizeText('help.emergency.global_mute.message'), NotificationAlertType.MODERATION, parser.infoUrl, LocalizeText('help.emergency.global_mute.link'), LocalizeText('help.emergency.global_mute.subtitle')) + }); + + useMessageEvent(SanctionStatusEvent, event => + { + const parser = event.getParser(); + + setSanctionInfo(parser); + }); + + return { activeReport, setActiveReport, sanctionInfo, setSanctionInfo, report }; +} + +export const useHelp = () => useBetween(useHelpState); diff --git a/src/hooks/index.ts b/src/hooks/index.ts new file mode 100644 index 0000000..58b4afc --- /dev/null +++ b/src/hooks/index.ts @@ -0,0 +1,25 @@ +export * from './UseMountEffect'; +export * from './achievements'; +export * from './avatar-editor'; +export * from './camera'; +export * from './catalog'; +export * from './chat-history'; +export * from './events'; +export * from './friends'; +export * from './game-center'; +export * from './groups'; +export * from './help'; +export * from './inventory'; +export * from './mod-tools'; +export * from './navigator'; +export * from './notification'; +export * from './purse'; +export * from './rooms'; +export * from './rooms/engine'; +export * from './rooms/promotes'; +export * from './rooms/widgets'; +export * from './rooms/widgets/furniture'; +export * from './session'; +export * from './useLocalStorage'; +export * from './useSharedVisibility'; +export * from './wired'; diff --git a/src/hooks/inventory/index.ts b/src/hooks/inventory/index.ts new file mode 100644 index 0000000..4e70819 --- /dev/null +++ b/src/hooks/inventory/index.ts @@ -0,0 +1,6 @@ +export * from './useInventoryBadges'; +export * from './useInventoryBots'; +export * from './useInventoryFurni'; +export * from './useInventoryPets'; +export * from './useInventoryTrade'; +export * from './useInventoryUnseenTracker'; diff --git a/src/hooks/inventory/useInventoryBadges.ts b/src/hooks/inventory/useInventoryBadges.ts new file mode 100644 index 0000000..766c668 --- /dev/null +++ b/src/hooks/inventory/useInventoryBadges.ts @@ -0,0 +1,152 @@ +import { BadgeReceivedEvent, BadgesEvent, RequestBadgesComposer, SetActivatedBadgesComposer } from '@nitrots/nitro-renderer'; +import { useEffect, useState } from 'react'; +import { useBetween } from 'use-between'; +import { GetConfigurationValue, SendMessageComposer, UnseenItemCategory } from '../../api'; +import { useMessageEvent } from '../events'; +import { useSharedVisibility } from '../useSharedVisibility'; +import { useInventoryUnseenTracker } from './useInventoryUnseenTracker'; + +const useInventoryBadgesState = () => +{ + const [ needsUpdate, setNeedsUpdate ] = useState(true); + const [ badgeCodes, setBadgeCodes ] = useState([]); + const [ badgeIds, setBadgeIds ] = useState>(new Map()); + const [ activeBadgeCodes, setActiveBadgeCodes ] = useState([]); + const [ selectedBadgeCode, setSelectedBadgeCode ] = useState(null); + const { isVisible = false, activate = null, deactivate = null } = useSharedVisibility(); + const { isUnseen = null, resetCategory = null } = useInventoryUnseenTracker(); + + const maxBadgeCount = GetConfigurationValue('user.badges.max.slots', 5); + const isWearingBadge = (badgeCode: string) => (activeBadgeCodes.indexOf(badgeCode) >= 0); + const canWearBadges = () => (activeBadgeCodes.length < maxBadgeCount); + + const toggleBadge = (badgeCode: string) => + { + setActiveBadgeCodes(prevValue => + { + const newValue = [ ...prevValue ]; + + const index = newValue.indexOf(badgeCode); + + if(index === -1) + { + if(!canWearBadges()) return prevValue; + + newValue.push(badgeCode); + } + else + { + newValue.splice(index, 1); + } + + const composer = new SetActivatedBadgesComposer(); + + for(let i = 0; i < maxBadgeCount; i++) composer.addActivatedBadge(newValue[i] ?? ''); + + SendMessageComposer(composer); + + return newValue; + }); + } + + const getBadgeId = (badgeCode: string) => + { + const index = badgeCodes.indexOf(badgeCode); + + if(index === -1) return 0; + + return (badgeIds.get(badgeCode) ?? 0); + } + + useMessageEvent(BadgesEvent, event => + { + const parser = event.getParser(); + const badgesToAdd: string[] = []; + + setBadgeIds(prevValue => + { + const newValue = new Map(prevValue); + + parser.getAllBadgeCodes().forEach(code => + { + const exists = badgeCodes.indexOf(code) >= 0; + const badgeId = parser.getBadgeId(code); + + newValue.set(code, badgeId); + + if(exists) return; + + badgesToAdd.push(code); + }); + + return newValue; + }); + + setActiveBadgeCodes(parser.getActiveBadgeCodes()); + setBadgeCodes(prev => [ ...prev, ...badgesToAdd ]); + }); + + useMessageEvent(BadgeReceivedEvent, event => + { + const parser = event.getParser(); + const unseen = isUnseen(UnseenItemCategory.BADGE, parser.badgeId); + + setBadgeCodes(prevValue => + { + const newValue = [ ...prevValue ]; + + if(unseen) newValue.unshift(parser.badgeCode) + else newValue.push(parser.badgeCode); + + return newValue; + }); + + setBadgeIds(prevValue => + { + const newValue = new Map(prevValue); + + newValue.set(parser.badgeCode, parser.badgeId); + + return newValue; + }); + }); + + useEffect(() => + { + if(!badgeCodes || !badgeCodes.length) return; + + setSelectedBadgeCode(prevValue => + { + let newValue = prevValue; + + if(newValue && (badgeCodes.indexOf(newValue) === -1)) newValue = null; + + if(!newValue) newValue = badgeCodes[0]; + + return newValue; + }); + }, [ badgeCodes ]); + + useEffect(() => + { + if(!isVisible) return; + + return () => + { + resetCategory(UnseenItemCategory.BADGE); + } + }, [ isVisible, resetCategory ]); + + useEffect(() => + { + if(!isVisible || !needsUpdate) return; + + SendMessageComposer(new RequestBadgesComposer()); + + setNeedsUpdate(false); + }, [ isVisible, needsUpdate ]); + + return { badgeCodes, activeBadgeCodes, selectedBadgeCode, setSelectedBadgeCode, isWearingBadge, canWearBadges, toggleBadge, getBadgeId, activate, deactivate }; +} + +export const useInventoryBadges = () => useBetween(useInventoryBadgesState); diff --git a/src/hooks/inventory/useInventoryBots.ts b/src/hooks/inventory/useInventoryBots.ts new file mode 100644 index 0000000..55663a5 --- /dev/null +++ b/src/hooks/inventory/useInventoryBots.ts @@ -0,0 +1,158 @@ +import { BotAddedToInventoryEvent, BotData, BotInventoryMessageEvent, BotRemovedFromInventoryEvent, CreateLinkEvent, GetBotInventoryComposer } from '@nitrots/nitro-renderer'; +import { useEffect, useState } from 'react'; +import { useBetween } from 'use-between'; +import { IBotItem, SendMessageComposer, UnseenItemCategory, cancelRoomObjectPlacement, getPlacingItemId } from '../../api'; +import { useMessageEvent } from '../events'; +import { useSharedVisibility } from '../useSharedVisibility'; +import { useInventoryUnseenTracker } from './useInventoryUnseenTracker'; + +const useInventoryBotsState = () => +{ + const [ needsUpdate, setNeedsUpdate ] = useState(true); + const [ botItems, setBotItems ] = useState([]); + const [ selectedBot, setSelectedBot ] = useState(null); + const { isVisible = false, activate = null, deactivate = null } = useSharedVisibility(); + const { isUnseen = null, resetCategory = null } = useInventoryUnseenTracker(); + + useMessageEvent(BotInventoryMessageEvent, event => + { + const parser = event.getParser(); + + setBotItems(prevValue => + { + const newValue = [ ...prevValue ]; + const existingIds = newValue.map(item => item.botData.id); + const addedDatas: BotData[] = []; + + for(const botData of parser.items.values()) ((existingIds.indexOf(botData.id) === -1) && addedDatas.push(botData)); + + for(const existingId of existingIds) + { + let remove = true; + + for(const botData of parser.items.values()) + { + if(botData.id === existingId) + { + remove = false; + + break; + } + } + + if(!remove) continue; + + const index = newValue.findIndex(item => (item.botData.id === existingId)); + const botItem = newValue[index]; + + if((index === -1) || !botItem) continue; + + if(getPlacingItemId() === botItem.botData.id) + { + cancelRoomObjectPlacement(); + + CreateLinkEvent('inventory/open'); + } + + newValue.splice(index, 1); + } + + for(const botData of addedDatas) + { + const botItem = { botData } as IBotItem; + const unseen = isUnseen(UnseenItemCategory.BOT, botData.id); + + if(unseen) newValue.unshift(botItem); + else newValue.push(botItem); + } + + return newValue; + }); + }); + + useMessageEvent(BotAddedToInventoryEvent, event => + { + const parser = event.getParser(); + + setBotItems(prevValue => + { + const newValue = [ ...prevValue ]; + + const index = newValue.findIndex(item => (item.botData.id === parser.item.id)); + + if(index >= 0) return prevValue; + + const botItem = { botData: parser.item } as IBotItem; + const unseen = isUnseen(UnseenItemCategory.BOT, botItem.botData.id); + + if(unseen) newValue.unshift(botItem); + else newValue.push(botItem); + + return newValue; + }); + }); + + useMessageEvent(BotRemovedFromInventoryEvent, event => + { + const parser = event.getParser(); + + setBotItems(prevValue => + { + const newValue = [ ...prevValue ]; + + const index = newValue.findIndex(item => (item.botData.id === parser.itemId)); + + if(index === -1) return prevValue; + + newValue.splice(index, 1); + + if(getPlacingItemId() === parser.itemId) + { + cancelRoomObjectPlacement(); + + CreateLinkEvent('inventory/show'); + } + + return newValue; + }); + }); + + useEffect(() => + { + if(!botItems || !botItems.length) return; + + setSelectedBot(prevValue => + { + let newValue = prevValue; + + if(newValue && (botItems.indexOf(newValue) === -1)) newValue = null; + + if(!newValue) newValue = botItems[0]; + + return newValue; + }); + }, [ botItems ]); + + useEffect(() => + { + if(!isVisible) return; + + return () => + { + resetCategory(UnseenItemCategory.BOT); + } + }, [ isVisible, resetCategory ]); + + useEffect(() => + { + if(!isVisible || !needsUpdate) return; + + SendMessageComposer(new GetBotInventoryComposer()); + + setNeedsUpdate(false); + }, [ isVisible, needsUpdate ]); + + return { botItems, selectedBot, setSelectedBot, activate, deactivate }; +} + +export const useInventoryBots = () => useBetween(useInventoryBotsState); diff --git a/src/hooks/inventory/useInventoryFurni.ts b/src/hooks/inventory/useInventoryFurni.ts new file mode 100644 index 0000000..b8a6d0d --- /dev/null +++ b/src/hooks/inventory/useInventoryFurni.ts @@ -0,0 +1,298 @@ +import { CreateLinkEvent, FurnitureListAddOrUpdateEvent, FurnitureListComposer, FurnitureListEvent, FurnitureListInvalidateEvent, FurnitureListItemParser, FurnitureListRemovedEvent, FurniturePostItPlacedEvent } from '@nitrots/nitro-renderer'; +import { useEffect, useState } from 'react'; +import { useBetween } from 'use-between'; +import { CloneObject, DispatchUiEvent, FurnitureItem, GroupItem, SendMessageComposer, UnseenItemCategory, addFurnitureItem, attemptItemPlacement, cancelRoomObjectPlacement, getAllItemIds, getPlacingItemId, mergeFurniFragments } from '../../api'; +import { InventoryFurniAddedEvent } from '../../events'; +import { useMessageEvent } from '../events'; +import { useSharedVisibility } from '../useSharedVisibility'; +import { useInventoryUnseenTracker } from './useInventoryUnseenTracker'; + +let furniMsgFragments: Map[] = null; + +const useInventoryFurniState = () => +{ + const [ needsUpdate, setNeedsUpdate ] = useState(true); + const [ groupItems, setGroupItems ] = useState([]); + const [ selectedItem, setSelectedItem ] = useState(null); + const { isVisible = false, activate = null, deactivate = null } = useSharedVisibility(); + const { isUnseen = null, resetCategory = null } = useInventoryUnseenTracker(); + + const getItemsByType = (type: number) => + { + if(!groupItems || !groupItems.length) return; + + return groupItems.filter((i) => i.type === type); + } + + const getWallItemById = (id: number) => + { + if(!groupItems || !groupItems.length) return; + + for(const groupItem of groupItems) + { + const item = groupItem.getItemById(id); + + if(item && item.isWallItem) return groupItem; + } + + return null; + } + + const getFloorItemById = (id: number) => + { + if(!groupItems || !groupItems.length) return; + + for(const groupItem of groupItems) + { + const item = groupItem.getItemById(id); + + if(item && !item.isWallItem) return groupItem; + } + + return null; + } + + useMessageEvent(FurnitureListAddOrUpdateEvent, event => + { + const parser = event.getParser(); + + setGroupItems(prevValue => + { + const newValue = [ ...prevValue ]; + + for(const item of parser.items) + { + let i = 0; + let groupItem: GroupItem = null; + + while(i < newValue.length) + { + const group = newValue[i]; + + let j = 0; + + while(j < group.items.length) + { + const furniture = group.items[j]; + + if(furniture.id === item.itemId) + { + furniture.update(item); + + const newFurniture = [ ...group.items ]; + + newFurniture[j] = furniture; + + group.items = newFurniture; + + groupItem = group; + + break; + } + + j++ + } + + if(groupItem) break; + + i++; + } + + if(groupItem) + { + groupItem.hasUnseenItems = true; + + newValue[i] = CloneObject(groupItem); + } + else + { + const furniture = new FurnitureItem(item); + + addFurnitureItem(newValue, furniture, isUnseen(UnseenItemCategory.FURNI, item.itemId)); + + DispatchUiEvent(new InventoryFurniAddedEvent(furniture.id, furniture.type, furniture.category)); + } + } + + return newValue; + }); + }); + + useMessageEvent(FurnitureListEvent, event => + { + const parser = event.getParser(); + + if(!furniMsgFragments) furniMsgFragments = new Array(parser.totalFragments); + + const fragment = mergeFurniFragments(parser.fragment, parser.totalFragments, parser.fragmentNumber, furniMsgFragments); + + if(!fragment) return; + + setGroupItems(prevValue => + { + const newValue = [ ...prevValue ]; + const existingIds = getAllItemIds(newValue); + + for(const existingId of existingIds) + { + if(fragment.get(existingId)) continue; + + let index = 0; + + while(index < newValue.length) + { + const group = newValue[index]; + const item = group.remove(existingId); + + if(!item) + { + index++; + + continue; + } + + if(getPlacingItemId() === item.ref) + { + cancelRoomObjectPlacement(); + + if(!attemptItemPlacement(group)) + { + CreateLinkEvent('inventory/show'); + } + } + + if(group.getTotalCount() <= 0) + { + newValue.splice(index, 1); + + group.dispose(); + } + + break; + } + } + + for(const itemId of fragment.keys()) + { + if(existingIds.indexOf(itemId) >= 0) continue; + + const parser = fragment.get(itemId); + + if(!parser) continue; + + const item = new FurnitureItem(parser); + + addFurnitureItem(newValue, item, isUnseen(UnseenItemCategory.FURNI, itemId)); + + DispatchUiEvent(new InventoryFurniAddedEvent(item.id, item.type, item.category)); + + } + + return newValue; + }); + + furniMsgFragments = null; + }); + + useMessageEvent(FurnitureListInvalidateEvent, event => + { + setNeedsUpdate(true); + }); + + useMessageEvent(FurnitureListRemovedEvent, event => + { + const parser = event.getParser(); + + setGroupItems(prevValue => + { + const newValue = [ ...prevValue ]; + + let index = 0; + + while(index < newValue.length) + { + const group = newValue[index]; + const item = group.remove(parser.itemId); + + if(!item) + { + index++; + + continue; + } + + if(getPlacingItemId() === item.ref) + { + cancelRoomObjectPlacement(); + + if(!attemptItemPlacement(group)) CreateLinkEvent('inventory/show'); + } + + if(group.getTotalCount() <= 0) + { + newValue.splice(index, 1); + + group.dispose(); + } + + break; + } + + return newValue; + }); + }); + + useMessageEvent(FurniturePostItPlacedEvent, event => + { + + }); + + useEffect(() => + { + if(!groupItems || !groupItems.length) return; + + setSelectedItem(prevValue => + { + let newValue = prevValue; + + if(newValue && (groupItems.indexOf(newValue) === -1)) newValue = null; + + if(!newValue) newValue = groupItems[0]; + + return newValue; + }); + }, [ groupItems ]); + + useEffect(() => + { + if(!isVisible) return; + + return () => + { + if(resetCategory(UnseenItemCategory.FURNI)) + { + setGroupItems(prevValue => + { + const newValue = [ ...prevValue ]; + + for(const newGroup of newValue) newGroup.hasUnseenItems = false; + + return newValue; + }); + } + } + }, [ isVisible, resetCategory ]); + + useEffect(() => + { + if(!isVisible || !needsUpdate) return; + + SendMessageComposer(new FurnitureListComposer()); + + setNeedsUpdate(false); + }, [ isVisible, needsUpdate ]); + + return { isVisible, groupItems, setGroupItems, selectedItem, setSelectedItem, activate, deactivate, getWallItemById, getFloorItemById, getItemsByType }; +} + +export const useInventoryFurni = () => useBetween(useInventoryFurniState); diff --git a/src/hooks/inventory/useInventoryPets.ts b/src/hooks/inventory/useInventoryPets.ts new file mode 100644 index 0000000..d95e8bf --- /dev/null +++ b/src/hooks/inventory/useInventoryPets.ts @@ -0,0 +1,107 @@ +import { PetAddedToInventoryEvent, PetData, PetInventoryEvent, PetRemovedFromInventory, RequestPetsComposer } from '@nitrots/nitro-renderer'; +import { useEffect, useState } from 'react'; +import { useBetween } from 'use-between'; +import { addSinglePetItem, IPetItem, mergePetFragments, processPetFragment, removePetItemById, SendMessageComposer, UnseenItemCategory } from '../../api'; +import { useMessageEvent } from '../events'; +import { useSharedVisibility } from '../useSharedVisibility'; +import { useInventoryUnseenTracker } from './useInventoryUnseenTracker'; + +let petMsgFragments: Map[] = null; + +const useInventoryPetsState = () => +{ + const [ needsUpdate, setNeedsUpdate ] = useState(true); + const [ petItems, setPetItems ] = useState([]); + const [ selectedPet, setSelectedPet ] = useState(null); + const { isVisible = false, activate = null, deactivate = null } = useSharedVisibility(); + const { isUnseen = null, resetCategory = null } = useInventoryUnseenTracker(); + + useMessageEvent(PetInventoryEvent, event => + { + const parser = event.getParser(); + + if(!petMsgFragments) petMsgFragments = new Array(parser.totalFragments); + + const fragment = mergePetFragments(parser.fragment, parser.totalFragments, parser.fragmentNumber, petMsgFragments); + + if(!fragment) return; + + setPetItems(prevValue => + { + const newValue = [ ...prevValue ]; + + processPetFragment(newValue, fragment, isUnseen); + + return newValue; + }); + + petMsgFragments = null; + }); + + useMessageEvent(PetAddedToInventoryEvent, event => + { + const parser = event.getParser(); + + setPetItems(prevValue => + { + const newValue = [ ...prevValue ]; + + addSinglePetItem(parser.pet, newValue, isUnseen(UnseenItemCategory.PET, parser.pet.id)); + + return newValue; + }); + }); + + useMessageEvent(PetRemovedFromInventory, event => + { + const parser = event.getParser(); + + setPetItems(prevValue => + { + const newValue = [ ...prevValue ]; + + removePetItemById(parser.petId, newValue); + + return newValue; + }); + }); + + useEffect(() => + { + if(!petItems || !petItems.length) return; + + setSelectedPet(prevValue => + { + let newValue = prevValue; + + if(newValue && (petItems.indexOf(newValue) === -1)) newValue = null; + + if(!newValue) newValue = petItems[0]; + + return newValue; + }); + }, [ petItems ]); + + useEffect(() => + { + if(!isVisible) return; + + return () => + { + resetCategory(UnseenItemCategory.PET); + } + }, [ isVisible, resetCategory ]); + + useEffect(() => + { + if(!isVisible || !needsUpdate) return; + + SendMessageComposer(new RequestPetsComposer()); + + setNeedsUpdate(false); + }, [ isVisible, needsUpdate ]); + + return { petItems, selectedPet, setSelectedPet, activate, deactivate }; +} + +export const useInventoryPets = () => useBetween(useInventoryPetsState); diff --git a/src/hooks/inventory/useInventoryTrade.ts b/src/hooks/inventory/useInventoryTrade.ts new file mode 100644 index 0000000..bb83022 --- /dev/null +++ b/src/hooks/inventory/useInventoryTrade.ts @@ -0,0 +1,288 @@ +import { AdvancedMap, GetSessionDataManager, TradingAcceptComposer, TradingAcceptEvent, TradingCancelComposer, TradingCloseComposer, TradingCloseEvent, TradingCloseParser, TradingCompletedEvent, TradingConfirmationComposer, TradingConfirmationEvent, TradingListItemEvent, TradingListItemRemoveComposer, TradingNotOpenEvent, TradingOpenEvent, TradingOpenFailedEvent, TradingOtherNotAllowedEvent, TradingUnacceptComposer, TradingYouAreNotAllowedEvent } from '@nitrots/nitro-renderer'; +import { useEffect, useState } from 'react'; +import { useBetween } from 'use-between'; +import { CloneObject, GetRoomSession, GroupItem, LocalizeText, SendMessageComposer, TradeState, TradeUserData, TradingNotificationType, parseTradeItems } from '../../api'; +import { useMessageEvent } from '../events'; +import { useNotification } from '../notification'; +import { useInventoryFurni } from './useInventoryFurni'; + +const useInventoryTradeState = () => +{ + const [ ownUser, setOwnUser ] = useState(null); + const [ otherUser, setOtherUser ] = useState(null); + const [ tradeState, setTradeState ] = useState(TradeState.TRADING_STATE_READY); + const { groupItems = [], setGroupItems = null, activate = null, deactivate = null } = useInventoryFurni(); + const { simpleAlert = null, showTradeAlert = null } = useNotification(); + const isTrading = (tradeState >= TradeState.TRADING_STATE_RUNNING); + + const progressTrade = () => + { + switch(tradeState) + { + case TradeState.TRADING_STATE_RUNNING: + if(!otherUser.itemCount && !ownUser.accepts) + { + simpleAlert(LocalizeText('inventory.trading.warning.other_not_offering'), null, null, null); + } + + if(ownUser.accepts) + { + SendMessageComposer(new TradingUnacceptComposer()); + } + else + { + SendMessageComposer(new TradingAcceptComposer()); + } + return; + case TradeState.TRADING_STATE_CONFIRMING: + SendMessageComposer(new TradingConfirmationComposer()); + + setTradeState(TradeState.TRADING_STATE_CONFIRMED); + return; + } + } + + const removeItem = (group: GroupItem) => + { + const item = group.getLastItem(); + + if(!item) return; + + SendMessageComposer(new TradingListItemRemoveComposer(item.id)); + } + + const stopTrading = () => + { + if(!isTrading) return; + + switch(tradeState) + { + case TradeState.TRADING_STATE_RUNNING: + SendMessageComposer(new TradingCloseComposer()); + return; + default: + SendMessageComposer(new TradingCancelComposer()); + return; + } + } + + useMessageEvent(TradingAcceptEvent, event => + { + const parser = event.getParser(); + + if(!ownUser || !otherUser) return; + + if(ownUser.userId === parser.userID) + { + setOwnUser(prevValue => + { + const newValue = CloneObject(prevValue); + + newValue.accepts = parser.userAccepts; + + return newValue; + }); + } + + else if(otherUser.userId === parser.userID) + { + setOtherUser(prevValue => + { + const newValue = CloneObject(prevValue); + + newValue.accepts = parser.userAccepts; + + return newValue; + }); + } + }); + + useMessageEvent(TradingCloseEvent, event => + { + const parser = event.getParser(); + + if(parser.reason === TradingCloseParser.ERROR_WHILE_COMMIT) + { + showTradeAlert(TradingNotificationType.ERROR_WHILE_COMMIT); + } + else + { + if(ownUser && (parser.userID !== ownUser.userId)) + { + showTradeAlert(TradingNotificationType.THEY_CANCELLED); + } + } + + setOwnUser(null); + setOtherUser(null); + setTradeState(TradeState.TRADING_STATE_READY); + }); + + useMessageEvent(TradingCompletedEvent, event => + { + const parser = event.getParser(); + + setOwnUser(null); + setOtherUser(null); + setTradeState(TradeState.TRADING_STATE_READY); + }); + + useMessageEvent(TradingConfirmationEvent, event => + { + const parser = event.getParser(); + + setTradeState(TradeState.TRADING_STATE_COUNTDOWN); + }); + + useMessageEvent(TradingListItemEvent, event => + { + const parser = event.getParser(); + const firstUserItems = parseTradeItems(parser.firstUserItemArray); + const secondUserItems = parseTradeItems(parser.secondUserItemArray); + + setOwnUser(prevValue => + { + const newValue = CloneObject(prevValue); + + if(newValue.userId === parser.firstUserID) + { + newValue.creditsCount = parser.firstUserNumCredits; + newValue.itemCount = parser.firstUserNumItems; + newValue.userItems = firstUserItems; + } + else + { + newValue.creditsCount = parser.secondUserNumCredits; + newValue.itemCount = parser.secondUserNumItems; + newValue.userItems = secondUserItems; + } + + const tradeIds: number[] = []; + + for(const groupItem of newValue.userItems.getValues()) + { + let i = 0; + + while(i < groupItem.getTotalCount()) + { + const item = groupItem.getItemByIndex(i); + + if(item) tradeIds.push(item.ref); + + i++; + } + } + + setGroupItems(prevValue => + { + const newValue = [ ...prevValue ]; + + for(const groupItem of newValue) groupItem.lockItemIds(tradeIds); + + return newValue; + }); + + return newValue; + }); + + setOtherUser(prevValue => + { + const newValue = CloneObject(prevValue); + + if(newValue.userId === parser.firstUserID) + { + newValue.creditsCount = parser.firstUserNumCredits; + newValue.itemCount = parser.firstUserNumItems; + newValue.userItems = firstUserItems; + } + else + { + newValue.creditsCount = parser.secondUserNumCredits; + newValue.itemCount = parser.secondUserNumItems; + newValue.userItems = secondUserItems; + } + + return newValue; + }); + }); + + useMessageEvent(TradingNotOpenEvent, event => + { + const parser = event.getParser(); + }); + + useMessageEvent(TradingOpenEvent, event => + { + const parser = event.getParser(); + + const firstUser = new TradeUserData(); + const firstUserData = GetRoomSession().userDataManager.getUserData(parser.userID); + + firstUser.userItems = new AdvancedMap(); + + const secondUser = new TradeUserData(); + const secondUserData = GetRoomSession().userDataManager.getUserData(parser.otherUserID); + + secondUser.userItems = new AdvancedMap(); + + if(firstUserData.webID === GetSessionDataManager().userId) + { + firstUser.userId = firstUserData.webID; + firstUser.userName = firstUserData.name; + firstUser.canTrade = parser.userCanTrade; + + secondUser.userId = secondUserData.webID; + secondUser.userName = secondUserData.name; + secondUser.canTrade = parser.otherUserCanTrade; + } + + else if(secondUserData.webID === GetSessionDataManager().userId) + { + firstUser.userId = secondUserData.webID; + firstUser.userName = secondUserData.name; + firstUser.canTrade = parser.otherUserCanTrade; + + secondUser.userId = firstUserData.webID; + secondUser.userName = firstUserData.name; + secondUser.canTrade = parser.userCanTrade; + } + + setOwnUser(firstUser); + setOtherUser(secondUser); + setTradeState(TradeState.TRADING_STATE_RUNNING); + }); + + useMessageEvent(TradingOpenFailedEvent, event => + { + const parser = event.getParser(); + + showTradeAlert(parser.reason, parser.otherUserName); + }); + + useMessageEvent(TradingOtherNotAllowedEvent, event => + { + const parser = event.getParser(); + + showTradeAlert(TradingNotificationType.THEY_NOT_ALLOWED); + }); + + useMessageEvent(TradingYouAreNotAllowedEvent, event => + { + const parser = event.getParser(); + + showTradeAlert(TradingNotificationType.YOU_NOT_ALLOWED); + }); + + useEffect(() => + { + if(tradeState === TradeState.TRADING_STATE_READY) return; + + const id = activate(); + + return () => deactivate(id); + }, [ tradeState, activate, deactivate ]); + + return { ownUser, otherUser, tradeState, setTradeState, isTrading, groupItems, progressTrade, removeItem, stopTrading }; +} + +export const useInventoryTrade = () => useBetween(useInventoryTradeState); diff --git a/src/hooks/inventory/useInventoryUnseenTracker.ts b/src/hooks/inventory/useInventoryUnseenTracker.ts new file mode 100644 index 0000000..90ba66e --- /dev/null +++ b/src/hooks/inventory/useInventoryUnseenTracker.ts @@ -0,0 +1,132 @@ +import { UnseenItemsEvent, UnseenResetCategoryComposer, UnseenResetItemsComposer } from '@nitrots/nitro-renderer'; +import { useCallback, useMemo, useState } from 'react'; +import { useBetween } from 'use-between'; +import { SendMessageComposer } from '../../api'; +import { useMessageEvent } from '../events'; + +const sendResetCategoryMessage = (category: number) => SendMessageComposer(new UnseenResetCategoryComposer(category)); +const sendResetItemsMessage = (category: number, itemIds: number[]) => SendMessageComposer(new UnseenResetItemsComposer(category, ...itemIds)); + +const useInventoryUnseenTrackerState = () => +{ + const [ unseenItems, setUnseenItems ] = useState>(new Map()); + + const getCount = useCallback((category: number) => (unseenItems.get(category)?.length || 0), [ unseenItems ]); + + const getFullCount = useMemo(() => + { + let count = 0; + + for(const key of unseenItems.keys()) count += getCount(key); + + return count; + }, [ unseenItems, getCount ]); + + const resetCategory = useCallback((category: number) => + { + let didReset = true; + + setUnseenItems(prevValue => + { + if(!prevValue.has(category)) + { + didReset = false; + + return prevValue; + } + + const newValue = new Map(prevValue); + + newValue.delete(category); + + sendResetCategoryMessage(category); + + return newValue; + }); + + return didReset; + }, []); + + const resetItems = useCallback((category: number, itemIds: number[]) => + { + let didReset = true; + + setUnseenItems(prevValue => + { + if(!prevValue.has(category)) + { + didReset = false; + + return prevValue; + } + + const newValue = new Map(prevValue); + const existing = newValue.get(category); + + if(existing) for(const itemId of itemIds) existing.splice(existing.indexOf(itemId), 1); + + sendResetItemsMessage(category, itemIds); + + return newValue; + }); + + return didReset; + }, []); + + const isUnseen = useCallback((category: number, itemId: number) => + { + if(!unseenItems.has(category)) return false; + + const items = unseenItems.get(category); + + return (items.indexOf(itemId) >= 0); + }, [ unseenItems ]); + + const removeUnseen = useCallback((category: number, itemId: number) => + { + setUnseenItems(prevValue => + { + if(!prevValue.has(category)) return prevValue; + + const newValue = new Map(prevValue); + const items = newValue.get(category); + const index = items.indexOf(itemId); + + if(index >= 0) items.splice(index, 1); + + return newValue; + }); + }, []); + + useMessageEvent(UnseenItemsEvent, event => + { + const parser = event.getParser(); + + setUnseenItems(prevValue => + { + const newValue = new Map(prevValue); + + for(const category of parser.categories) + { + let existing = newValue.get(category); + + if(!existing) + { + existing = []; + + newValue.set(category, existing); + } + + const itemIds = parser.getItemsByCategory(category); + + for(const itemId of itemIds) ((existing.indexOf(itemId) === -1) && existing.push(itemId)); + } + + return newValue; + }); + }); + + return { getCount, getFullCount, resetCategory, resetItems, isUnseen, removeUnseen }; +} + +export const useInventoryUnseenTracker = () => useBetween(useInventoryUnseenTrackerState); diff --git a/src/hooks/mod-tools/index.ts b/src/hooks/mod-tools/index.ts new file mode 100644 index 0000000..26546fd --- /dev/null +++ b/src/hooks/mod-tools/index.ts @@ -0,0 +1 @@ +export * from './useModTools'; diff --git a/src/hooks/mod-tools/useModTools.ts b/src/hooks/mod-tools/useModTools.ts new file mode 100644 index 0000000..93628f1 --- /dev/null +++ b/src/hooks/mod-tools/useModTools.ts @@ -0,0 +1,207 @@ +import { CallForHelpCategoryData, CfhSanctionMessageEvent, CfhTopicsInitEvent, IssueDeletedMessageEvent, IssueInfoMessageEvent, IssueMessageData, IssuePickFailedMessageEvent, ModeratorActionResultMessageEvent, ModeratorInitData, ModeratorInitMessageEvent, ModeratorToolPreferencesEvent } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { useBetween } from 'use-between'; +import { NotificationAlertType, PlaySound, SoundNames } from '../../api'; +import { useMessageEvent } from '../events'; +import { useNotification } from '../notification'; + +const useModToolsState = () => +{ + const [ settings, setSettings ] = useState(null); + const [ openRooms, setOpenRooms ] = useState([]); + const [ openRoomChatlogs, setOpenRoomChatlogs ] = useState([]); + const [ openUserInfos, setOpenUserInfos ] = useState([]); + const [ openUserChatlogs, setOpenUserChatlogs ] = useState([]); + const [ tickets, setTickets ] = useState([]); + const [ cfhCategories, setCfhCategories ] = useState([]); + const { simpleAlert = null } = useNotification(); + + const openRoomInfo = (roomId: number) => + { + if(openRooms.indexOf(roomId) >= 0) return; + + setOpenRooms(prevValue => [ ...prevValue, roomId ]); + } + + const closeRoomInfo = (roomId: number) => + { + setOpenRooms(prevValue => + { + const newValue = [ ...prevValue ]; + const existingIndex = newValue.indexOf(roomId); + + if(existingIndex >= 0) newValue.splice(existingIndex); + + return newValue; + }); + } + + const toggleRoomInfo = (roomId: number) => + { + if(openRooms.indexOf(roomId) >= 0) closeRoomInfo(roomId); + else openRoomInfo(roomId); + } + + const openRoomChatlog = (roomId: number) => + { + if(openRoomChatlogs.indexOf(roomId) >= 0) return; + + setOpenRoomChatlogs(prevValue => [ ...prevValue, roomId ]); + } + + const closeRoomChatlog = (roomId: number) => + { + setOpenRoomChatlogs(prevValue => + { + const newValue = [ ...prevValue ]; + const existingIndex = newValue.indexOf(roomId); + + if(existingIndex >= 0) newValue.splice(existingIndex); + + return newValue; + }); + } + + const toggleRoomChatlog = (roomId: number) => + { + if(openRoomChatlogs.indexOf(roomId) >= 0) closeRoomChatlog(roomId); + else openRoomChatlog(roomId); + } + + const openUserInfo = (userId: number) => + { + if(openUserInfos.indexOf(userId) >= 0) return; + + setOpenUserInfos(prevValue => [ ...prevValue, userId ]); + } + + const closeUserInfo = (userId: number) => + { + setOpenUserInfos(prevValue => + { + const newValue = [ ...prevValue ]; + const existingIndex = newValue.indexOf(userId); + + if(existingIndex >= 0) newValue.splice(existingIndex); + + return newValue; + }); + } + + const toggleUserInfo = (userId: number) => + { + if(openUserInfos.indexOf(userId) >= 0) closeUserInfo(userId); + else openUserInfo(userId); + } + + const openUserChatlog = (userId: number) => + { + if(openUserChatlogs.indexOf(userId) >= 0) return; + + setOpenUserChatlogs(prevValue => [ ...prevValue, userId ]); + } + + const closeUserChatlog = (userId: number) => + { + setOpenUserChatlogs(prevValue => + { + const newValue = [ ...prevValue ]; + const existingIndex = newValue.indexOf(userId); + + if(existingIndex >= 0) newValue.splice(existingIndex); + + return newValue; + }); + } + + const toggleUserChatlog = (userId: number) => + { + if(openRoomChatlogs.indexOf(userId) >= 0) closeUserChatlog(userId); + else openUserChatlog(userId); + } + + useMessageEvent(ModeratorInitMessageEvent, event => + { + const parser = event.getParser(); + const data = parser.data; + + setSettings(data); + setTickets(data.issues); + }); + + useMessageEvent(IssueInfoMessageEvent, event => + { + const parser = event.getParser(); + + setTickets(prevValue => + { + const newValue = [ ...prevValue ]; + const existingIndex = newValue.findIndex(ticket => (ticket.issueId === parser.issueData.issueId)); + + if(existingIndex >= 0) newValue[existingIndex] = parser.issueData; + else + { + newValue.push(parser.issueData); + + PlaySound(SoundNames.MODTOOLS_NEW_TICKET); + } + + return newValue; + }); + }); + + useMessageEvent(ModeratorToolPreferencesEvent, event => + { + const parser = event.getParser(); + }); + + useMessageEvent(IssuePickFailedMessageEvent, event => + { + const parser = event.getParser(); + + if(!parser) return; + + simpleAlert('Failed to pick issue', NotificationAlertType.DEFAULT, null, null, 'Error') + }); + + useMessageEvent(IssueDeletedMessageEvent, event => + { + const parser = event.getParser(); + + setTickets(prevValue => + { + const newValue = [ ...prevValue ]; + const existingIndex = newValue.findIndex(ticket => (ticket.issueId === parser.issueId)); + + if(existingIndex >= 0) newValue.splice(existingIndex, 1); + + return newValue; + }); + }); + + useMessageEvent(ModeratorActionResultMessageEvent, event => + { + const parser = event.getParser(); + + if(parser.success) simpleAlert('Moderation action was successfull', NotificationAlertType.MODERATION, null, null, 'Success'); + else simpleAlert('There was a problem applying tht moderation action', NotificationAlertType.MODERATION, null, null, 'Error'); + }); + + useMessageEvent(CfhTopicsInitEvent, event => + { + const parser = event.getParser(); + + setCfhCategories(parser.callForHelpCategories); + }); + + useMessageEvent(CfhSanctionMessageEvent, event => + { + const parser = event.getParser(); + + // todo: update sanction data + }); + + return { settings, openRooms, openRoomChatlogs, openUserChatlogs, openUserInfos, cfhCategories, tickets, openRoomInfo, closeRoomInfo, toggleRoomInfo, openRoomChatlog, closeRoomChatlog, toggleRoomChatlog, openUserInfo, closeUserInfo, toggleUserInfo, openUserChatlog, closeUserChatlog, toggleUserChatlog }; +} + +export const useModTools = () => useBetween(useModToolsState); diff --git a/src/hooks/navigator/index.ts b/src/hooks/navigator/index.ts new file mode 100644 index 0000000..1f6f053 --- /dev/null +++ b/src/hooks/navigator/index.ts @@ -0,0 +1 @@ +export * from './useNavigator'; diff --git a/src/hooks/navigator/useNavigator.ts b/src/hooks/navigator/useNavigator.ts new file mode 100644 index 0000000..87d2de3 --- /dev/null +++ b/src/hooks/navigator/useNavigator.ts @@ -0,0 +1,442 @@ +import { CanCreateRoomEventEvent, CantConnectMessageParser, CreateLinkEvent, DoorbellMessageEvent, FlatAccessDeniedMessageEvent, FlatCreatedEvent, FollowFriendMessageComposer, GenericErrorEvent, GetGuestRoomMessageComposer, GetGuestRoomResultEvent, GetSessionDataManager, GetUserEventCatsMessageComposer, GetUserFlatCatsMessageComposer, HabboWebTools, LegacyExternalInterface, NavigatorCategoryDataParser, NavigatorEventCategoryDataParser, NavigatorHomeRoomEvent, NavigatorMetadataEvent, NavigatorOpenRoomCreatorEvent, NavigatorSearchEvent, NavigatorSearchResultSet, NavigatorTopLevelContext, RoomDataParser, RoomDoorbellAcceptedEvent, RoomEnterErrorEvent, RoomEntryInfoMessageEvent, RoomForwardEvent, RoomScoreEvent, RoomSettingsUpdatedEvent, SecurityLevel, UserEventCatsEvent, UserFlatCatsEvent, UserInfoEvent, UserPermissionsEvent } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { useBetween } from 'use-between'; +import { CreateRoomSession, DoorStateType, GetConfigurationValue, INavigatorData, LocalizeText, NotificationAlertType, SendMessageComposer, TryVisitRoom, VisitDesktop } from '../../api'; +import { useMessageEvent } from '../events'; +import { useNotification } from '../notification'; + +const useNavigatorState = () => +{ + const [ categories, setCategories ] = useState(null); + const [ eventCategories, setEventCategories ] = useState(null); + const [ topLevelContext, setTopLevelContext ] = useState(null); + const [ topLevelContexts, setTopLevelContexts ] = useState(null); + const [ doorData, setDoorData ] = useState<{ roomInfo: RoomDataParser, state: number }>({ roomInfo: null, state: DoorStateType.NONE }); + const [ searchResult, setSearchResult ] = useState(null); + const [ navigatorData, setNavigatorData ] = useState({ + settingsReceived: false, + homeRoomId: 0, + enteredGuestRoom: null, + currentRoomOwner: false, + currentRoomId: 0, + currentRoomIsStaffPick: false, + createdFlatId: 0, + avatarId: 0, + roomPicker: false, + eventMod: false, + currentRoomRating: 0, + canRate: true + }); + const { simpleAlert = null } = useNotification(); + + useMessageEvent(RoomSettingsUpdatedEvent, event => + { + const parser = event.getParser(); + + SendMessageComposer(new GetGuestRoomMessageComposer(parser.roomId, false, false)); + }); + + useMessageEvent(CanCreateRoomEventEvent, event => + { + const parser = event.getParser(); + + if(parser.canCreate) + { + // show room event cvreate + + return; + } + + simpleAlert(LocalizeText(`navigator.cannotcreateevent.error.${ parser.errorCode }`), null, null, null, LocalizeText('navigator.cannotcreateevent.title')); + }); + + useMessageEvent(UserInfoEvent, event => + { + SendMessageComposer(new GetUserFlatCatsMessageComposer()); + SendMessageComposer(new GetUserEventCatsMessageComposer()); + }); + + useMessageEvent(UserPermissionsEvent, event => + { + const parser = event.getParser(); + + setNavigatorData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.eventMod = (parser.securityLevel >= SecurityLevel.MODERATOR); + newValue.roomPicker = (parser.securityLevel >= SecurityLevel.COMMUNITY); + + return newValue; + }); + }); + + useMessageEvent(RoomForwardEvent, event => + { + const parser = event.getParser(); + + TryVisitRoom(parser.roomId); + }); + + useMessageEvent(RoomEntryInfoMessageEvent, event => + { + const parser = event.getParser(); + + setNavigatorData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.enteredGuestRoom = null; + newValue.currentRoomOwner = parser.isOwner; + newValue.currentRoomId = parser.roomId; + + return newValue; + }); + + // close room info + // close room settings + // close room filter + + SendMessageComposer(new GetGuestRoomMessageComposer(parser.roomId, true, false)); + + if(LegacyExternalInterface.available) LegacyExternalInterface.call('legacyTrack', 'navigator', 'private', [ parser.roomId ]); + }); + + useMessageEvent(GetGuestRoomResultEvent, event => + { + const parser = event.getParser(); + + if(parser.roomEnter) + { + setDoorData({ roomInfo: null, state: DoorStateType.NONE }); + + setNavigatorData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.enteredGuestRoom = parser.data; + newValue.currentRoomIsStaffPick = parser.staffPick; + + const isCreated = (newValue.createdFlatId === parser.data.roomId); + + if(!isCreated && parser.data.displayRoomEntryAd) + { + if(GetConfigurationValue('roomenterad.habblet.enabled', false)) HabboWebTools.openRoomEnterAd(); + } + + newValue.createdFlatId = 0; + + if(newValue.enteredGuestRoom && (newValue.enteredGuestRoom.habboGroupId > 0)) + { + // close event info + } + + return newValue; + }); + } + else if(parser.roomForward) + { + if((parser.data.ownerName !== GetSessionDataManager().userName) && !parser.isGroupMember) + { + switch(parser.data.doorMode) + { + case RoomDataParser.DOORBELL_STATE: + setDoorData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.roomInfo = parser.data; + newValue.state = DoorStateType.START_DOORBELL; + + return newValue; + }); + return; + case RoomDataParser.PASSWORD_STATE: + setDoorData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.roomInfo = parser.data; + newValue.state = DoorStateType.START_PASSWORD; + + return newValue; + }); + return; + } + } + + if((parser.data.doorMode === RoomDataParser.NOOB_STATE) && !GetSessionDataManager().isAmbassador && !GetSessionDataManager().isRealNoob && !GetSessionDataManager().isModerator) return; + + CreateRoomSession(parser.data.roomId); + } + else + { + setNavigatorData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.enteredGuestRoom = parser.data; + newValue.currentRoomIsStaffPick = parser.staffPick; + + return newValue; + }); + } + }); + + useMessageEvent(RoomScoreEvent, event => + { + const parser = event.getParser(); + + setNavigatorData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.currentRoomRating = parser.totalLikes; + newValue.canRate = parser.canLike; + + return newValue; + }); + }); + + useMessageEvent(DoorbellMessageEvent, event => + { + const parser = event.getParser(); + + if(!parser.userName || (parser.userName.length === 0)) + { + setDoorData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.state = DoorStateType.STATE_WAITING; + + return newValue; + }); + } + }); + + useMessageEvent(RoomDoorbellAcceptedEvent, event => + { + const parser = event.getParser(); + + if(!parser.userName || (parser.userName.length === 0)) + { + setDoorData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.state = DoorStateType.STATE_ACCEPTED; + + return newValue; + }); + } + }); + + useMessageEvent(FlatAccessDeniedMessageEvent, event => + { + const parser = event.getParser(); + + if(!parser.userName || (parser.userName.length === 0)) + { + setDoorData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.state = DoorStateType.STATE_NO_ANSWER; + + return newValue; + }); + } + }); + + useMessageEvent(GenericErrorEvent, event => + { + const parser = event.getParser(); + + switch(parser.errorCode) + { + case -100002: + setDoorData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.state = DoorStateType.STATE_WRONG_PASSWORD; + + return newValue; + }); + return; + case 4009: + simpleAlert(LocalizeText('navigator.alert.need.to.be.vip'), NotificationAlertType.DEFAULT, null, null, LocalizeText('generic.alert.title')); + + return; + case 4010: + simpleAlert(LocalizeText('navigator.alert.invalid_room_name'), NotificationAlertType.DEFAULT, null, null, LocalizeText('generic.alert.title')); + + return; + case 4011: + simpleAlert(LocalizeText('navigator.alert.cannot_perm_ban'), NotificationAlertType.DEFAULT, null, null, LocalizeText('generic.alert.title')); + + return; + case 4013: + simpleAlert(LocalizeText('navigator.alert.room_in_maintenance'), NotificationAlertType.DEFAULT, null, null, LocalizeText('generic.alert.title')); + + return; + } + }); + + useMessageEvent(NavigatorMetadataEvent, event => + { + const parser = event.getParser(); + + setTopLevelContexts(parser.topLevelContexts); + setTopLevelContext(parser.topLevelContexts.length ? parser.topLevelContexts[0] : null); + }); + + useMessageEvent(NavigatorSearchEvent, event => + { + const parser = event.getParser(); + + setTopLevelContext(prevValue => + { + let newValue = prevValue; + + if(!newValue) newValue = ((topLevelContexts && topLevelContexts.length && topLevelContexts[0]) || null); + + if(!newValue) return null; + + if((parser.result.code !== newValue.code) && topLevelContexts && topLevelContexts.length) + { + for(const context of topLevelContexts) + { + if(context.code !== parser.result.code) continue; + + newValue = context; + } + } + + for(const context of topLevelContexts) + { + if(context.code !== parser.result.code) continue; + + newValue = context; + } + + return newValue; + }); + + setSearchResult(parser.result); + }); + + useMessageEvent(UserFlatCatsEvent, event => + { + const parser = event.getParser(); + + setCategories(parser.categories); + }); + + useMessageEvent(UserEventCatsEvent, event => + { + const parser = event.getParser(); + + setEventCategories(parser.categories); + }); + + useMessageEvent(FlatCreatedEvent, event => + { + const parser = event.getParser(); + + CreateRoomSession(parser.roomId); + }); + + useMessageEvent(NavigatorHomeRoomEvent, event => + { + const parser = event.getParser(); + + let prevSettingsReceived = false; + + setNavigatorData(prevValue => + { + prevSettingsReceived = prevValue.settingsReceived; + + const newValue = { ...prevValue }; + + newValue.homeRoomId = parser.homeRoomId; + newValue.settingsReceived = true; + + return newValue; + }); + + if(prevSettingsReceived) + { + // refresh room info window + return; + } + + let forwardType = -1; + let forwardId = -1; + + if((GetConfigurationValue('friend.id') !== undefined) && (parseInt(GetConfigurationValue('friend.id')) > 0)) + { + forwardType = 0; + SendMessageComposer(new FollowFriendMessageComposer(parseInt(GetConfigurationValue('friend.id')))); + } + + if((GetConfigurationValue('forward.type') !== undefined) && (GetConfigurationValue('forward.id') !== undefined)) + { + forwardType = parseInt(GetConfigurationValue('forward.type')); + forwardId = parseInt(GetConfigurationValue('forward.id')) + } + + if(forwardType === 2) + { + TryVisitRoom(forwardId); + } + + else if((forwardType === -1) && (parser.roomIdToEnter > 0)) + { + CreateLinkEvent('navigator/close'); + + if(parser.roomIdToEnter !== parser.homeRoomId) + { + CreateRoomSession(parser.roomIdToEnter); + } + else + { + CreateRoomSession(parser.homeRoomId); + } + } + }); + + useMessageEvent(RoomEnterErrorEvent, event => + { + const parser = event.getParser(); + + switch(parser.reason) + { + case CantConnectMessageParser.REASON_FULL: + simpleAlert(LocalizeText('navigator.guestroomfull.text'), NotificationAlertType.DEFAULT, null, null, LocalizeText('navigator.guestroomfull.title')); + + break; + case CantConnectMessageParser.REASON_QUEUE_ERROR: + simpleAlert(LocalizeText(`room.queue.error.${ parser.parameter }`), NotificationAlertType.DEFAULT, null, null, LocalizeText('room.queue.error.title')); + + break; + case CantConnectMessageParser.REASON_BANNED: + simpleAlert(LocalizeText('navigator.banned.text'), NotificationAlertType.DEFAULT, null, null, LocalizeText('navigator.banned.title')); + + break; + default: + simpleAlert(LocalizeText('room.queue.error.title'), NotificationAlertType.DEFAULT, null, null, LocalizeText('room.queue.error.title')); + + break; + } + + VisitDesktop(); + }); + + useMessageEvent(NavigatorOpenRoomCreatorEvent, event => CreateLinkEvent('navigator/show')); + + return { categories, doorData, setDoorData, topLevelContext, topLevelContexts, searchResult, navigatorData }; +} + +export const useNavigator = () => useBetween(useNavigatorState); diff --git a/src/hooks/notification/index.ts b/src/hooks/notification/index.ts new file mode 100644 index 0000000..b5a8561 --- /dev/null +++ b/src/hooks/notification/index.ts @@ -0,0 +1 @@ +export * from './useNotification'; diff --git a/src/hooks/notification/useNotification.ts b/src/hooks/notification/useNotification.ts new file mode 100644 index 0000000..2343b8e --- /dev/null +++ b/src/hooks/notification/useNotification.ts @@ -0,0 +1,433 @@ +import { AchievementNotificationMessageEvent, ActivityPointNotificationMessageEvent, ClubGiftNotificationEvent, ClubGiftSelectedEvent, ConnectionErrorEvent, GetLocalizationManager, GetRoomEngine, GetSessionDataManager, HabboBroadcastMessageEvent, HotelClosedAndOpensEvent, HotelClosesAndWillOpenAtEvent, HotelWillCloseInMinutesEvent, InfoFeedEnableMessageEvent, MaintenanceStatusMessageEvent, ModeratorCautionEvent, ModeratorMessageEvent, MOTDNotificationEvent, NotificationDialogMessageEvent, PetLevelNotificationEvent, PetReceivedMessageEvent, RespectReceivedEvent, RoomEnterEffect, RoomEnterEvent, SimpleAlertMessageEvent, UserBannedMessageEvent, Vector3d } from '@nitrots/nitro-renderer'; +import { useCallback, useState } from 'react'; +import { useBetween } from 'use-between'; +import { GetConfigurationValue, LocalizeBadgeName, LocalizeText, NotificationAlertItem, NotificationAlertType, NotificationBubbleItem, NotificationBubbleType, NotificationConfirmItem, PlaySound, ProductImageUtility, TradingNotificationType } from '../../api'; +import { useMessageEvent } from '../events'; + +const cleanText = (text: string) => (text && text.length) ? text.replace(/\\r/g, '\r') : ''; + +const getTimeZeroPadded = (time: number) => +{ + const text = ('0' + time); + + return text.substr((text.length - 2), text.length); +} + +let modDisclaimerTimeout: ReturnType = null; + +const useNotificationState = () => +{ + const [ alerts, setAlerts ] = useState([]); + const [ bubbleAlerts, setBubbleAlerts ] = useState([]); + const [ confirms, setConfirms ] = useState([]); + const [ bubblesDisabled, setBubblesDisabled ] = useState(false); + const [ modDisclaimerShown, setModDisclaimerShown ] = useState(false); + + const getMainNotificationConfig = () => GetConfigurationValue<{ [key: string]: { delivery?: string, display?: string; title?: string; image?: string }}>('notification', {}); + + const getNotificationConfig = (key: string) => + { + const mainConfig = getMainNotificationConfig(); + + if(!mainConfig) return null; + + return mainConfig[key]; + } + + const getNotificationPart = (options: Map, type: string, key: string, localize: boolean) => + { + if(options.has(key)) return options.get(key); + + const localizeKey = [ 'notification', type, key ].join('.'); + + if(GetLocalizationManager().hasValue(localizeKey) || localize) return LocalizeText(localizeKey, Array.from(options.keys()), Array.from(options.values())); + + return null; + } + + const getNotificationImageUrl = (options: Map, type: string) => + { + let imageUrl = options.get('image'); + + if(!imageUrl) imageUrl = GetConfigurationValue('image.library.notifications.url', '').replace('%image%', type.replace(/\./g, '_')); + + return LocalizeText(imageUrl); + } + + const simpleAlert = useCallback((message: string, type: string = null, clickUrl: string = null, clickUrlText: string = null, title: string = null, imageUrl: string = null) => + { + if(!title || !title.length) title = LocalizeText('notifications.broadcast.title'); + + if(!type || !type.length) type = NotificationAlertType.DEFAULT; + + const alertItem = new NotificationAlertItem([ cleanText(message) ], type, clickUrl, clickUrlText, title, imageUrl); + + setAlerts(prevValue => [ alertItem, ...prevValue ]); + }, []); + + const showNitroAlert = useCallback(() => simpleAlert(null, NotificationAlertType.NITRO), [ simpleAlert ]); + + const showSingleBubble = useCallback((message: string, type: string, imageUrl: string = null, internalLink: string = null) => + { + if(bubblesDisabled) return; + + const notificationItem = new NotificationBubbleItem(message, type, imageUrl, internalLink); + + setBubbleAlerts(prevValue => [ notificationItem, ...prevValue ]); + }, [ bubblesDisabled ]); + + const showNotification = (type: string, options: Map = null) => + { + if(!options) options = new Map(); + + const configuration = getNotificationConfig(('notification.' + type)); + + if(configuration) for(const key in configuration) options.set(key, configuration[key]); + + if (type === 'floorplan_editor.error') options.set('message', options.get('message').replace(/[^a-zA-Z._ ]/g, '')); + + const title = getNotificationPart(options, type, 'title', true); + const message = getNotificationPart(options, type, 'message', true).replace(/\\r/g, '\r'); + const linkTitle = getNotificationPart(options, type, 'linkTitle', false); + const linkUrl = getNotificationPart(options, type, 'linkUrl', false); + const image = getNotificationImageUrl(options, type); + + if(options.get('display') === 'BUBBLE') + { + showSingleBubble(LocalizeText(message), NotificationBubbleType.INFO, image, linkUrl); + } + else + { + simpleAlert(LocalizeText(message), type, linkUrl, linkTitle, title, image); + } + + if(options.get('sound')) PlaySound(options.get('sound')); + } + + const showConfirm = useCallback((message: string, onConfirm: () => void, onCancel: () => void, confirmText: string = null, cancelText: string = null, title: string = null, type: string = null) => + { + if(!confirmText || !confirmText.length) confirmText = LocalizeText('generic.confirm'); + + if(!cancelText || !cancelText.length) cancelText = LocalizeText('generic.cancel'); + + if(!title || !title.length) title = LocalizeText('notifications.broadcast.title'); + + const confirmItem = new NotificationConfirmItem(type, message, onConfirm, onCancel, confirmText, cancelText, title); + + setConfirms(prevValue => [ confirmItem, ...prevValue ]); + }, []); + + const showModeratorMessage = (message: string, url: string = null, showHabboWay: boolean = true) => + { + simpleAlert(message, NotificationAlertType.DEFAULT, url, LocalizeText('mod.alert.link'), LocalizeText('mod.alert.title')); + } + + const showTradeAlert = useCallback((type: number, otherUsername: string = '') => + { + switch(type) + { + case TradingNotificationType.ALERT_SCAM: + simpleAlert(LocalizeText('inventory.trading.warning.other_not_offering'), null, null, null, LocalizeText('inventory.trading.notification.title')); + return; + case TradingNotificationType.HOTEL_TRADING_DISABLED: + case TradingNotificationType.YOU_NOT_ALLOWED: + case TradingNotificationType.THEY_NOT_ALLOWED: + case TradingNotificationType.ROOM_DISABLED: + case TradingNotificationType.YOU_OPEN: + case TradingNotificationType.THEY_OPEN: + simpleAlert(LocalizeText(`inventory.trading.openfail.${ type }`, [ 'otherusername' ], [ otherUsername ]), null, null, null, LocalizeText('inventory.trading.openfail.title')); + return; + case TradingNotificationType.ERROR_WHILE_COMMIT: + simpleAlert(`${ LocalizeText('inventory.trading.notification.caption') }, ${ LocalizeText('inventory.trading.notification.commiterror.info') }`, null, null, null, LocalizeText('inventory.trading.notification.title')); + return; + case TradingNotificationType.THEY_CANCELLED: + simpleAlert(LocalizeText('inventory.trading.info.closed'), null, null, null, LocalizeText('inventory.trading.notification.title')); + return; + } + }, [ simpleAlert ]); + + const closeAlert = useCallback((alert: NotificationAlertItem) => + { + setAlerts(prevValue => + { + const newAlerts = [ ...prevValue ]; + const index = newAlerts.findIndex(value => (alert === value)); + + if(index >= 0) newAlerts.splice(index, 1); + + return newAlerts; + }); + }, []); + + const closeBubbleAlert = useCallback((item: NotificationBubbleItem) => + { + setBubbleAlerts(prevValue => + { + const newAlerts = [ ...prevValue ]; + const index = newAlerts.findIndex(value => (item === value)); + + if(index >= 0) newAlerts.splice(index, 1); + + return newAlerts; + }) + }, []); + + const closeConfirm = useCallback((item: NotificationConfirmItem) => + { + setConfirms(prevValue => + { + const newConfirms = [ ...prevValue ]; + const index = newConfirms.findIndex(value => (item === value)); + + if(index >= 0) newConfirms.splice(index, 1); + + return newConfirms; + }) + }, []); + + useMessageEvent(RespectReceivedEvent, event => + { + const parser = event.getParser(); + + if(parser.userId !== GetSessionDataManager().userId) return; + + const text1 = LocalizeText('notifications.text.respect.1'); + const text2 = LocalizeText('notifications.text.respect.2', [ 'count' ], [ parser.respectsReceived.toString() ]); + + showSingleBubble(text1, NotificationBubbleType.RESPECT); + showSingleBubble(text2, NotificationBubbleType.RESPECT); + }); + + useMessageEvent(HabboBroadcastMessageEvent, event => + { + const parser = event.getParser(); + + simpleAlert(parser.message.replace(/\\r/g, '\r'), null, null, LocalizeText('notifications.broadcast.title')); + }); + + useMessageEvent(AchievementNotificationMessageEvent, event => + { + const parser = event.getParser(); + + const text1 = LocalizeText('achievements.levelup.desc'); + const badgeName = LocalizeBadgeName(parser.data.badgeCode); + const badgeImage = GetSessionDataManager().getBadgeUrl(parser.data.badgeCode); + const internalLink = 'questengine/achievements/' + parser.data.category; + + showSingleBubble((text1 + ' ' + badgeName), NotificationBubbleType.ACHIEVEMENT, badgeImage, internalLink); + }); + + useMessageEvent(ClubGiftNotificationEvent, event => + { + const parser = event.getParser(); + + if(parser.numGifts <= 0) return; + + showSingleBubble(parser.numGifts.toString(), NotificationBubbleType.CLUBGIFT, null, ('catalog/open/' + GetConfigurationValue('catalog.links')['hc.hc_gifts'])); + }); + + useMessageEvent(ModeratorMessageEvent, event => + { + const parser = event.getParser(); + + showModeratorMessage(parser.message, parser.url, false); + }); + + useMessageEvent(ActivityPointNotificationMessageEvent, event => + { + const parser = event.getParser(); + + if((parser.amountChanged <= 0) || (parser.type !== 5)) return; + + const imageUrl = GetConfigurationValue('currency.asset.icon.url', '').replace('%type%', parser.type.toString()); + + showSingleBubble(LocalizeText('notifications.text.loyalty.received', [ 'AMOUNT' ], [ parser.amountChanged.toString() ]), NotificationBubbleType.INFO, imageUrl); + }); + + useMessageEvent(UserBannedMessageEvent, event => + { + const parser = event.getParser(); + + showModeratorMessage(parser.message); + }); + + useMessageEvent(HotelClosesAndWillOpenAtEvent, event => + { + const parser = event.getParser(); + + simpleAlert( LocalizeText(('opening.hours.' + (parser.userThrowOutAtClose ? 'disconnected' : 'closed')), [ 'h', 'm' ], [ getTimeZeroPadded(parser.openHour), getTimeZeroPadded(parser.openMinute) ]), NotificationAlertType.DEFAULT, null, null, LocalizeText('opening.hours.title')); + }); + + useMessageEvent(PetReceivedMessageEvent, async event => + { + const parser = event.getParser(); + + const text = LocalizeText('notifications.text.' + (parser.boughtAsGift ? 'petbought' : 'petreceived')); + + let imageUrl: string = null; + + const imageResult = GetRoomEngine().getRoomObjectPetImage(parser.pet.typeId, parser.pet.paletteId, parseInt(parser.pet.color, 16), new Vector3d(45 * 3), 64, null, true); + + if(imageResult) imageUrl = (await imageResult.getImage())?.src; + + showSingleBubble(text, NotificationBubbleType.PETLEVEL, imageUrl); + }); + + useMessageEvent(MOTDNotificationEvent, event => + { + const parser = event.getParser(); + + const messages = parser.messages.map(message => cleanText(message)); + + const alertItem = new NotificationAlertItem(messages, NotificationAlertType.MOTD, null, null, LocalizeText('notifications.motd.title')); + + setAlerts(prevValue => [ alertItem, ...prevValue ]); + }); + + useMessageEvent(PetLevelNotificationEvent, async event => + { + const parser = event.getParser(); + + let imageUrl: string = null; + + const imageResult = GetRoomEngine().getRoomObjectPetImage(parser.figureData.typeId, parser.figureData.paletteId, parseInt(parser.figureData.color, 16), new Vector3d(45 * 3), 64, null, true); + + if(imageResult) imageUrl = (await imageResult.getImage())?.src; + + showSingleBubble(LocalizeText('notifications.text.petlevel', [ 'pet_name', 'level' ], [ parser.petName, parser.level.toString() ]), NotificationBubbleType.PETLEVEL, imageUrl); + }); + + useMessageEvent(InfoFeedEnableMessageEvent, event => + { + const parser = event.getParser(); + + setBubblesDisabled(!parser.enabled); + }); + + useMessageEvent(ClubGiftSelectedEvent, event => + { + const parser = event.getParser(); + + if(!parser.products || !parser.products.length) return; + + const productData = parser.products[0]; + + if(!productData) return; + + showSingleBubble(LocalizeText('notifications.text.club_gift.selected'), NotificationBubbleType.INFO, ProductImageUtility.getProductImageUrl(productData.productType, productData.furniClassId, productData.extraParam)); + }); + + useMessageEvent(MaintenanceStatusMessageEvent, event => + { + const parser = event.getParser(); + + simpleAlert(LocalizeText('maintenance.shutdown', [ 'm', 'd' ], [ parser.minutesUntilMaintenance.toString(), parser.duration.toString() ]), NotificationAlertType.DEFAULT, null, null, LocalizeText('opening.hours.title')); + }); + + useMessageEvent(ModeratorCautionEvent, event => + { + const parser = event.getParser(); + + showModeratorMessage(parser.message, parser.url); + }); + + useMessageEvent(NotificationDialogMessageEvent, event => + { + const parser = event.getParser(); + + showNotification(parser.type, parser.parameters); + }); + + useMessageEvent(HotelWillCloseInMinutesEvent, event => + { + const parser = event.getParser(); + + simpleAlert(LocalizeText('opening.hours.shutdown', [ 'm' ], [ parser.openMinute.toString() ]), NotificationAlertType.DEFAULT, null, null, LocalizeText('opening.hours.title')); + }); + + useMessageEvent(HotelClosedAndOpensEvent, event => + { + const parser = event.getParser(); + + simpleAlert(LocalizeText('opening.hours.disconnected', [ 'h', 'm' ], [ parser.openHour.toString(), parser.openMinute.toString() ]), NotificationAlertType.DEFAULT, null, null, LocalizeText('opening.hours.title')); + }); + + useMessageEvent(ConnectionErrorEvent, event => + { + const parser = event.getParser(); + + switch(parser.errorCode) + { + default: + case 0: + simpleAlert(LocalizeText('connection.server.error.desc', [ 'errorCode' ], [ parser.errorCode.toString() ]), NotificationAlertType.ALERT, null, null, LocalizeText('connection.server.error.title')); + break; + case 1001: + case 1002: + case 1003: + case 1004: + case 1005: + case 1006: + case 1007: + case 1008: + case 1009: + case 1010: + case 1011: + case 1012: + case 1013: + case 1014: + case 1015: + case 1016: + case 1017: + case 1018: + case 1019: + // TODO: fix dispose + //event.connection.dispose(); + break; + case 4013: + simpleAlert(LocalizeText('connection.room.maintenance.desc'), NotificationAlertType.ALERT, null, null, LocalizeText('connection.room.maintenance.title')); + break; + } + }); + + useMessageEvent(SimpleAlertMessageEvent, event => + { + const parser = event.getParser(); + + simpleAlert(LocalizeText(parser.alertMessage), NotificationAlertType.DEFAULT, null, null, LocalizeText(parser.titleMessage ? parser.titleMessage : 'notifications.broadcast.title')); + }); + + const onRoomEnterEvent = useCallback(() => + { + if(modDisclaimerShown) return; + + if(RoomEnterEffect.isRunning()) + { + if(modDisclaimerTimeout) return; + + modDisclaimerTimeout = setTimeout(() => + { + onRoomEnterEvent(); + }, (RoomEnterEffect.totalRunningTime + 5000)); + } + else + { + if(modDisclaimerTimeout) + { + clearTimeout(modDisclaimerTimeout); + + modDisclaimerTimeout = null; + } + + showSingleBubble(LocalizeText('mod.chatdisclaimer'), NotificationBubbleType.INFO); + + setModDisclaimerShown(true); + } + }, [ modDisclaimerShown, showSingleBubble ]); + + useMessageEvent(RoomEnterEvent, onRoomEnterEvent); + + return { alerts, bubbleAlerts, confirms, simpleAlert, showNitroAlert, showTradeAlert, showConfirm, showSingleBubble, closeAlert, closeBubbleAlert, closeConfirm }; +} + +export const useNotification = () => useBetween(useNotificationState); diff --git a/src/hooks/purse/index.ts b/src/hooks/purse/index.ts new file mode 100644 index 0000000..d9d1ff7 --- /dev/null +++ b/src/hooks/purse/index.ts @@ -0,0 +1 @@ +export * from './usePurse'; diff --git a/src/hooks/purse/usePurse.ts b/src/hooks/purse/usePurse.ts new file mode 100644 index 0000000..e8155ce --- /dev/null +++ b/src/hooks/purse/usePurse.ts @@ -0,0 +1,126 @@ +import { ActivityPointNotificationMessageEvent, UserCreditsEvent, UserCurrencyComposer, UserCurrencyEvent, UserSubscriptionComposer, UserSubscriptionEvent, UserSubscriptionParser } from '@nitrots/nitro-renderer'; +import { useEffect, useMemo, useState } from 'react'; +import { useBetween } from 'use-between'; +import { CloneObject, ClubStatus, GetConfigurationValue, IPurse, PlaySound, Purse, SendMessageComposer, SoundNames } from '../../api'; +import { useMessageEvent } from '../events'; + +const usePurseState = () => +{ + const [ purse, setPurse ] = useState(new Purse()); + const hcDisabled = useMemo(() => GetConfigurationValue('hc.disabled', false), []); + + const clubStatus = useMemo(() => + { + if(hcDisabled || (purse.clubDays > 0)) return ClubStatus.ACTIVE; + + if((purse.pastVipDays > 0) || (purse.pastVipDays > 0)) return ClubStatus.EXPIRED; + + return ClubStatus.NONE; + }, [ purse, hcDisabled ]); + + const getCurrencyAmount = (type: number) => + { + if(type === -1) return purse.credits; + + for(const [ key, value ] of purse.activityPoints.entries()) + { + if(key !== type) continue; + + return value; + } + + return 0; + } + + useMessageEvent(UserCreditsEvent, event => + { + const parser = event.getParser(); + + setPurse(prevValue => + { + const newValue = CloneObject(prevValue); + + newValue.credits = parseFloat(parser.credits); + + if(prevValue.credits !== newValue.credits) PlaySound(SoundNames.CREDITS); + + return newValue; + }); + }); + + useMessageEvent(UserCurrencyEvent, event => + { + const parser = event.getParser(); + + setPurse(prevValue => + { + const newValue = CloneObject(prevValue); + + newValue.activityPoints = parser.currencies; + + return newValue; + }); + }); + + useMessageEvent(ActivityPointNotificationMessageEvent, event => + { + const parser = event.getParser(); + + setPurse(prevValue => + { + const newValue = CloneObject(prevValue); + + newValue.activityPoints = new Map(newValue.activityPoints); + + newValue.activityPoints.set(parser.type, parser.amount); + + if(parser.type === 0) PlaySound(SoundNames.DUCKETS) + + return newValue; + }); + }); + + useMessageEvent(UserSubscriptionEvent, event => + { + const parser = event.getParser(); + const productName = parser.productName; + + if((productName !== 'club_habbo') && (productName !== 'habbo_club')) return; + + setPurse(prevValue => + { + const newValue = CloneObject(prevValue); + + newValue.clubDays = Math.max(0, parser.daysToPeriodEnd); + newValue.clubPeriods = Math.max(0, parser.periodsSubscribedAhead); + newValue.isVip = parser.isVip; + newValue.pastClubDays = parser.pastClubDays; + newValue.pastVipDays = parser.pastVipDays; + newValue.isExpiring = ((parser.responseType === UserSubscriptionParser.RESPONSE_TYPE_DISCOUNT_AVAILABLE) ? true : false); + newValue.minutesUntilExpiration = parser.minutesUntilExpiration; + newValue.minutesSinceLastModified = parser.minutesSinceLastModified; + + return newValue; + }); + }); + + useEffect(() => + { + if(hcDisabled) return; + + SendMessageComposer(new UserSubscriptionComposer('habbo_club')); + + const interval = setInterval(() => SendMessageComposer(new UserSubscriptionComposer('habbo_club')), 50000); + + return () => clearInterval(interval); + }, [ hcDisabled ]); + + useEffect(() => + { + SendMessageComposer(new UserCurrencyComposer()); + }, []); + + return { purse, hcDisabled, clubStatus, getCurrencyAmount }; +} + +export const usePurse = () => useBetween(usePurseState); diff --git a/src/hooks/rooms/engine/index.ts b/src/hooks/rooms/engine/index.ts new file mode 100644 index 0000000..42364b6 --- /dev/null +++ b/src/hooks/rooms/engine/index.ts @@ -0,0 +1,9 @@ +export * from './useFurniAddedEvent'; +export * from './useFurniRemovedEvent'; +export * from './useObjectDeselectedEvent'; +export * from './useObjectDoubleClickedEvent'; +export * from './useObjectRollOutEvent'; +export * from './useObjectRollOverEvent'; +export * from './useObjectSelectedEvent'; +export * from './useUserAddedEvent'; +export * from './useUserRemovedEvent'; diff --git a/src/hooks/rooms/engine/useFurniAddedEvent.ts b/src/hooks/rooms/engine/useFurniAddedEvent.ts new file mode 100644 index 0000000..f5422c6 --- /dev/null +++ b/src/hooks/rooms/engine/useFurniAddedEvent.ts @@ -0,0 +1,19 @@ +import { useEffect } from 'react'; +import { RoomWidgetUpdateRoomObjectEvent, UI_EVENT_DISPATCHER } from '../../../api'; + +export const useFurniAddedEvent = (isActive: boolean, handler: (event: RoomWidgetUpdateRoomObjectEvent) => void) => +{ + useEffect(() => + { + if(!isActive) return; + + const onRoomWidgetUpdateRoomObjectEvent = (event: RoomWidgetUpdateRoomObjectEvent) => handler(event); + + UI_EVENT_DISPATCHER.addEventListener(RoomWidgetUpdateRoomObjectEvent.FURNI_ADDED, onRoomWidgetUpdateRoomObjectEvent); + + return () => + { + UI_EVENT_DISPATCHER.removeEventListener(RoomWidgetUpdateRoomObjectEvent.FURNI_ADDED, onRoomWidgetUpdateRoomObjectEvent); + } + }, [ isActive, handler ]); +} diff --git a/src/hooks/rooms/engine/useFurniRemovedEvent.ts b/src/hooks/rooms/engine/useFurniRemovedEvent.ts new file mode 100644 index 0000000..ddd1be2 --- /dev/null +++ b/src/hooks/rooms/engine/useFurniRemovedEvent.ts @@ -0,0 +1,19 @@ +import { useEffect } from 'react'; +import { RoomWidgetUpdateRoomObjectEvent, UI_EVENT_DISPATCHER } from '../../../api'; + +export const useFurniRemovedEvent = (isActive: boolean, handler: (event: RoomWidgetUpdateRoomObjectEvent) => void) => +{ + useEffect(() => + { + if(!isActive) return; + + const onRoomWidgetUpdateRoomObjectEvent = (event: RoomWidgetUpdateRoomObjectEvent) => handler(event); + + UI_EVENT_DISPATCHER.addEventListener(RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED, onRoomWidgetUpdateRoomObjectEvent); + + return () => + { + UI_EVENT_DISPATCHER.removeEventListener(RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED, onRoomWidgetUpdateRoomObjectEvent); + } + }, [ isActive, handler ]); +} diff --git a/src/hooks/rooms/engine/useObjectDeselectedEvent.ts b/src/hooks/rooms/engine/useObjectDeselectedEvent.ts new file mode 100644 index 0000000..ba54981 --- /dev/null +++ b/src/hooks/rooms/engine/useObjectDeselectedEvent.ts @@ -0,0 +1,7 @@ +import { RoomWidgetUpdateRoomObjectEvent } from '../../../api'; +import { useUiEvent } from '../../events'; + +export const useObjectDeselectedEvent = (handler: (event: RoomWidgetUpdateRoomObjectEvent) => void) => +{ + useUiEvent(RoomWidgetUpdateRoomObjectEvent.OBJECT_DESELECTED, handler); +} diff --git a/src/hooks/rooms/engine/useObjectDoubleClickedEvent.ts b/src/hooks/rooms/engine/useObjectDoubleClickedEvent.ts new file mode 100644 index 0000000..66e3673 --- /dev/null +++ b/src/hooks/rooms/engine/useObjectDoubleClickedEvent.ts @@ -0,0 +1,7 @@ +import { RoomWidgetUpdateRoomObjectEvent } from '../../../api'; +import { useUiEvent } from '../../events'; + +export const useObjectDoubleClickedEvent = (handler: (event: RoomWidgetUpdateRoomObjectEvent) => void) => +{ + useUiEvent(RoomWidgetUpdateRoomObjectEvent.OBJECT_DOUBLE_CLICKED, handler); +} diff --git a/src/hooks/rooms/engine/useObjectRollOutEvent.ts b/src/hooks/rooms/engine/useObjectRollOutEvent.ts new file mode 100644 index 0000000..433ca91 --- /dev/null +++ b/src/hooks/rooms/engine/useObjectRollOutEvent.ts @@ -0,0 +1,7 @@ +import { RoomWidgetUpdateRoomObjectEvent } from '../../../api'; +import { useUiEvent } from '../../events'; + +export const useObjectRollOutEvent = (handler: (event: RoomWidgetUpdateRoomObjectEvent) => void) => +{ + useUiEvent(RoomWidgetUpdateRoomObjectEvent.OBJECT_ROLL_OUT, handler); +} diff --git a/src/hooks/rooms/engine/useObjectRollOverEvent.ts b/src/hooks/rooms/engine/useObjectRollOverEvent.ts new file mode 100644 index 0000000..2774ef2 --- /dev/null +++ b/src/hooks/rooms/engine/useObjectRollOverEvent.ts @@ -0,0 +1,7 @@ +import { RoomWidgetUpdateRoomObjectEvent } from '../../../api'; +import { useUiEvent } from '../../events'; + +export const useObjectRollOverEvent = (handler: (event: RoomWidgetUpdateRoomObjectEvent) => void) => +{ + useUiEvent(RoomWidgetUpdateRoomObjectEvent.OBJECT_ROLL_OVER, handler); +} diff --git a/src/hooks/rooms/engine/useObjectSelectedEvent.ts b/src/hooks/rooms/engine/useObjectSelectedEvent.ts new file mode 100644 index 0000000..38db66f --- /dev/null +++ b/src/hooks/rooms/engine/useObjectSelectedEvent.ts @@ -0,0 +1,7 @@ +import { RoomWidgetUpdateRoomObjectEvent } from '../../../api'; +import { useUiEvent } from '../../events'; + +export const useObjectSelectedEvent = (handler: (event: RoomWidgetUpdateRoomObjectEvent) => void) => +{ + useUiEvent(RoomWidgetUpdateRoomObjectEvent.OBJECT_SELECTED, handler); +} diff --git a/src/hooks/rooms/engine/useUserAddedEvent.ts b/src/hooks/rooms/engine/useUserAddedEvent.ts new file mode 100644 index 0000000..d3c761b --- /dev/null +++ b/src/hooks/rooms/engine/useUserAddedEvent.ts @@ -0,0 +1,19 @@ +import { useEffect } from 'react'; +import { RoomWidgetUpdateRoomObjectEvent, UI_EVENT_DISPATCHER } from '../../../api'; + +export const useUserAddedEvent = (isActive: boolean, handler: (event: RoomWidgetUpdateRoomObjectEvent) => void) => +{ + useEffect(() => + { + if(!isActive) return; + + const onRoomWidgetUpdateRoomObjectEvent = (event: RoomWidgetUpdateRoomObjectEvent) => handler(event); + + UI_EVENT_DISPATCHER.addEventListener(RoomWidgetUpdateRoomObjectEvent.USER_ADDED, onRoomWidgetUpdateRoomObjectEvent); + + return () => + { + UI_EVENT_DISPATCHER.removeEventListener(RoomWidgetUpdateRoomObjectEvent.USER_ADDED, onRoomWidgetUpdateRoomObjectEvent); + } + }, [ isActive, handler ]); +} diff --git a/src/hooks/rooms/engine/useUserRemovedEvent.ts b/src/hooks/rooms/engine/useUserRemovedEvent.ts new file mode 100644 index 0000000..2820241 --- /dev/null +++ b/src/hooks/rooms/engine/useUserRemovedEvent.ts @@ -0,0 +1,19 @@ +import { useEffect } from 'react'; +import { RoomWidgetUpdateRoomObjectEvent, UI_EVENT_DISPATCHER } from '../../../api'; + +export const useUserRemovedEvent = (isActive: boolean, handler: (event: RoomWidgetUpdateRoomObjectEvent) => void) => +{ + useEffect(() => + { + if(!isActive) return; + + const onRoomWidgetUpdateRoomObjectEvent = (event: RoomWidgetUpdateRoomObjectEvent) => handler(event); + + UI_EVENT_DISPATCHER.addEventListener(RoomWidgetUpdateRoomObjectEvent.USER_REMOVED, onRoomWidgetUpdateRoomObjectEvent); + + return () => + { + UI_EVENT_DISPATCHER.removeEventListener(RoomWidgetUpdateRoomObjectEvent.USER_REMOVED, onRoomWidgetUpdateRoomObjectEvent); + } + }, [ isActive, handler ]); +} diff --git a/src/hooks/rooms/index.ts b/src/hooks/rooms/index.ts new file mode 100644 index 0000000..e57ecfb --- /dev/null +++ b/src/hooks/rooms/index.ts @@ -0,0 +1,4 @@ +export * from './engine'; +export * from './promotes'; +export * from './useRoom'; +export * from './widgets'; diff --git a/src/hooks/rooms/promotes/index.ts b/src/hooks/rooms/promotes/index.ts new file mode 100644 index 0000000..b1fd0d3 --- /dev/null +++ b/src/hooks/rooms/promotes/index.ts @@ -0,0 +1 @@ +export * from './useRoomPromote'; diff --git a/src/hooks/rooms/promotes/useRoomPromote.ts b/src/hooks/rooms/promotes/useRoomPromote.ts new file mode 100644 index 0000000..e534d18 --- /dev/null +++ b/src/hooks/rooms/promotes/useRoomPromote.ts @@ -0,0 +1,23 @@ +import { RoomEventEvent, RoomEventMessageParser } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { useBetween } from 'use-between'; +import { useMessageEvent } from '../../events'; + +const useRoomPromoteState = () => +{ + const [ promoteInformation, setPromoteInformation ] = useState(null); + const [ isExtended, setIsExtended ] = useState(false); + + useMessageEvent(RoomEventEvent, event => + { + const parser = event.getParser(); + + if (!parser) return; + + setPromoteInformation(parser); + }); + + return { promoteInformation, isExtended, setPromoteInformation, setIsExtended }; +} + +export const useRoomPromote = () => useBetween(useRoomPromoteState); diff --git a/src/hooks/rooms/useRoom.ts b/src/hooks/rooms/useRoom.ts new file mode 100644 index 0000000..0ad1732 --- /dev/null +++ b/src/hooks/rooms/useRoom.ts @@ -0,0 +1,283 @@ +import { ColorConverter, GetRenderer, GetRoomEngine, GetStage, IRoomSession, NitroAdjustmentFilter, NitroSprite, NitroTexture, RoomBackgroundColorEvent, RoomEngineEvent, RoomEngineObjectEvent, RoomGeometry, RoomId, RoomObjectCategory, RoomObjectHSLColorEnabledEvent, RoomObjectOperationType, RoomSessionEvent, RoomVariableEnum, Vector3d } from '@nitrots/nitro-renderer'; +import { useEffect, useState } from 'react'; +import { useBetween } from 'use-between'; +import { CanManipulateFurniture, DispatchUiEvent, GetRoomSession, InitializeRoomInstanceRenderingCanvas, IsFurnitureSelectionDisabled, ProcessRoomObjectOperation, RoomWidgetUpdateBackgroundColorPreviewEvent, RoomWidgetUpdateRoomObjectEvent, SetActiveRoomId, StartRoomSession } from '../../api'; +import { useNitroEvent, useUiEvent } from '../events'; + +const useRoomState = () => +{ + const [ roomSession, setRoomSession ] = useState(null); + const [ roomBackground, setRoomBackground ] = useState(null); + const [ roomFilter, setRoomFilter ] = useState(null); + const [ originalRoomBackgroundColor, setOriginalRoomBackgroundColor ] = useState(0); + + 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); + + if(!hue && !saturation && !lightness) + { + roomBackground.tint = 0; + } + else + { + roomBackground.tint = newColor; + } + } + + const updateRoomFilter = (color: number) => + { + if(!roomFilter) return; + + const r = ((color >> 16) & 0xFF); + const g = ((color >> 8) & 0xFF); + const b = (color & 0xFF); + + roomFilter.red = (r / 255); + roomFilter.green = (g / 255); + roomFilter.blue = (b / 255); + } + + useUiEvent(RoomWidgetUpdateBackgroundColorPreviewEvent.PREVIEW, event => updateRoomBackgroundColor(event.hue, event.saturation, event.lightness)); + + useUiEvent(RoomWidgetUpdateBackgroundColorPreviewEvent.CLEAR_PREVIEW, event => + { + if(!roomBackground) return; + + roomBackground.tint = originalRoomBackgroundColor; + }); + + useNitroEvent(RoomObjectHSLColorEnabledEvent.ROOM_BACKGROUND_COLOR, event => + { + if(RoomId.isRoomPreviewerId(event.roomId)) return; + + if(event.enable) updateRoomBackgroundColor(event.hue, event.saturation, event.lightness, true); + else updateRoomBackgroundColor(0, 0, 0, true); + }); + + useNitroEvent(RoomBackgroundColorEvent.ROOM_COLOR, event => + { + if(RoomId.isRoomPreviewerId(event.roomId)) return; + + let color = 0x000000; + let brightness = 0xFF; + + if(!event.bgOnly) + { + color = event.color; + brightness = event.brightness; + } + + updateRoomFilter(ColorConverter.hslToRGB(((ColorConverter.rgbToHSL(color) & 0xFFFF00) + brightness))); + }); + + useNitroEvent([ + RoomEngineEvent.INITIALIZED, + RoomEngineEvent.DISPOSED + ], event => + { + if(RoomId.isRoomPreviewerId(event.roomId)) return; + + const session = GetRoomSession(); + + if(!session) return; + + switch(event.type) + { + case RoomEngineEvent.INITIALIZED: + SetActiveRoomId(event.roomId); + setRoomSession(session); + return; + case RoomEngineEvent.DISPOSED: + setRoomSession(null); + return; + } + }); + + useNitroEvent([ + RoomSessionEvent.CREATED, + RoomSessionEvent.ENDED + ], event => + { + switch(event.type) + { + case RoomSessionEvent.CREATED: + StartRoomSession(event.session); + return; + case RoomSessionEvent.ENDED: + setRoomSession(null); + return; + } + }); + + useNitroEvent([ + RoomEngineObjectEvent.SELECTED, + RoomEngineObjectEvent.DESELECTED, + RoomEngineObjectEvent.ADDED, + RoomEngineObjectEvent.REMOVED, + RoomEngineObjectEvent.PLACED, + RoomEngineObjectEvent.REQUEST_MOVE, + RoomEngineObjectEvent.REQUEST_ROTATE, + RoomEngineObjectEvent.MOUSE_ENTER, + RoomEngineObjectEvent.MOUSE_LEAVE, + RoomEngineObjectEvent.DOUBLE_CLICK + ], event => + { + if(RoomId.isRoomPreviewerId(event.roomId)) return; + + let updateEvent: RoomWidgetUpdateRoomObjectEvent = null; + + switch(event.type) + { + case RoomEngineObjectEvent.SELECTED: + if(!IsFurnitureSelectionDisabled(event)) updateEvent = new RoomWidgetUpdateRoomObjectEvent(RoomWidgetUpdateRoomObjectEvent.OBJECT_SELECTED, event.objectId, event.category, event.roomId); + break; + case RoomEngineObjectEvent.DESELECTED: + updateEvent = new RoomWidgetUpdateRoomObjectEvent(RoomWidgetUpdateRoomObjectEvent.OBJECT_DESELECTED, event.objectId, event.category, event.roomId); + break; + case RoomEngineObjectEvent.ADDED: { + let addedEventType: string = null; + + switch(event.category) + { + case RoomObjectCategory.FLOOR: + case RoomObjectCategory.WALL: + addedEventType = RoomWidgetUpdateRoomObjectEvent.FURNI_ADDED; + break; + case RoomObjectCategory.UNIT: + addedEventType = RoomWidgetUpdateRoomObjectEvent.USER_ADDED; + break; + } + + if(addedEventType) updateEvent = new RoomWidgetUpdateRoomObjectEvent(addedEventType, event.objectId, event.category, event.roomId); + break; + } + case RoomEngineObjectEvent.REMOVED: { + let removedEventType: string = null; + + switch(event.category) + { + case RoomObjectCategory.FLOOR: + case RoomObjectCategory.WALL: + removedEventType = RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED; + break; + case RoomObjectCategory.UNIT: + removedEventType = RoomWidgetUpdateRoomObjectEvent.USER_REMOVED; + break; + } + + if(removedEventType) updateEvent = new RoomWidgetUpdateRoomObjectEvent(removedEventType, event.objectId, event.category, event.roomId); + break; + } + case RoomEngineObjectEvent.REQUEST_MOVE: + if(CanManipulateFurniture(roomSession, event.objectId, event.category)) ProcessRoomObjectOperation(event.objectId, event.category, RoomObjectOperationType.OBJECT_MOVE); + break; + case RoomEngineObjectEvent.REQUEST_ROTATE: + if(CanManipulateFurniture(roomSession, event.objectId, event.category)) ProcessRoomObjectOperation(event.objectId, event.category, RoomObjectOperationType.OBJECT_ROTATE_POSITIVE); + break; + case RoomEngineObjectEvent.MOUSE_ENTER: + updateEvent = new RoomWidgetUpdateRoomObjectEvent(RoomWidgetUpdateRoomObjectEvent.OBJECT_ROLL_OVER, event.objectId, event.category, event.roomId); + break; + case RoomEngineObjectEvent.MOUSE_LEAVE: + updateEvent = new RoomWidgetUpdateRoomObjectEvent(RoomWidgetUpdateRoomObjectEvent.OBJECT_ROLL_OUT, event.objectId, event.category, event.roomId); + break; + case RoomEngineObjectEvent.DOUBLE_CLICK: + updateEvent = new RoomWidgetUpdateRoomObjectEvent(RoomWidgetUpdateRoomObjectEvent.OBJECT_DOUBLE_CLICKED, event.objectId, event.category, event.roomId); + break; + } + + if(updateEvent) DispatchUiEvent(updateEvent); + }); + + useEffect(() => + { + if(!roomSession) return; + + const roomEngine = GetRoomEngine(); + const roomId = roomSession.roomId; + const canvasId = 1; + const width = Math.floor(window.innerWidth); + const height = Math.floor(window.innerHeight); + const renderer = GetRenderer(); + + if(renderer) renderer.resize(width, height); + + const displayObject = roomEngine.getRoomInstanceDisplay(roomId, canvasId, width, height, RoomGeometry.SCALE_ZOOMED_IN); + const canvas = GetRoomEngine().getRoomInstanceRenderingCanvas(roomId, canvasId); + + if(!displayObject || !canvas) return; + + const background = new NitroSprite(NitroTexture.WHITE); + const filter = new NitroAdjustmentFilter(); + const master = canvas.master; + + background.tint = 0; + background.width = width; + background.height = height; + + master.addChildAt(background, 0); + master.filters = [ filter ]; + + setRoomBackground(background); + setRoomFilter(filter); + + const geometry = (roomEngine.getRoomInstanceGeometry(roomId, canvasId) as RoomGeometry); + + if(geometry) + { + const minX = (roomEngine.getRoomInstanceVariable(roomId, RoomVariableEnum.ROOM_MIN_X) || 0); + const maxX = (roomEngine.getRoomInstanceVariable(roomId, RoomVariableEnum.ROOM_MAX_X) || 0); + const minY = (roomEngine.getRoomInstanceVariable(roomId, RoomVariableEnum.ROOM_MIN_Y) || 0); + const maxY = (roomEngine.getRoomInstanceVariable(roomId, RoomVariableEnum.ROOM_MAX_Y) || 0); + + let x = ((minX + maxX) / 2); + let y = ((minY + maxY) / 2); + + const offset = 20; + + x = (x + (offset - 1)); + y = (y + (offset - 1)); + + const z = (Math.sqrt(((offset * offset) + (offset * offset))) * Math.tan(((30 / 180) * Math.PI))); + + geometry.location = new Vector3d(x, y, z); + } + + GetStage().addChild(displayObject); + + SetActiveRoomId(roomSession.roomId); + + const resize = (event: UIEvent) => + { + const width = Math.floor(window.innerWidth); + const height = Math.floor(window.innerHeight); + + renderer.resolution = window.devicePixelRatio; + renderer.resize(width, height); + + background.width = width; + background.height = height; + + InitializeRoomInstanceRenderingCanvas(width, height, 1); + } + + window.addEventListener('resize', resize); + + return () => + { + setRoomBackground(null); + setRoomFilter(null); + setOriginalRoomBackgroundColor(0); + + window.removeEventListener('resize', resize); + } + }, [ roomSession ]); + + return { roomSession }; +} + +export const useRoom = () => useBetween(useRoomState); diff --git a/src/hooks/rooms/widgets/furniture/index.ts b/src/hooks/rooms/widgets/furniture/index.ts new file mode 100644 index 0000000..37fa573 --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/index.ts @@ -0,0 +1,19 @@ +export * from './useFurnitureBackgroundColorWidget'; +export * from './useFurnitureBadgeDisplayWidget'; +export * from './useFurnitureContextMenuWidget'; +export * from './useFurnitureCraftingWidget'; +export * from './useFurnitureDimmerWidget'; +export * from './useFurnitureExchangeWidget'; +export * from './useFurnitureExternalImageWidget'; +export * from './useFurnitureFriendFurniWidget'; +export * from './useFurnitureHighScoreWidget'; +export * from './useFurnitureInternalLinkWidget'; +export * from './useFurnitureMannequinWidget'; +export * from './useFurniturePlaylistEditorWidget'; +export * from './useFurniturePresentWidget'; +export * from './useFurnitureRoomLinkWidget'; +export * from './useFurnitureSpamWallPostItWidget'; +export * from './useFurnitureStackHeightWidget'; +export * from './useFurnitureStickieWidget'; +export * from './useFurnitureTrophyWidget'; +export * from './useFurnitureYoutubeWidget'; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureBackgroundColorWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureBackgroundColorWidget.ts new file mode 100644 index 0000000..a605f28 --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureBackgroundColorWidget.ts @@ -0,0 +1,71 @@ +import { ApplyTonerComposer, ColorConverter, GetRoomEngine, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { useEffect, useState } from 'react'; +import { CanManipulateFurniture, ColorUtils, DispatchUiEvent, RoomWidgetUpdateBackgroundColorPreviewEvent, SendMessageComposer } from '../../../../api'; +import { useNitroEvent } from '../../../events'; +import { useFurniRemovedEvent } from '../../engine'; +import { useRoom } from '../../useRoom'; + +const useFurnitureBackgroundColorWidgetState = () => +{ + const [ objectId, setObjectId ] = useState(-1); + const [ category, setCategory ] = useState(-1); + const [ color, setColor ] = useState(0); + const { roomSession = null } = useRoom(); + + const applyToner = () => + { + const hsl = ColorConverter.rgbToHSL(color); + const [ _, hue, saturation, lightness ] = ColorUtils.int_to_8BitVals(hsl); + SendMessageComposer(new ApplyTonerComposer(objectId, hue, saturation, lightness)); + } + + const toggleToner = () => roomSession.useMultistateItem(objectId); + + const onClose = () => + { + DispatchUiEvent(new RoomWidgetUpdateBackgroundColorPreviewEvent(RoomWidgetUpdateBackgroundColorPreviewEvent.CLEAR_PREVIEW)); + + setObjectId(-1); + setCategory(-1); + setColor(0); + } + + useNitroEvent(RoomEngineTriggerWidgetEvent.REQUEST_BACKGROUND_COLOR, event => + { + if(!CanManipulateFurniture(roomSession, event.objectId, event.category)) return; + + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + const model = roomObject.model; + + setObjectId(event.objectId); + setCategory(event.category) + const hue = parseInt(model.getValue(RoomObjectVariable.FURNITURE_ROOM_BACKGROUND_COLOR_HUE)); + const saturation = parseInt(model.getValue(RoomObjectVariable.FURNITURE_ROOM_BACKGROUND_COLOR_SATURATION)); + const light = parseInt(model.getValue(RoomObjectVariable.FURNITURE_ROOM_BACKGROUND_COLOR_LIGHTNESS)); + + const hsl = ColorUtils.eight_bitVals_to_int(0, hue,saturation,light); + + const rgbColor = ColorConverter.hslToRGB(hsl); + setColor(rgbColor); + }); + + useFurniRemovedEvent(((objectId !== -1) && (category !== -1)), event => + { + if((event.id !== objectId) || (event.category !== category)) return; + + onClose(); + }); + + useEffect(() => + { + if((objectId === -1) || (category === -1)) return; + + const hls = ColorConverter.rgbToHSL(color); + const [ _, hue, saturation, lightness ] = ColorUtils.int_to_8BitVals(hls); + DispatchUiEvent(new RoomWidgetUpdateBackgroundColorPreviewEvent(RoomWidgetUpdateBackgroundColorPreviewEvent.PREVIEW, hue, saturation, lightness)); + }, [ objectId, category, color ]); + + return { objectId, color, setColor, applyToner, toggleToner, onClose }; +} + +export const useFurnitureBackgroundColorWidget = useFurnitureBackgroundColorWidgetState; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureBadgeDisplayWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureBadgeDisplayWidget.ts new file mode 100644 index 0000000..1cc513c --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureBadgeDisplayWidget.ts @@ -0,0 +1,75 @@ +import { GetRoomEngine, GetSessionDataManager, RoomEngineTriggerWidgetEvent, RoomObjectVariable, StringDataType } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { LocalizeBadgeDescription, LocalizeBadgeName, LocalizeText } from '../../../../api'; +import { useNitroEvent } from '../../../events'; +import { useNotification } from '../../../notification'; +import { useFurniRemovedEvent } from '../../engine'; + +const useFurnitureBadgeDisplayWidgetState = () => +{ + const [ objectId, setObjectId ] = useState(-1); + const [ category, setCategory ] = useState(-1); + const [ color, setColor ] = useState('1'); + const [ badgeName, setBadgeName ] = useState(''); + const [ badgeDesc, setBadgeDesc ] = useState(''); + const [ date, setDate ] = useState(''); + const [ senderName, setSenderName ] = useState(''); + const { simpleAlert = null } = useNotification(); + + const onClose = () => + { + setObjectId(-1); + setCategory(-1); + setColor('1'); + setBadgeName(''); + setBadgeDesc(''); + setDate(''); + setSenderName(''); + } + + useNitroEvent([ + RoomEngineTriggerWidgetEvent.REQUEST_BADGE_DISPLAY_ENGRAVING, + RoomEngineTriggerWidgetEvent.REQUEST_ACHIEVEMENT_RESOLUTION_ENGRAVING + ], event => + { + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + + if(!roomObject) return; + + const stringStuff = new StringDataType(); + + stringStuff.initializeFromRoomObjectModel(roomObject.model); + + setObjectId(event.objectId); + setCategory(event.category); + setColor('1'); + setBadgeName(LocalizeBadgeName(stringStuff.getValue(1))); + setBadgeDesc(LocalizeBadgeDescription(stringStuff.getValue(1))); + setDate(stringStuff.getValue(2)); + setSenderName(stringStuff.getValue(3)); + }); + + useNitroEvent(RoomEngineTriggerWidgetEvent.REQUEST_ACHIEVEMENT_RESOLUTION_FAILED, event => + { + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + + if(!roomObject) return; + + const ownerId = roomObject.model.getValue(RoomObjectVariable.FURNITURE_OWNER_ID); + + if(ownerId !== GetSessionDataManager().userId) return; + + simpleAlert(`${ LocalizeText('resolution.failed.subtitle') } ${ LocalizeText('resolution.failed.text') }`, null, null, null, LocalizeText('resolution.failed.title')); + }); + + useFurniRemovedEvent(((objectId !== -1) && (category !== -1)), event => + { + if((event.id !== objectId) || (event.category !== category)) return; + + onClose(); + }); + + return { objectId, category, color, badgeName, badgeDesc, date, senderName, onClose }; +} + +export const useFurnitureBadgeDisplayWidget = useFurnitureBadgeDisplayWidgetState; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureContextMenuWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureContextMenuWidget.ts new file mode 100644 index 0000000..c9616bb --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureContextMenuWidget.ts @@ -0,0 +1,179 @@ +import { ContextMenuEnum, GetRoomEngine, GroupFurniContextMenuInfoMessageEvent, GroupFurniContextMenuInfoMessageParser, RoomEngineTriggerWidgetEvent, RoomObjectCategory, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { IsOwnerOfFurniture, TryJoinGroup, TryVisitRoom } from '../../../../api'; +import { useMessageEvent, useNitroEvent } from '../../../events'; +import { useRoom } from '../../useRoom'; + +export const MONSTERPLANT_SEED_CONFIRMATION: string = 'MONSTERPLANT_SEED_CONFIRMATION'; +export const PURCHASABLE_CLOTHING_CONFIRMATION: string = 'PURCHASABLE_CLOTHING_CONFIRMATION'; +export const GROUP_FURNITURE: string = 'GROUP_FURNITURE'; +export const EFFECTBOX_OPEN: string = 'EFFECTBOX_OPEN'; +export const MYSTERYTROPHY_OPEN_DIALOG: string = 'MYSTERYTROPHY_OPEN_DIALOG'; + +const useFurnitureContextMenuWidgetState = () => +{ + const [ objectId, setObjectId ] = useState(-1); + const [ mode, setMode ] = useState(null); + const [ confirmMode, setConfirmMode ] = useState(null); + const [ confirmingObjectId, setConfirmingObjectId ] = useState(-1); + const [ groupData, setGroupData ] = useState(null); + const [ isGroupMember, setIsGroupMember ] = useState(false); + const [ objectOwnerId, setObjectOwnerId ] = useState(-1); + const { roomSession = null } = useRoom(); + + const onClose = () => + { + setObjectId(-1); + setGroupData(null); + setIsGroupMember(false); + setMode(null); + } + + const closeConfirm = () => + { + setConfirmMode(null); + setConfirmingObjectId(-1); + } + + const processAction = (name: string) => + { + if(name) + { + switch(name) + { + case 'use_friend_furni': + roomSession.useMultistateItem(objectId); + break; + case 'use_monsterplant_seed': + setConfirmMode(MONSTERPLANT_SEED_CONFIRMATION); + setConfirmingObjectId(objectId); + break; + case 'use_random_teleport': + GetRoomEngine().useRoomObject(objectId, RoomObjectCategory.FLOOR); + break; + case 'use_purchaseable_clothing': + setConfirmMode(PURCHASABLE_CLOTHING_CONFIRMATION); + setConfirmingObjectId(objectId); + break; + case 'use_mystery_box': + roomSession.useMultistateItem(objectId); + break; + case 'use_mystery_trophy': + setConfirmMode(MYSTERYTROPHY_OPEN_DIALOG); + setConfirmingObjectId(objectId); + break; + case 'join_group': + TryJoinGroup(groupData.guildId); + setIsGroupMember(true); + return; + case 'go_to_group_homeroom': + if(groupData) TryVisitRoom(groupData.guildHomeRoomId); + break; + } + } + + onClose(); + } + + useNitroEvent([ + RoomEngineTriggerWidgetEvent.OPEN_FURNI_CONTEXT_MENU, + RoomEngineTriggerWidgetEvent.CLOSE_FURNI_CONTEXT_MENU, + RoomEngineTriggerWidgetEvent.REQUEST_MONSTERPLANT_SEED_PLANT_CONFIRMATION_DIALOG, + RoomEngineTriggerWidgetEvent.REQUEST_PURCHASABLE_CLOTHING_CONFIRMATION_DIALOG, + RoomEngineTriggerWidgetEvent.REQUEST_EFFECTBOX_OPEN_DIALOG, + RoomEngineTriggerWidgetEvent.REQUEST_MYSTERYBOX_OPEN_DIALOG, + RoomEngineTriggerWidgetEvent.REQUEST_MYSTERYTROPHY_OPEN_DIALOG + ], event => + { + const object = GetRoomEngine().getRoomObject(roomSession.roomId, event.objectId, event.category); + + if(!object) return; + + setObjectOwnerId(object.model.getValue(RoomObjectVariable.FURNITURE_OWNER_ID)); + + switch(event.type) + { + case RoomEngineTriggerWidgetEvent.REQUEST_MONSTERPLANT_SEED_PLANT_CONFIRMATION_DIALOG: + if(!IsOwnerOfFurniture(object)) return; + + setConfirmingObjectId(object.id); + setConfirmMode(MONSTERPLANT_SEED_CONFIRMATION); + + onClose(); + return; + case RoomEngineTriggerWidgetEvent.REQUEST_EFFECTBOX_OPEN_DIALOG: + if(!IsOwnerOfFurniture(object)) return; + + setConfirmingObjectId(object.id); + setConfirmMode(EFFECTBOX_OPEN); + + onClose(); + return; + case RoomEngineTriggerWidgetEvent.REQUEST_PURCHASABLE_CLOTHING_CONFIRMATION_DIALOG: + if(!IsOwnerOfFurniture(object)) return; + + setConfirmingObjectId(object.id); + setConfirmMode(PURCHASABLE_CLOTHING_CONFIRMATION); + + onClose(); + return; + case RoomEngineTriggerWidgetEvent.REQUEST_MYSTERYBOX_OPEN_DIALOG: + roomSession.useMultistateItem(object.id); + + onClose(); + return; + case RoomEngineTriggerWidgetEvent.REQUEST_MYSTERYTROPHY_OPEN_DIALOG: + if(!IsOwnerOfFurniture(object)) return; + + setConfirmingObjectId(object.id); + setConfirmMode(MYSTERYTROPHY_OPEN_DIALOG); + + onClose(); + return; + case RoomEngineTriggerWidgetEvent.OPEN_FURNI_CONTEXT_MENU: + + setObjectId(object.id); + + switch(event.contextMenu) + { + case ContextMenuEnum.FRIEND_FURNITURE: + setMode(ContextMenuEnum.FRIEND_FURNITURE); + return; + case ContextMenuEnum.MONSTERPLANT_SEED: + if(IsOwnerOfFurniture(object)) setMode(ContextMenuEnum.MONSTERPLANT_SEED); + return; + case ContextMenuEnum.MYSTERY_BOX: + setMode(ContextMenuEnum.MYSTERY_BOX); + return; + case ContextMenuEnum.MYSTERY_TROPHY: + if(IsOwnerOfFurniture(object)) setMode(ContextMenuEnum.MYSTERY_TROPHY); + return; + case ContextMenuEnum.RANDOM_TELEPORT: + setMode(ContextMenuEnum.RANDOM_TELEPORT); + return; + case ContextMenuEnum.PURCHASABLE_CLOTHING: + if(IsOwnerOfFurniture(object)) setMode(ContextMenuEnum.PURCHASABLE_CLOTHING); + return; + } + + return; + case RoomEngineTriggerWidgetEvent.CLOSE_FURNI_CONTEXT_MENU: + if(object.id === objectId) onClose(); + return; + } + }); + + useMessageEvent(GroupFurniContextMenuInfoMessageEvent, event => + { + const parser = event.getParser(); + + setObjectId(parser.objectId); + setGroupData(parser); + setIsGroupMember(parser.userIsMember); + setMode(GROUP_FURNITURE); + }); + + return { objectId, mode, confirmMode, confirmingObjectId, groupData, isGroupMember, objectOwnerId, closeConfirm, processAction, onClose }; +} + +export const useFurnitureContextMenuWidget = useFurnitureContextMenuWidgetState; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureCraftingWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureCraftingWidget.ts new file mode 100644 index 0000000..d2bca20 --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureCraftingWidget.ts @@ -0,0 +1,166 @@ +import { CraftableProductsEvent, CraftComposer, CraftingRecipeEvent, CraftingRecipeIngredientParser, CraftingRecipesAvailableEvent, CraftingResultEvent, GetCraftableProductsComposer, GetCraftingRecipeComposer, GetRoomContentLoader, GetRoomEngine, RoomEngineTriggerWidgetEvent, RoomWidgetEnum } from '@nitrots/nitro-renderer'; +import { useEffect, useState } from 'react'; +import { ICraftingIngredient, ICraftingRecipe, LocalizeText, SendMessageComposer } from '../../../../api'; +import { useMessageEvent, useNitroEvent } from '../../../events'; +import { useInventoryFurni } from '../../../inventory'; +import { useNotification } from './../../../notification'; + +const useFurnitureCraftingWidgetState = () => +{ + const [ objectId, setObjectId ] = useState(-1); + const [ recipes, setRecipes ] = useState([]); + const [ selectedRecipe, setSelectedRecipe ] = useState(null); + const [ ingredients, setIngredients ] = useState([]); + const [ ingredientNames, setIngredientNames ] = useState(null); + const [ cachedIngredients, setCachedIngredients ] = useState>(new Map()); + const [ isCrafting, setIsCrafting ] = useState(false); + const { groupItems = [], getItemsByType = null, activate = null, deactivate = null } = useInventoryFurni(); + const { simpleAlert = null } = useNotification(); + + const requiredIngredients = ((selectedRecipe && cachedIngredients.get(selectedRecipe.name) || null)); + + const resetData = () => + { + setRecipes([]); + setSelectedRecipe(null); + setIngredients([]); + setCachedIngredients(new Map()); + }; + + const onClose = () => + { + setObjectId(-1); + resetData(); + }; + + const craft = () => + { + setIsCrafting(true); + + SendMessageComposer(new CraftComposer(objectId, selectedRecipe.name)); + }; + + const selectRecipe = (recipe: ICraftingRecipe) => + { + setSelectedRecipe(recipe); + + const cache = cachedIngredients.get(recipe.name); + + if(!cache) SendMessageComposer(new GetCraftingRecipeComposer(recipe.name)); + } + + useNitroEvent(RoomEngineTriggerWidgetEvent.OPEN_WIDGET, event => + { + if (event.widget !== RoomWidgetEnum.CRAFTING) return; + + setObjectId(event.objectId); + resetData(); + SendMessageComposer(new GetCraftableProductsComposer(event.objectId)); + }); + + useMessageEvent(CraftableProductsEvent, event => + { + const parser = event.getParser(); + + if (!parser.isActive()) + { + setObjectId(-1); + + return; + } + + setRecipes(prevValue => + { + const newValue: ICraftingRecipe[] = []; + + for(const recipe of parser.recipes) + { + //@ts-ignore + const itemId = GetRoomContentLoader()._activeObjectTypeIds.get(recipe.itemName); + const iconUrl = GetRoomEngine().getFurnitureFloorIconUrl(itemId); + + newValue.push({ + name: recipe.recipeName, + localizedName: LocalizeText('roomItem.name.' + itemId), + iconUrl + }); + } + + return newValue; + }); + + setIngredientNames(parser.ingredients); + }); + + useMessageEvent(CraftingRecipeEvent, event => + { + const parser = event.getParser(); + + setCachedIngredients(prevValue => + { + const newValue = new Map(prevValue); + + newValue.set(selectedRecipe.name, parser.ingredients); + + return newValue; + }); + }); + + useMessageEvent(CraftingResultEvent, event => + { + setSelectedRecipe(null); + setIsCrafting(false); + + const parser = event.getParser(); + + if(parser.result) simpleAlert(LocalizeText('crafting.info.result.ok')); + }); + + useMessageEvent(CraftingRecipesAvailableEvent, event => + { + }); + + useEffect(() => + { + if(!ingredientNames || !ingredientNames.length) return; + + setIngredients(prevValue => + { + const newValue: ICraftingIngredient[] = []; + + for(const name of ingredientNames) + { + //@ts-ignore + const itemId = GetRoomContentLoader()._activeObjectTypeIds.get(name); + const iconUrl = GetRoomEngine().getFurnitureFloorIconUrl(itemId); + + const inventoryItems = getItemsByType(itemId); + + let amountAvailable = 0; + + if (inventoryItems) for (const inventoryItem of inventoryItems) amountAvailable += inventoryItem.items.length; + + newValue.push({ + name: name, + iconUrl, + count: amountAvailable + }); + } + + return newValue; + }); + }, [ groupItems, ingredientNames, getItemsByType ]); + + useEffect(() => + { + if((objectId === -1)) return; + + const id = activate(); + + return () => deactivate(id); + }, [ objectId, activate, deactivate ]); + + return { objectId, recipes, ingredients, selectedRecipe, requiredIngredients, isCrafting, selectRecipe, craft, onClose }; +} + +export const useFurnitureCraftingWidget = useFurnitureCraftingWidgetState; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureDimmerWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureDimmerWidget.ts new file mode 100644 index 0000000..5c206a7 --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureDimmerWidget.ts @@ -0,0 +1,110 @@ +import { GetSessionDataManager, RoomControllerLevel, RoomEngineDimmerStateEvent, RoomEngineTriggerWidgetEvent, RoomId, RoomSessionDimmerPresetsEvent } from '@nitrots/nitro-renderer'; +import { useEffect, useState } from 'react'; +import { DimmerFurnitureWidgetPresetItem, FurnitureDimmerUtilities } from '../../../../api'; +import { useNitroEvent } from '../../../events'; +import { useRoom } from '../../useRoom'; + +const useFurnitureDimmerWidgetState = () => +{ + const [ presets, setPresets ] = useState([]); + const [ selectedPresetId, setSelectedPresetId ] = useState(0); + const [ dimmerState, setDimmerState ] = useState(0); + const [ lastDimmerState, setLastDimmerState ] = useState(0); + const [ effectId, setEffectId ] = useState(0); + const [ color, setColor ] = useState(0xFFFFFF); + const [ brightness, setBrightness ] = useState(0xFF); + const [ selectedEffectId, setSelectedEffectId ] = useState(0); + const [ selectedColor, setSelectedColor ] = useState(0); + const [ selectedBrightness, setSelectedBrightness ] = useState(0); + const { roomSession = null } = useRoom(); + + const canOpenWidget = () => (roomSession.isRoomOwner || (roomSession.controllerLevel >= RoomControllerLevel.GUEST) || GetSessionDataManager().isModerator); + + const selectPresetId = (id: number) => + { + const preset = presets[(id - 1)]; + + if(!preset) return; + + setSelectedPresetId(preset.id); + setSelectedEffectId(preset.type); + setSelectedColor(preset.color); + setSelectedBrightness(preset.light); + } + + const applyChanges = () => + { + if(dimmerState === 0) return; + + const selectedPresetIndex = (selectedPresetId - 1); + + if((selectedPresetId < 1) || (selectedPresetId > presets.length)) return; + + const preset = presets[selectedPresetIndex]; + + if(!preset || ((selectedEffectId === preset.type) && (selectedColor === preset.color) && (selectedBrightness === preset.light))) return; + + setPresets(prevValue => + { + const newValue = [ ...prevValue ]; + + newValue[selectedPresetIndex] = new DimmerFurnitureWidgetPresetItem(preset.id, selectedEffectId, selectedColor, selectedBrightness); + + return newValue; + }); + + FurnitureDimmerUtilities.savePreset(preset.id, selectedEffectId, selectedColor, selectedBrightness, true); + } + + useNitroEvent(RoomEngineTriggerWidgetEvent.REQUEST_DIMMER, event => + { + if(!canOpenWidget()) return; + + roomSession.requestMoodlightSettings(); + }); + + useNitroEvent(RoomSessionDimmerPresetsEvent.ROOM_DIMMER_PRESETS, event => + { + const presets: DimmerFurnitureWidgetPresetItem[] = []; + + let i = 0; + + while(i < event.presetCount) + { + const preset = event.getPreset(i); + + if(preset) presets.push(new DimmerFurnitureWidgetPresetItem(preset.id, preset.type, preset.color, preset.brightness)); + + i++; + } + + setPresets(presets); + setSelectedPresetId(event.selectedPresetId); + }); + + useNitroEvent(RoomEngineDimmerStateEvent.ROOM_COLOR, event => + { + if(RoomId.isRoomPreviewerId(event.roomId)) return; + + setLastDimmerState(dimmerState); + setDimmerState(event.state); + setSelectedPresetId(event.presetId); + setEffectId(event.effectId); + setSelectedEffectId(event.effectId); + setColor(event.color); + setSelectedColor(event.color); + setBrightness(event.brightness); + setSelectedBrightness(event.brightness); + }); + + useEffect(() => + { + if((dimmerState === 0) && (lastDimmerState === 0)) return; + + FurnitureDimmerUtilities.previewDimmer(selectedColor, selectedBrightness, (selectedEffectId === 2)); + }, [ dimmerState, lastDimmerState, selectedColor, selectedBrightness, selectedEffectId ]); + + return { presets, selectedPresetId, dimmerState, lastDimmerState, effectId, color, brightness, selectedEffectId, setSelectedEffectId, selectedColor, setSelectedColor, selectedBrightness, setSelectedBrightness, selectPresetId, applyChanges }; +} + +export const useFurnitureDimmerWidget = useFurnitureDimmerWidgetState; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureExchangeWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureExchangeWidget.ts new file mode 100644 index 0000000..dc3404f --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureExchangeWidget.ts @@ -0,0 +1,48 @@ +import { FurnitureExchangeComposer, GetRoomEngine, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { IsOwnerOfFurniture, SendMessageComposer } from '../../../../api'; +import { useNitroEvent } from '../../../events'; +import { useFurniRemovedEvent } from '../../engine'; + +const useFurnitureExchangeWidgetState = () => +{ + const [ objectId, setObjectId ] = useState(-1); + const [ category, setCategory ] = useState(-1); + const [ value, setValue ] = useState(0); + + const onClose = () => + { + setObjectId(-1); + setCategory(-1); + setValue(0); + } + + const redeem = () => + { + SendMessageComposer(new FurnitureExchangeComposer(objectId)); + + onClose(); + } + + useNitroEvent(RoomEngineTriggerWidgetEvent.REQUEST_CREDITFURNI, event => + { + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + + if(!roomObject || !IsOwnerOfFurniture(roomObject)) return; + + setObjectId(event.objectId); + setCategory(event.category); + setValue(roomObject.model.getValue(RoomObjectVariable.FURNITURE_CREDIT_VALUE) || 0); + }); + + useFurniRemovedEvent(((objectId !== -1) && (category !== -1)), event => + { + if((event.id !== objectId) || (event.category !== category)) return; + + onClose(); + }); + + return { objectId, value, redeem, onClose }; +} + +export const useFurnitureExchangeWidget = useFurnitureExchangeWidgetState; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureExternalImageWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureExternalImageWidget.ts new file mode 100644 index 0000000..5e5aa99 --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureExternalImageWidget.ts @@ -0,0 +1,74 @@ +import { GetRoomEngine, RoomEngineTriggerWidgetEvent, RoomObjectCategory, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { IPhotoData } from '../../../../api'; +import { useNitroEvent } from '../../../events'; +import { useFurniRemovedEvent } from '../../engine'; +import { useRoom } from '../../useRoom'; + +const useFurnitureExternalImageWidgetState = () => +{ + const [ objectId, setObjectId ] = useState(-1); + const [ category, setCategory ] = useState(-1); + const [ currentPhotoIndex, setCurrentPhotoIndex ] = useState(-1); + const [ currentPhotos, setCurrentPhotos ] = useState([]); + const { roomSession = null } = useRoom(); + + const onClose = () => + { + setObjectId(-1); + setCategory(-1); + setCurrentPhotoIndex(-1); + setCurrentPhotos([]); + } + + useNitroEvent(RoomEngineTriggerWidgetEvent.REQUEST_EXTERNAL_IMAGE, event => + { + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + const roomTotalImages = GetRoomEngine().getRoomObjects(roomSession?.roomId, RoomObjectCategory.WALL); + + if(!roomObject) return; + + const datas: IPhotoData[] = []; + + roomTotalImages.forEach(object => + { + if (object.type !== 'external_image_wallitem_poster_small') return null; + + const data = object.model.getValue(RoomObjectVariable.FURNITURE_DATA); + const jsonData: IPhotoData = JSON.parse(data); + + datas.push(jsonData); + }); + + setObjectId(event.objectId); + setCategory(event.category); + setCurrentPhotos(datas); + + const roomObjectPhotoData = (JSON.parse(roomObject.model.getValue(RoomObjectVariable.FURNITURE_DATA)) as IPhotoData); + + setCurrentPhotoIndex(prevValue => + { + let index = 0; + + if(roomObjectPhotoData) + { + index = datas.findIndex(data => (data.u === roomObjectPhotoData.u)) + } + + if(index < 0) index = 0; + + return index; + }); + }); + + useFurniRemovedEvent(((objectId !== -1) && (category !== -1)), event => + { + if((event.id !== objectId) || (event.category !== category)) return; + + onClose(); + }); + + return { objectId, currentPhotoIndex, currentPhotos, onClose }; +} + +export const useFurnitureExternalImageWidget = useFurnitureExternalImageWidgetState; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureFriendFurniWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureFriendFurniWidget.ts new file mode 100644 index 0000000..3517d39 --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureFriendFurniWidget.ts @@ -0,0 +1,75 @@ +import { FriendFurniConfirmLockMessageComposer, GetRoomEngine, LoveLockFurniFinishedEvent, LoveLockFurniFriendConfirmedEvent, LoveLockFurniStartEvent, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { SendMessageComposer } from '../../../../api'; +import { useMessageEvent, useNitroEvent } from '../../../events'; +import { useFurniRemovedEvent } from '../../engine'; + +const useFurnitureFriendFurniWidgetState = () => +{ + const [ objectId, setObjectId ] = useState(-1); + const [ category, setCategory ] = useState(-1); + const [ type, setType ] = useState(0); + const [ usernames, setUsernames ] = useState([]); + const [ figures, setFigures ] = useState([]); + const [ date, setDate ] = useState(null); + const [ stage, setStage ] = useState(0); + + const onClose = () => + { + setObjectId(-1); + setCategory(-1); + setType(0); + setUsernames([]); + setFigures([]); + setDate(null); + } + + const respond = (flag: boolean) => + { + SendMessageComposer(new FriendFurniConfirmLockMessageComposer(objectId, flag)); + + onClose(); + } + + useMessageEvent(LoveLockFurniStartEvent, event => + { + const parser = event.getParser(); + + setObjectId(parser.furniId); + setStage(parser.start ? 1 : 2); + }); + + useMessageEvent(LoveLockFurniFinishedEvent, event => onClose()); + useMessageEvent(LoveLockFurniFriendConfirmedEvent, event => onClose()); + + useNitroEvent(RoomEngineTriggerWidgetEvent.REQUEST_FRIEND_FURNITURE_ENGRAVING, event => + { + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + + if(!roomObject) return; + + const data = roomObject.model.getValue(RoomObjectVariable.FURNITURE_DATA); + const type = roomObject.model.getValue(RoomObjectVariable.FURNITURE_FRIENDFURNI_ENGRAVING); + + if((data[0] !== '1') || (data.length !== 6)) return; + + setObjectId(event.objectId); + setCategory(event.category); + setType(type); + setUsernames([ data[1], data[2] ]); + setFigures([ data[3], data[4] ]); + setDate(data[5]); + setStage(0); + }); + + useFurniRemovedEvent(((objectId !== -1) && (category !== -1)), event => + { + if((event.id !== objectId) || (event.category !== category)) return; + + onClose(); + }); + + return { objectId, type, usernames, figures, date, stage, onClose, respond }; +} + +export const useFurnitureFriendFurniWidget = useFurnitureFriendFurniWidgetState; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureHighScoreWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureHighScoreWidget.ts new file mode 100644 index 0000000..08e5038 --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureHighScoreWidget.ts @@ -0,0 +1,55 @@ +import { GetRoomEngine, HighScoreDataType, ObjectDataFactory, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { useNitroEvent } from '../../../events'; +import { useRoom } from '../../useRoom'; + +const SCORE_TYPES = [ 'perteam', 'mostwins', 'classic' ]; +const CLEAR_TYPES = [ 'alltime', 'daily', 'weekly', 'monthly' ]; + +const useFurnitureHighScoreWidgetState = () => +{ + const [ stuffDatas, setStuffDatas ] = useState>(new Map()); + const { roomSession = null } = useRoom(); + + const getScoreType = (type: number) => SCORE_TYPES[type]; + const getClearType = (type: number) => CLEAR_TYPES[type]; + + useNitroEvent(RoomEngineTriggerWidgetEvent.REQUEST_HIGH_SCORE_DISPLAY, event => + { + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + + if(!roomObject) return; + + const formatKey = roomObject.model.getValue(RoomObjectVariable.FURNITURE_DATA_FORMAT); + const stuffData = (ObjectDataFactory.getData(formatKey) as HighScoreDataType); + + stuffData.initializeFromRoomObjectModel(roomObject.model); + + setStuffDatas(prevValue => + { + const newValue = new Map(prevValue); + + newValue.set(roomObject.id, stuffData); + + return newValue; + }); + }); + + useNitroEvent(RoomEngineTriggerWidgetEvent.REQUEST_HIDE_HIGH_SCORE_DISPLAY, event => + { + if(event.roomId !== roomSession.roomId) return; + + setStuffDatas(prevValue => + { + const newValue = new Map(prevValue); + + newValue.delete(event.objectId); + + return newValue; + }); + }); + + return { stuffDatas, getScoreType, getClearType }; +} + +export const useFurnitureHighScoreWidget = useFurnitureHighScoreWidgetState; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureInternalLinkWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureInternalLinkWidget.ts new file mode 100644 index 0000000..27e6b2e --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureInternalLinkWidget.ts @@ -0,0 +1,26 @@ +import { CreateLinkEvent, GetRoomEngine, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { useNitroEvent } from '../../../events'; + +const INTERNALLINK = 'internalLink'; + +const useFurnitureInternalLinkWidgetState = () => +{ + useNitroEvent(RoomEngineTriggerWidgetEvent.REQUEST_INTERNAL_LINK, event => + { + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + + if(!roomObject) return; + + const data = roomObject.model.getValue(RoomObjectVariable.FURNITURE_DATA); + + let link = data[INTERNALLINK]; + + if(!link || !link.length) link = roomObject.model.getValue(RoomObjectVariable.FURNITURE_INTERNAL_LINK); + + if(link && link.length) CreateLinkEvent(link); + }); + + return {}; +} + +export const useFurnitureInternalLinkWidget = useFurnitureInternalLinkWidgetState; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureMannequinWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureMannequinWidget.ts new file mode 100644 index 0000000..3e88457 --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureMannequinWidget.ts @@ -0,0 +1,80 @@ +import { FurnitureMannequinSaveLookComposer, FurnitureMannequinSaveNameComposer, FurnitureMultiStateComposer, GetAvatarRenderManager, GetRoomEngine, HabboClubLevelEnum, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { MannequinUtilities, SendMessageComposer } from '../../../../api'; +import { useNitroEvent } from '../../../events'; +import { useFurniRemovedEvent } from '../../engine'; + +const useFurnitureMannequinWidgetState = () => +{ + const [ objectId, setObjectId ] = useState(-1); + const [ category, setCategory ] = useState(-1); + const [ figure, setFigure ] = useState(null); + const [ gender, setGender ] = useState(null); + const [ clubLevel, setClubLevel ] = useState(HabboClubLevelEnum.NO_CLUB); + const [ name, setName ] = useState(null); + + const onClose = () => + { + setObjectId(-1); + setCategory(-1); + setFigure(null); + setGender(null); + setName(null); + } + + const saveFigure = () => + { + if(objectId === -1) return; + + SendMessageComposer(new FurnitureMannequinSaveLookComposer(objectId)); + + onClose(); + } + + const wearFigure = () => + { + if(objectId === -1) return; + + SendMessageComposer(new FurnitureMultiStateComposer(objectId)); + + onClose(); + } + + const saveName = () => + { + if(objectId === -1) return; + + SendMessageComposer(new FurnitureMannequinSaveNameComposer(objectId, name)); + } + + useNitroEvent(RoomEngineTriggerWidgetEvent.REQUEST_MANNEQUIN, event => + { + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + + if(!roomObject) return; + + const model = roomObject.model; + const figure = (model.getValue(RoomObjectVariable.FURNITURE_MANNEQUIN_FIGURE) || null); + const gender = (model.getValue(RoomObjectVariable.FURNITURE_MANNEQUIN_GENDER) || null); + const figureContainer = GetAvatarRenderManager().createFigureContainer(figure); + const figureClubLevel = GetAvatarRenderManager().getFigureClubLevel(figureContainer, gender, MannequinUtilities.MANNEQUIN_CLOTHING_PART_TYPES); + + setObjectId(event.objectId); + setCategory(event.category); + setFigure(figure); + setGender(gender); + setClubLevel(figureClubLevel); + setName(model.getValue(RoomObjectVariable.FURNITURE_MANNEQUIN_NAME) || null); + }); + + useFurniRemovedEvent(((objectId !== -1) && (category !== -1)), event => + { + if((event.id !== objectId) || (event.category !== category)) return; + + onClose(); + }); + + return { objectId, figure, gender, clubLevel, name, setName, saveFigure, wearFigure, saveName, onClose }; +} + +export const useFurnitureMannequinWidget = useFurnitureMannequinWidgetState; diff --git a/src/hooks/rooms/widgets/furniture/useFurniturePlaylistEditorWidget.ts b/src/hooks/rooms/widgets/furniture/useFurniturePlaylistEditorWidget.ts new file mode 100644 index 0000000..ec89553 --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurniturePlaylistEditorWidget.ts @@ -0,0 +1,108 @@ +import { AddJukeboxDiskComposer, AdvancedMap, FurnitureListAddOrUpdateEvent, FurnitureListEvent, FurnitureListRemovedEvent, FurnitureMultiStateComposer, GetRoomEngine, GetSessionDataManager, GetSoundManager, IAdvancedMap, IMessageEvent, ISongInfo, NotifyPlayedSongEvent, NowPlayingEvent, PlayListStatusEvent, RemoveJukeboxDiskComposer, RoomControllerLevel, RoomEngineTriggerWidgetEvent, SongDiskInventoryReceivedEvent } from '@nitrots/nitro-renderer'; +import { useCallback, useState } from 'react'; +import { IsOwnerOfFurniture, LocalizeText, NotificationAlertType, NotificationBubbleType, SendMessageComposer } from '../../../../api'; +import { useMessageEvent, useNitroEvent } 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)), []); + + useNitroEvent(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); + + GetSoundManager().musicController?.requestUserSongDisks(); + GetSoundManager().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(); + }); + + useNitroEvent(NowPlayingEvent.NPE_SONG_CHANGED, event => + { + setCurrentPlayingIndex(event.position); + }); + + useNitroEvent(NotifyPlayedSongEvent.NOTIFY_PLAYED_SONG, event => + { + showSingleBubble(LocalizeText('soundmachine.notification.playing', [ 'songname', 'songauthor' ], [ event.name, event.creator ]), NotificationBubbleType.SOUNDMACHINE) + }); + + useNitroEvent(SongDiskInventoryReceivedEvent.SDIR_SONG_DISK_INVENTORY_RECEIVENT_EVENT, event => + { + setDiskInventory(GetSoundManager().musicController?.songDiskInventory.clone()); + }); + + useNitroEvent(PlayListStatusEvent.PLUE_PLAY_LIST_UPDATED, event => + { + setPlaylist(GetSoundManager().musicController?.getRoomItemPlaylist()?.entries.concat()) + }); + + useNitroEvent(PlayListStatusEvent.PLUE_PLAY_LIST_FULL, event => + { + simpleAlert(LocalizeText('playlist.editor.alert.playlist.full'), NotificationAlertType.ALERT, '', '', LocalizeText('playlist.editor.alert.playlist.full.title')); + }); + + const onFurniListUpdated = (event : IMessageEvent) => + { + if(objectId === -1) return; + + if(event instanceof FurnitureListEvent) + { + if(event.getParser().fragmentNumber === 0) + { + GetSoundManager().musicController?.requestUserSongDisks(); + } + } + else + { + GetSoundManager().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/furniture/useFurniturePresentWidget.ts b/src/hooks/rooms/widgets/furniture/useFurniturePresentWidget.ts new file mode 100644 index 0000000..0058885 --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurniturePresentWidget.ts @@ -0,0 +1,235 @@ +import { GetRoomEngine, GetSessionDataManager, IFurnitureData, IGetImageListener, PetFigureData, RoomEngineTriggerWidgetEvent, RoomObjectCategory, RoomObjectVariable, RoomSessionPresentEvent, TextureUtils, Vector3d } from '@nitrots/nitro-renderer'; +import { useMemo, useState } from 'react'; +import { IsOwnerOfFurniture, LocalizeText, ProductTypeEnum } from '../../../../api'; +import { useNitroEvent } from '../../../events'; +import { useFurniRemovedEvent } from '../../engine'; +import { useRoom } from '../../useRoom'; + +const FLOOR: string = 'floor'; +const WALLPAPER: string = 'wallpaper'; +const LANDSCAPE: string = 'landscape'; +const POSTER: string = 'poster'; + +const useFurniturePresentWidgetState = () => +{ + const [ objectId, setObjectId ] = useState(-1); + const [ classId, setClassId ] = useState(-1); + const [ itemType, setItemType ] = useState(null); + const [ text, setText ] = useState(null); + const [ isOwnerOfFurniture, setIsOwnerOfFurniture ] = useState(false); + const [ senderName, setSenderName ] = useState(null); + const [ senderFigure, setSenderFigure ] = useState(null); + const [ placedItemId, setPlacedItemId ] = useState(-1); + const [ placedItemType, setPlacedItemType ] = useState(null); + const [ placedInRoom, setPlacedInRoom ] = useState(false); + const [ imageUrl, setImageUrl ] = useState(null); + const { roomSession = null } = useRoom(); + + const onClose = () => + { + setObjectId(-1); + setClassId(-1); + setItemType(null); + setText(null); + setIsOwnerOfFurniture(false); + setSenderName(null); + setSenderFigure(null); + setPlacedItemId(-1); + setPlacedItemType(null); + setPlacedInRoom(false); + setImageUrl(null); + } + + const openPresent = () => + { + if(objectId === -1) return; + + roomSession.openGift(objectId); + + GetRoomEngine().changeObjectModelData(GetRoomEngine().activeRoomId, objectId, RoomObjectCategory.FLOOR, RoomObjectVariable.FURNITURE_DISABLE_PICKING_ANIMATION, 1); + } + + const imageListener: IGetImageListener = useMemo(() => + { + // async fix image + return { + imageReady: (id, texture, image) => + { + (async () => + { + if(!image && texture) + { + image = await TextureUtils.generateImage(texture); + } + + setImageUrl(image.src); + })(); + }, + imageFailed: null + } + }, []); + + useNitroEvent(RoomSessionPresentEvent.RSPE_PRESENT_OPENED, event => + { + let furniData: IFurnitureData = null; + + if(event.itemType === ProductTypeEnum.FLOOR) + { + furniData = GetSessionDataManager().getFloorItemData(event.classId); + } + else if(event.itemType === ProductTypeEnum.WALL) + { + furniData = GetSessionDataManager().getWallItemData(event.classId); + } + + let isOwnerOfFurni = false; + + if(event.placedInRoom) + { + const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, event.placedItemId, RoomObjectCategory.FLOOR); + + if(roomObject) isOwnerOfFurni = IsOwnerOfFurniture(roomObject); + } + + let giftImage: string = null; + + switch(event.itemType) + { + case ProductTypeEnum.WALL: { + if(furniData) + { + switch(furniData.className) + { + case FLOOR: + case LANDSCAPE: + case WALLPAPER: + let imageType = null; + let message = null; + + if(furniData.className === FLOOR) + { + imageType = 'packagecard_icon_floor'; + message = LocalizeText('inventory.furni.item.floor.name'); + } + + else if(furniData.className === LANDSCAPE) + { + imageType = 'packagecard_icon_landscape'; + message = LocalizeText('inventory.furni.item.landscape.name'); + } + + else + { + imageType = 'packagecard_icon_wallpaper'; + message = LocalizeText('inventory.furni.item.wallpaper.name'); + } + + setText(message); + //setImageUrl(getGiftImageUrl(imageType)); + break; + case POSTER: { + const productCode = event.productCode; + + let extras: string = null; + + if(productCode.indexOf('poster') === 0) extras = productCode.replace('poster', ''); + + const productData = GetSessionDataManager().getProductData(productCode); + + let name: string = null; + + if(productData) name = productData.name; + else if(furniData) name = furniData.name; + + setText(name); + setImageUrl(GetRoomEngine().getFurnitureWallIconUrl(event.classId, extras)); + + break; + } + default: { + setText(furniData.name || null); + setImageUrl(GetRoomEngine().getFurnitureWallIconUrl(event.classId)); + break; + } + } + } + + break; + } + case ProductTypeEnum.HABBO_CLUB: + setText(LocalizeText('widget.furni.present.hc')); + //setImageUrl(getGiftImageUrl('packagecard_icon_hc')); + break; + default: { + if(event.placedItemType === ProductTypeEnum.PET) + { + const petfigureString = event.petFigureString; + + if(petfigureString && petfigureString.length) + { + const petFigureData = new PetFigureData(petfigureString); + + (async () => + { + const petImage = GetRoomEngine().getRoomObjectPetImage(petFigureData.typeId, petFigureData.paletteId, petFigureData.color, new Vector3d(90), 64, imageListener, true, 0, petFigureData.customParts); + + if(petImage) setImageUrl((await petImage.getImage()).src); + })(); + } + } + else + { + (async () => + { + const furniImage = GetRoomEngine().getFurnitureFloorImage(event.classId, new Vector3d(90), 64, imageListener); + + if(furniImage) setImageUrl((await furniImage.getImage()).src); + })(); + } + + const productData = GetSessionDataManager().getProductData(event.productCode); + + setText((productData && productData.name) || furniData.name); + break; + } + } + + setObjectId(0); + setClassId(event.classId); + setItemType(event.itemType); + setIsOwnerOfFurniture(isOwnerOfFurni); + setPlacedItemId(event.placedItemId); + setPlacedItemType(event.placedItemType); + setPlacedInRoom(event.placedInRoom); + }); + + useNitroEvent(RoomEngineTriggerWidgetEvent.REQUEST_PRESENT, event => + { + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + + if(!roomObject) return null; + + onClose(); + + setObjectId(event.objectId); + setClassId(-1); + setText((roomObject.model.getValue(RoomObjectVariable.FURNITURE_DATA) || '')); + setIsOwnerOfFurniture(IsOwnerOfFurniture(roomObject)); + setSenderName((roomObject.model.getValue(RoomObjectVariable.FURNITURE_PURCHASER_NAME) || null)); + setSenderFigure((roomObject.model.getValue(RoomObjectVariable.FURNITURE_PURCHASER_FIGURE) || null)); + }); + + useFurniRemovedEvent((objectId !== -1), event => + { + if(event.id === objectId) onClose(); + + if(event.id === placedItemId) + { + if(placedInRoom) setPlacedInRoom(false); + } + }); + + return { objectId, classId, itemType, text, isOwnerOfFurniture, senderName, senderFigure, placedItemId, placedItemType, placedInRoom, imageUrl, openPresent, onClose }; +} + +export const useFurniturePresentWidget = useFurniturePresentWidgetState; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureRoomLinkWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureRoomLinkWidget.ts new file mode 100644 index 0000000..31180a0 --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureRoomLinkWidget.ts @@ -0,0 +1,49 @@ +import { GetGuestRoomMessageComposer, GetGuestRoomResultEvent, GetRoomEngine, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { SendMessageComposer } from '../../../../api'; +import { useMessageEvent, useNitroEvent } from '../../../events'; + +const INTERNALLINK = 'internalLink'; + +const useFurnitureRoomLinkWidgetState = () => +{ + const [ roomIdToEnter, setRoomIdToEnter ] = useState(0); + + useNitroEvent(RoomEngineTriggerWidgetEvent.REQUEST_ROOM_LINK, event => + { + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + + if(!roomObject) return; + + const data = roomObject.model.getValue(RoomObjectVariable.FURNITURE_DATA); + + let roomId = data[INTERNALLINK]; + + if(!roomId || !roomId.length) roomId = roomObject.model.getValue(RoomObjectVariable.FURNITURE_INTERNAL_LINK); + + if(!roomId || !roomId.length) return; + + roomId = parseInt(roomId, 10); + + if(isNaN(roomId)) return; + + setRoomIdToEnter(roomId); + + SendMessageComposer(new GetGuestRoomMessageComposer(roomId, false, false)); + }); + + useMessageEvent(GetGuestRoomResultEvent, event => + { + if(!roomIdToEnter) return; + + const parser = event.getParser(); + + if(parser.data.roomId !== roomIdToEnter) return; + + setRoomIdToEnter(0); + }); + + return {}; +} + +export const useFurnitureRoomLinkWidget = useFurnitureRoomLinkWidgetState; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureSpamWallPostItWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureSpamWallPostItWidget.ts new file mode 100644 index 0000000..3226382 --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureSpamWallPostItWidget.ts @@ -0,0 +1,59 @@ +import { AddSpamWallPostItMessageComposer, GetRoomEngine, RequestSpamWallPostItMessageEvent, RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { SendMessageComposer } from '../../../../api'; +import { useMessageEvent } from '../../../events'; +import { useInventoryFurni } from '../../../inventory'; + +const useFurnitureSpamWallPostItWidgetState = () => +{ + const [ objectId, setObjectId ] = useState(-1); + const [ category, setCategory ] = useState(-1); + const [ itemType, setItemType ] = useState(''); + const [ location, setLocation ] = useState(''); + const [ color, setColor ] = useState('0'); + const [ text, setText ] = useState(''); + const [ canModify, setCanModify ] = useState(false); + const { getWallItemById = null } = useInventoryFurni(); + + const onClose = () => + { + SendMessageComposer(new AddSpamWallPostItMessageComposer(objectId, location, color, text)); + + setObjectId(-1); + setCategory(-1); + setItemType(''); + setLocation(''); + setColor('0'); + setText(''); + setCanModify(false); + } + + useMessageEvent(RequestSpamWallPostItMessageEvent, event => + { + const parser = event.getParser(); + + setObjectId(parser.itemId); + setCategory(RoomObjectCategory.WALL); + + const inventoryItem = getWallItemById(parser.itemId); + + let itemType = 'post_it'; + + if(inventoryItem) + { + const wallItemType = GetRoomEngine().getFurnitureWallName(inventoryItem.type); + + if(wallItemType.match('post_it_')) itemType = wallItemType; + } + + setItemType(itemType); + setLocation(parser.location); + setColor('FFFF33'); + setText(''); + setCanModify(true); + }); + + return { objectId, color, setColor, text, setText, canModify, onClose }; +} + +export const useFurnitureSpamWallPostItWidget = useFurnitureSpamWallPostItWidgetState; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureStackHeightWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureStackHeightWidget.ts new file mode 100644 index 0000000..846f14e --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureStackHeightWidget.ts @@ -0,0 +1,79 @@ +import { FurnitureStackHeightComposer, FurnitureStackHeightEvent, GetRoomEngine, RoomEngineTriggerWidgetEvent } from '@nitrots/nitro-renderer'; +import { useEffect, useState } from 'react'; +import { CanManipulateFurniture, GetRoomSession, SendMessageComposer } from '../../../../api'; +import { useMessageEvent, useNitroEvent } from '../../../events'; +import { useFurniRemovedEvent } from '../../engine'; + +const MAX_HEIGHT: number = 40; + +const useFurnitureStackHeightWidgetState = () => +{ + const [ objectId, setObjectId ] = useState(-1); + const [ category, setCategory ] = useState(-1); + const [ height, setHeight ] = useState(0); + const [ pendingHeight, setPendingHeight ] = useState(-1); + + const onClose = () => + { + setObjectId(-1); + setCategory(-1); + setHeight(0); + setPendingHeight(-1); + } + + const updateHeight = (height: number, server: boolean = false) => + { + if(!height) height = 0; + + height = Math.abs(height); + + if(!server) ((height > MAX_HEIGHT) && (height = MAX_HEIGHT)); + + setHeight(parseFloat(height.toFixed(2))); + + if(!server) setPendingHeight(height * 100); + } + + useMessageEvent(FurnitureStackHeightEvent, event => + { + const parser = event.getParser(); + + if(objectId !== parser.furniId) return; + + updateHeight(parser.height, true); + }); + + useNitroEvent(RoomEngineTriggerWidgetEvent.REQUEST_STACK_HEIGHT, event => + { + if(!CanManipulateFurniture(GetRoomSession(), event.objectId, event.category)) return; + + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + + if(!roomObject) return; + + setObjectId(event.objectId); + setCategory(event.category); + setHeight(roomObject.getLocation().z); + setPendingHeight(-1); + }); + + useFurniRemovedEvent(((objectId !== -1) && (category !== -1)), event => + { + if((event.id !== objectId) || (event.category !== category)) return; + + onClose(); + }); + + useEffect(() => + { + if((objectId === -1) || (pendingHeight === -1)) return; + + const timeout = setTimeout(() => SendMessageComposer(new FurnitureStackHeightComposer(objectId, ~~(pendingHeight))), 10); + + return () => clearTimeout(timeout); + }, [ objectId, pendingHeight ]); + + return { objectId, height, maxHeight: MAX_HEIGHT, onClose, updateHeight }; +} + +export const useFurnitureStackHeightWidget = useFurnitureStackHeightWidgetState; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureStickieWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureStickieWidget.ts new file mode 100644 index 0000000..3d745a3 --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureStickieWidget.ts @@ -0,0 +1,85 @@ +import { GetRoomEngine, GetSessionDataManager, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { GetRoomSession, IsOwnerOfFurniture } from '../../../../api'; +import { useNitroEvent } from '../../../events'; +import { useFurniRemovedEvent } from '../../engine'; + +const useFurnitureStickieWidgetState = () => +{ + const [ objectId, setObjectId ] = useState(-1); + const [ category, setCategory ] = useState(-1); + const [ color, setColor ] = useState('0'); + const [ text, setText ] = useState(''); + const [ type, setType ] = useState(''); + const [ canModify, setCanModify ] = useState(false); + + const onClose = () => + { + setObjectId(-1); + setCategory(-1); + setColor('0'); + setText(''); + setType(''); + setCanModify(false); + } + + const updateColor = (newColor: string) => + { + if(newColor === color) return; + + setColor(newColor); + + GetRoomEngine().modifyRoomObjectData(objectId, category, newColor, text); + } + + const updateText = (newText: string) => + { + setText(newText); + + GetRoomEngine().modifyRoomObjectData(objectId, category, color, newText); + } + + const trash = () => GetRoomEngine().deleteRoomObject(objectId, category); + + useNitroEvent(RoomEngineTriggerWidgetEvent.REQUEST_STICKIE, event => + { + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + + if(!roomObject) return; + + const data = roomObject.model.getValue(RoomObjectVariable.FURNITURE_ITEMDATA); + + if(data.length < 6) return; + + let color: string = null; + let text: string = null; + + if(data.indexOf(' ') > 0) + { + color = data.slice(0, data.indexOf(' ')); + text = data.slice((data.indexOf(' ') + 1), data.length); + } + else + { + color = data; + } + + setObjectId(event.objectId); + setCategory(event.category); + setColor(color || '0'); + setText(text || ''); + setType(roomObject.type || 'post_it'); + setCanModify(GetRoomSession().isRoomOwner || GetSessionDataManager().isModerator || IsOwnerOfFurniture(roomObject)); + }); + + useFurniRemovedEvent(((objectId !== -1) && (category !== -1)), event => + { + if((event.id !== objectId) || (event.category !== category)) return; + + onClose(); + }); + + return { objectId, color, text, type, canModify, updateColor, updateText, trash, onClose }; +} + +export const useFurnitureStickieWidget = useFurnitureStickieWidgetState; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureTrophyWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureTrophyWidget.ts new file mode 100644 index 0000000..5808b5f --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureTrophyWidget.ts @@ -0,0 +1,62 @@ +import { GetRoomEngine, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { useNitroEvent } from '../../../events'; +import { useFurniRemovedEvent } from '../../engine'; + +const useFurnitureTrophyWidgetState = () => +{ + const [ objectId, setObjectId ] = useState(-1); + const [ category, setCategory ] = useState(-1); + const [ color, setColor ] = useState('1'); + const [ senderName, setSenderName ] = useState(''); + const [ date, setDate ] = useState(''); + const [ message, setMessage ] = useState(''); + + const onClose = () => + { + setObjectId(-1); + setCategory(-1); + setColor('1'); + setSenderName(''); + setDate(''); + setMessage(''); + } + + useNitroEvent(RoomEngineTriggerWidgetEvent.REQUEST_TROPHY, event => + { + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + + if(!roomObject) return; + + let data = roomObject.model.getValue(RoomObjectVariable.FURNITURE_DATA); + let extra = roomObject.model.getValue(RoomObjectVariable.FURNITURE_EXTRAS); + + if(!extra) extra = '0'; + + setObjectId(event.objectId); + setCategory(event.category); + setColor(roomObject.model.getValue(RoomObjectVariable.FURNITURE_COLOR) || '1'); + + const senderName = data.substring(0, data.indexOf('\t')); + + data = data.substring((senderName.length + 1), data.length); + + const trophyDate = data.substring(0, data.indexOf('\t')); + const trophyText = data.substr((trophyDate.length + 1), data.length); + + setSenderName(senderName); + setDate(trophyDate); + setMessage(trophyText); + }); + + useFurniRemovedEvent(((objectId !== -1) && (category !== -1)), event => + { + if((event.id !== objectId) || (event.category !== category)) return; + + onClose(); + }); + + return { objectId, color, senderName, date, message, onClose }; +} + +export const useFurnitureTrophyWidget = useFurnitureTrophyWidgetState; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureYoutubeWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureYoutubeWidget.ts new file mode 100644 index 0000000..64c0f0d --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureYoutubeWidget.ts @@ -0,0 +1,127 @@ +import { ControlYoutubeDisplayPlaybackMessageComposer, GetRoomEngine, GetSessionDataManager, GetYoutubeDisplayStatusMessageComposer, RoomEngineTriggerWidgetEvent, RoomId, SecurityLevel, SetYoutubeDisplayPlaylistMessageComposer, YoutubeControlVideoMessageEvent, YoutubeDisplayPlaylist, YoutubeDisplayPlaylistsEvent, YoutubeDisplayVideoMessageEvent } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { IsOwnerOfFurniture, SendMessageComposer, YoutubeVideoPlaybackStateEnum } from '../../../../api'; +import { useMessageEvent, useNitroEvent } from '../../../events'; +import { useFurniRemovedEvent } from '../../engine'; + +const CONTROL_COMMAND_PREVIOUS_VIDEO = 0; +const CONTROL_COMMAND_NEXT_VIDEO = 1; +const CONTROL_COMMAND_PAUSE_VIDEO = 2; +const CONTROL_COMMAND_CONTINUE_VIDEO = 3; + +const useFurnitureYoutubeWidgetState = () => +{ + const [ objectId, setObjectId ] = useState(-1); + const [ category, setCategory ] = useState(-1); + const [ videoId, setVideoId ] = useState(null); + const [ videoStart, setVideoStart ] = useState(null); + const [ videoEnd, setVideoEnd ] = useState(null); + const [ currentVideoState, setCurrentVideoState ] = useState(-1); + const [ selectedVideo, setSelectedVideo ] = useState(null); + const [ playlists, setPlaylists ] = useState(null); + const [ hasControl, setHasControl ] = useState(false); + + const onClose = () => + { + setObjectId(-1); + setCategory(-1); + setVideoId(null); + setVideoStart(null); + setVideoEnd(null); + setCurrentVideoState(-1); + setSelectedVideo(null); + setPlaylists(null); + setHasControl(false); + } + + const previous = () => SendMessageComposer(new ControlYoutubeDisplayPlaybackMessageComposer(objectId, CONTROL_COMMAND_PREVIOUS_VIDEO)); + + const next = () => SendMessageComposer(new ControlYoutubeDisplayPlaybackMessageComposer(objectId, CONTROL_COMMAND_NEXT_VIDEO)); + + const pause = () => (hasControl && videoId && videoId.length) && SendMessageComposer(new ControlYoutubeDisplayPlaybackMessageComposer(objectId, CONTROL_COMMAND_PAUSE_VIDEO)); + + const play = () => (hasControl && videoId && videoId.length) && SendMessageComposer(new ControlYoutubeDisplayPlaybackMessageComposer(objectId, CONTROL_COMMAND_CONTINUE_VIDEO)); + + const selectVideo = (video: string) => + { + if(selectedVideo === video) + { + setSelectedVideo(null); + SendMessageComposer(new SetYoutubeDisplayPlaylistMessageComposer(objectId, '')); + + return; + } + + setSelectedVideo(video); + SendMessageComposer(new SetYoutubeDisplayPlaylistMessageComposer(objectId, video)); + } + + useNitroEvent(RoomEngineTriggerWidgetEvent.REQUEST_YOUTUBE, event => + { + if(RoomId.isRoomPreviewerId(event.roomId)) return; + + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + + if(!roomObject) return; + + setObjectId(event.objectId); + setCategory(event.category); + setHasControl(GetSessionDataManager().hasSecurity(SecurityLevel.EMPLOYEE) || IsOwnerOfFurniture(roomObject)); + + SendMessageComposer(new GetYoutubeDisplayStatusMessageComposer(event.objectId)); + }); + + useMessageEvent(YoutubeDisplayVideoMessageEvent, event => + { + const parser = event.getParser(); + + if((objectId === -1) || (objectId !== parser.furniId)) return; + + setVideoId(parser.videoId); + setVideoStart(parser.startAtSeconds); + setVideoEnd(parser.endAtSeconds); + setCurrentVideoState(parser.state); + }); + + useMessageEvent(YoutubeDisplayPlaylistsEvent, event => + { + const parser = event.getParser(); + + if((objectId === -1) || (objectId !== parser.furniId)) return; + + setPlaylists(parser.playlists); + setSelectedVideo(parser.selectedPlaylistId); + setVideoId(null); + setCurrentVideoState(-1); + setVideoEnd(null); + setVideoStart(null); + }); + + useMessageEvent(YoutubeControlVideoMessageEvent, event => + { + const parser = event.getParser(); + + if((objectId === -1) || (objectId !== parser.furniId)) return; + + switch(parser.commandId) + { + case 1: + setCurrentVideoState(YoutubeVideoPlaybackStateEnum.PLAYING); + break; + case 2: + setCurrentVideoState(YoutubeVideoPlaybackStateEnum.PAUSED); + break; + } + }); + + useFurniRemovedEvent(((objectId !== -1) && (category !== -1)), event => + { + if((event.id !== objectId) || (event.category !== category)) return; + + onClose(); + }); + + return { objectId, videoId, videoStart, videoEnd, currentVideoState, selectedVideo, playlists, onClose, previous, next, pause, play, selectVideo }; +} + +export const useFurnitureYoutubeWidget = useFurnitureYoutubeWidgetState; diff --git a/src/hooks/rooms/widgets/index.ts b/src/hooks/rooms/widgets/index.ts new file mode 100644 index 0000000..9984450 --- /dev/null +++ b/src/hooks/rooms/widgets/index.ts @@ -0,0 +1,12 @@ +export * from './furniture'; +export * from './useAvatarInfoWidget'; +export * from './useChatInputWidget'; +export * from './useChatWidget'; +export * from './useDoorbellWidget'; +export * from './useFilterWordsWidget'; +export * from './useFriendRequestWidget'; +export * from './useFurniChooserWidget'; +export * from './usePetPackageWidget'; +export * from './usePollWidget'; +export * from './useUserChooserWidget'; +export * from './useWordQuizWidget'; diff --git a/src/hooks/rooms/widgets/useAvatarInfoWidget.ts b/src/hooks/rooms/widgets/useAvatarInfoWidget.ts new file mode 100644 index 0000000..9f5e213 --- /dev/null +++ b/src/hooks/rooms/widgets/useAvatarInfoWidget.ts @@ -0,0 +1,355 @@ +import { GetRoomEngine, GetSessionDataManager, RoomEngineObjectEvent, RoomEngineUseProductEvent, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomSessionPetInfoUpdateEvent, RoomSessionPetStatusUpdateEvent, RoomSessionUserDataUpdateEvent } from '@nitrots/nitro-renderer'; +import { useEffect, useState } from 'react'; +import { AvatarInfoFurni, AvatarInfoName, AvatarInfoPet, AvatarInfoRentableBot, AvatarInfoUser, AvatarInfoUtilities, CanManipulateFurniture, FurniCategory, IAvatarInfo, IsOwnerOfFurniture, RoomWidgetUpdateRoomObjectEvent, UseProductItem } from '../../../api'; +import { useNitroEvent, useUiEvent } from '../../events'; +import { useFriends } from '../../friends'; +import { useWired } from '../../wired'; +import { useObjectDeselectedEvent, useObjectRollOutEvent, useObjectRollOverEvent, useObjectSelectedEvent } from '../engine'; +import { useRoom } from '../useRoom'; + +const useAvatarInfoWidgetState = () => +{ + const [ avatarInfo, setAvatarInfo ] = useState(null); + const [ activeNameBubble, setActiveNameBubble ] = useState(null); + const [ nameBubbles, setNameBubbles ] = useState([]); + const [ productBubbles, setProductBubbles ] = useState([]); + const [ confirmingProduct, setConfirmingProduct ] = useState(null); + const [ pendingPetId, setPendingPetId ] = useState(-1); + const [ isDecorating, setIsDecorating ] = useState(false); + const { friends = [] } = useFriends(); + const { selectObjectForWired = null } = useWired(); + const { roomSession = null } = useRoom(); + + const removeNameBubble = (index: number) => + { + setNameBubbles(prevValue => + { + const newValue = [ ...prevValue ]; + + newValue.splice(index, 1); + + return newValue; + }); + } + + const removeProductBubble = (index: number) => + { + setProductBubbles(prevValue => + { + const newValue = [ ...prevValue ]; + const item = newValue.splice(index, 1)[0]; + + if(confirmingProduct === item) setConfirmingProduct(null); + + return newValue; + }); + } + + const updateConfirmingProduct = (product: UseProductItem) => + { + setConfirmingProduct(product); + setProductBubbles([]); + } + + const getObjectName = (objectId: number, category: number) => + { + const name = AvatarInfoUtilities.getObjectName(objectId, category); + + if(!name) return; + + setActiveNameBubble(name); + + if(category !== RoomObjectCategory.UNIT) setProductBubbles([]); + } + + const getObjectInfo = (objectId: number, category: number) => + { + let info: IAvatarInfo = null; + + switch(category) + { + case RoomObjectCategory.FLOOR: + case RoomObjectCategory.WALL: + info = AvatarInfoUtilities.getFurniInfo(objectId, category); + + if(info) selectObjectForWired(objectId, category); + break; + case RoomObjectCategory.UNIT: { + const userData = roomSession.userDataManager.getUserDataByIndex(objectId); + + if(!userData) break; + + switch(userData.type) + { + case RoomObjectType.PET: + roomSession.userDataManager.requestPetInfo(userData.webID); + setPendingPetId(userData.webID); + break; + case RoomObjectType.USER: + info = AvatarInfoUtilities.getUserInfo(category, userData); + break; + case RoomObjectType.BOT: + info = AvatarInfoUtilities.getBotInfo(category, userData); + break; + case RoomObjectType.RENTABLE_BOT: + info = AvatarInfoUtilities.getRentableBotInfo(category, userData); + break; + } + } + + } + + if(!info) return; + + setAvatarInfo(info); + } + + const processUsableRoomObject = (objectId: number) => + { + } + + const refreshPetInfo = () => + { + // roomSession.userDataManager.requestPetInfo(petData.id); + } + + useNitroEvent(RoomSessionUserDataUpdateEvent.USER_DATA_UPDATED, event => + { + if(!event.addedUsers.length) return; + + let addedNameBubbles: AvatarInfoName[] = []; + + event.addedUsers.forEach(user => + { + if(user.webID === GetSessionDataManager().userId || user.type !== RoomObjectType.USER) return; + + if(friends.find(friend => (friend.id === user.webID))) + { + addedNameBubbles.push(new AvatarInfoName(user.roomIndex, RoomObjectCategory.UNIT, user.webID, user.name, user.type, true)); + } + }); + + if(!addedNameBubbles.length) return; + + setNameBubbles(prevValue => + { + const newValue = [ ...prevValue ]; + + addedNameBubbles.forEach(bubble => + { + const oldIndex = newValue.findIndex(oldBubble => (oldBubble.id === bubble.id)); + + if(oldIndex > -1) newValue.splice(oldIndex, 1); + + newValue.push(bubble); + }); + + return newValue; + }); + }); + + useNitroEvent(RoomSessionPetInfoUpdateEvent.PET_INFO, event => + { + const petData = event.petInfo; + + if(!petData) return; + + if(petData.id !== pendingPetId) return; + + const petInfo = AvatarInfoUtilities.getPetInfo(petData); + + if(!petInfo) return; + + setAvatarInfo(petInfo); + setPendingPetId(-1); + }); + + useNitroEvent(RoomSessionPetStatusUpdateEvent.PET_STATUS_UPDATE, event => + { + /* var _local_2:Boolean; + var _local_3:Boolean; + var _local_4:Boolean; + var _local_5:Boolean; + var _local_6:RoomUserData; + var _local_7:_Str_4828; + if (((!(this._container == null)) && (!(this._container.events == null)))) + { + _local_2 = k.canBreed; + _local_3 = k.canHarvest; + _local_4 = k.canRevive; + _local_5 = k.hasBreedingPermission; + _local_6 = this._Str_19958(k.petId); + if (_local_6 == null) + { + Logger.log((("Could not find pet with the id: " + k.petId) + " given by petStatusUpdate")); + return; + } + _local_7 = new _Str_4828(_local_6.roomObjectId, _local_2, _local_3, _local_4, _local_5); + this._container.events.dispatchEvent(_local_7); */ + }); + + useNitroEvent(RoomEngineUseProductEvent.USE_PRODUCT_FROM_INVENTORY, event => + { + // this._Str_23199((k as RoomEngineUseProductEvent).inventoryStripId, (k as RoomEngineUseProductEvent).furnitureTypeId); + }); + + useNitroEvent(RoomEngineUseProductEvent.USE_PRODUCT_FROM_ROOM, event => + { + const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, event.objectId, RoomObjectCategory.FLOOR); + + if(!roomObject || !IsOwnerOfFurniture(roomObject)) return; + + const ownerId = roomObject.model.getValue(RoomObjectVariable.FURNITURE_OWNER_ID); + const typeId = roomObject.model.getValue(RoomObjectVariable.FURNITURE_TYPE_ID); + const furniData = GetSessionDataManager().getFloorItemData(typeId); + const parts = furniData.customParams.split(' '); + const part = (parts.length ? parseInt(parts[0]) : -1); + + if(part === -1) return; + + const useProductBubbles: UseProductItem[] = []; + const roomObjects = GetRoomEngine().getRoomObjects(roomSession.roomId, RoomObjectCategory.UNIT); + + for(const roomObject of roomObjects) + { + const userData = roomSession.userDataManager.getUserDataByIndex(roomObject.id); + + let replace = false; + + if(!userData || (userData.type !== RoomObjectType.PET)) + { + + } + else + { + if(userData.ownerId === ownerId) + { + if(userData.hasSaddle && (furniData.specialType === FurniCategory.PET_SADDLE)) replace = true; + + const figureParts = userData.figure.split(' '); + const figurePart = (figureParts.length ? parseInt(figureParts[0]) : -1); + + if(figurePart === part) + { + if(furniData.specialType === FurniCategory.MONSTERPLANT_REVIVAL) + { + if(!userData.canRevive) continue; + } + + if(furniData.specialType === FurniCategory.MONSTERPLANT_REBREED) + { + if((userData.petLevel < 7) || userData.canRevive || userData.canBreed) continue; + } + + if(furniData.specialType === FurniCategory.MONSTERPLANT_FERTILIZE) + { + if((userData.petLevel >= 7) || userData.canRevive) continue; + } + + useProductBubbles.push(new UseProductItem(userData.roomIndex, RoomObjectCategory.UNIT, userData.name, event.objectId, roomObject.id, -1, replace)); + } + } + } + } + + setConfirmingProduct(null); + + if(useProductBubbles.length) setProductBubbles(useProductBubbles); + }); + + useNitroEvent(RoomEngineObjectEvent.REQUEST_MANIPULATION, event => + { + if(!CanManipulateFurniture(roomSession, event.objectId, event.category)) return; + + setIsDecorating(true); + }); + + useObjectSelectedEvent(event => + { + getObjectInfo(event.id, event.category); + }); + + useObjectDeselectedEvent(event => + { + setAvatarInfo(null); + setProductBubbles([]); + }); + + useObjectRollOverEvent(event => + { + if(avatarInfo || (event.category !== RoomObjectCategory.UNIT)) return; + + getObjectName(event.id, event.category); + }); + + useObjectRollOutEvent(event => + { + if(!activeNameBubble || (event.category !== RoomObjectCategory.UNIT) || (activeNameBubble.roomIndex !== event.id)) return; + + setActiveNameBubble(null); + }); + + useUiEvent([ + RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED, + RoomWidgetUpdateRoomObjectEvent.USER_REMOVED + ], event => + { + if(activeNameBubble && (activeNameBubble.category === event.category) && (activeNameBubble.roomIndex === event.id)) setActiveNameBubble(null); + + if(event.category === RoomObjectCategory.UNIT) + { + let index = nameBubbles.findIndex(bubble => (bubble.roomIndex === event.id)); + + if(index > -1) setNameBubbles(prevValue => prevValue.filter(bubble => (bubble.roomIndex === event.id))); + + index = productBubbles.findIndex(bubble => (bubble.id === event.id)); + + if(index > -1) setProductBubbles(prevValue => prevValue.filter(bubble => (bubble.id !== event.id))); + } + + else if(event.category === RoomObjectCategory.FLOOR) + { + const index = productBubbles.findIndex(bubble => (bubble.id === event.id)); + + if(index > -1) setProductBubbles(prevValue => prevValue.filter(bubble => (bubble.requestRoomObjectId !== event.id))); + } + + if(avatarInfo) + { + if(avatarInfo instanceof AvatarInfoFurni) + { + if(avatarInfo.id === event.id) setAvatarInfo(null); + } + + else if((avatarInfo instanceof AvatarInfoUser) || (avatarInfo instanceof AvatarInfoRentableBot) || (avatarInfo instanceof AvatarInfoPet)) + { + if(avatarInfo.roomIndex === event.id) setAvatarInfo(null); + } + } + }); + + useEffect(() => + { + if(!avatarInfo) return; + + setActiveNameBubble(null); + setNameBubbles([]); + setProductBubbles([]); + }, [ avatarInfo ]); + + useEffect(() => + { + if(!activeNameBubble) return; + + setNameBubbles([]); + }, [ activeNameBubble ]); + + useEffect(() => + { + roomSession.isDecorating = isDecorating; + }, [ roomSession, isDecorating ]); + + return { avatarInfo, setAvatarInfo, activeNameBubble, setActiveNameBubble, nameBubbles, productBubbles, confirmingProduct, isDecorating, setIsDecorating, removeNameBubble, removeProductBubble, updateConfirmingProduct, getObjectName }; +} + +export const useAvatarInfoWidget = useAvatarInfoWidgetState; diff --git a/src/hooks/rooms/widgets/useChatInputWidget.ts b/src/hooks/rooms/widgets/useChatInputWidget.ts new file mode 100644 index 0000000..db47e02 --- /dev/null +++ b/src/hooks/rooms/widgets/useChatInputWidget.ts @@ -0,0 +1,282 @@ +import { AvatarExpressionEnum, CreateLinkEvent, GetEventDispatcher, GetRoomEngine, GetSessionDataManager, GetTicker, HabboClubLevelEnum, RoomControllerLevel, RoomEngineObjectEvent, RoomObjectCategory, RoomRotatingEffect, RoomSessionChatEvent, RoomSettingsComposer, RoomShakingEffect, RoomZoomEvent, TextureUtils } from '@nitrots/nitro-renderer'; +import { useEffect, useState } from 'react'; +import { ChatMessageTypeEnum, GetClubMemberLevel, GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../../api'; +import { useNitroEvent } from '../../events'; +import { useNotification } from '../../notification'; +import { useObjectSelectedEvent } from '../engine'; +import { useRoom } from '../useRoom'; + +const useChatInputWidgetState = () => +{ + const [ selectedUsername, setSelectedUsername ] = useState(''); + const [ isTyping, setIsTyping ] = useState(false); + const [ typingStartedSent, setTypingStartedSent ] = useState(false); + const [ isIdle, setIsIdle ] = useState(false); + const [ floodBlocked, setFloodBlocked ] = useState(false); + const [ floodBlockedSeconds, setFloodBlockedSeconds ] = useState(0); + const { showNitroAlert = null, showConfirm = null } = useNotification(); + const { roomSession = null } = useRoom(); + + const sendChat = (text: string, chatType: number, recipientName: string = '', styleId: number = 0) => + { + if(text === '') return null; + + const parts = text.split(' '); + + if(parts.length > 0) + { + const firstPart = parts[0]; + let secondPart = ''; + + if(parts.length > 1) secondPart = parts[1]; + + if((firstPart.charAt(0) === ':') && (secondPart === 'x')) + { + const selectedAvatarId = GetRoomEngine().selectedAvatarId; + + if(selectedAvatarId > -1) + { + const userData = roomSession.userDataManager.getUserDataByIndex(selectedAvatarId); + + if(userData) + { + secondPart = userData.name; + text = text.replace(' x', (' ' + userData.name)); + } + } + } + + switch(firstPart.toLowerCase()) + { + case ':shake': + RoomShakingEffect.init(2500, 5000); + RoomShakingEffect.turnVisualizationOn(); + + return null; + + case ':rotate': + RoomRotatingEffect.init(2500, 5000); + RoomRotatingEffect.turnVisualizationOn(); + + return null; + case ':d': + case ';d': + if(GetClubMemberLevel() === HabboClubLevelEnum.VIP) + { + roomSession.sendExpressionMessage(AvatarExpressionEnum.LAUGH.ordinal); + } + + break; + case 'o/': + case '_o/': + roomSession.sendExpressionMessage(AvatarExpressionEnum.WAVE.ordinal); + + return null; + case ':kiss': + if(GetClubMemberLevel() === HabboClubLevelEnum.VIP) + { + roomSession.sendExpressionMessage(AvatarExpressionEnum.BLOW.ordinal); + + return null; + } + + break; + case ':jump': + if(GetClubMemberLevel() === HabboClubLevelEnum.VIP) + { + roomSession.sendExpressionMessage(AvatarExpressionEnum.JUMP.ordinal); + + return null; + } + + break; + case ':idle': + roomSession.sendExpressionMessage(AvatarExpressionEnum.IDLE.ordinal); + + return null; + case '_b': + roomSession.sendExpressionMessage(AvatarExpressionEnum.RESPECT.ordinal); + + return null; + case ':sign': + roomSession.sendSignMessage(parseInt(secondPart)); + + return null; + case ':iddqd': + case ':flip': + GetEventDispatcher().dispatchEvent(new RoomZoomEvent(roomSession.roomId, -1, true)); + + return null; + case ':zoom': + GetEventDispatcher().dispatchEvent(new RoomZoomEvent(roomSession.roomId, parseFloat(secondPart), false)); + + return null; + case ':screenshot': + const texture = GetRoomEngine().createTextureFromRoom(roomSession.roomId, 1); + + (async () => + { + const image = new Image(); + + image.src = await TextureUtils.generateImageUrl(texture); + + const newWindow = window.open(''); + newWindow.document.write(image.outerHTML); + })(); + return null; + case ':pickall': + if(roomSession.isRoomOwner || GetSessionDataManager().isModerator) + { + showConfirm(LocalizeText('room.confirm.pick_all'), () => + { + GetSessionDataManager().sendSpecialCommandMessage(':pickall'); + }, + null, null, null, LocalizeText('generic.alert.title')); + } + + return null; + case ':ejectall': + if (roomSession.isRoomOwner || GetSessionDataManager().isModerator || roomSession.controllerLevel >= RoomControllerLevel.GUEST) + { + showConfirm(LocalizeText('room.confirm.eject_all'), () => + { + GetSessionDataManager().sendSpecialCommandMessage(':ejectall'); + }, + null, null, null, LocalizeText('generic.alert.title')); + } + return null; + case ':furni': + CreateLinkEvent('furni-chooser/'); + return null; + case ':chooser': + CreateLinkEvent('user-chooser/'); + return null; + case ':floor': + case ':bcfloor': + if(roomSession.controllerLevel >= RoomControllerLevel.ROOM_OWNER) CreateLinkEvent('floor-editor/show'); + + return null; + case ':togglefps': { + if(GetTicker().maxFPS > 0) GetTicker().maxFPS = 0; + else GetTicker().maxFPS = GetConfigurationValue('system.animation.fps'); + + return null; + } + case ':client': + case ':nitro': + case ':billsonnn': + showNitroAlert(); + return null; + case ':settings': + if(roomSession.isRoomOwner || GetSessionDataManager().isModerator) + { + SendMessageComposer(new RoomSettingsComposer(roomSession.roomId)); + } + + return null; + } + } + + switch(chatType) + { + case ChatMessageTypeEnum.CHAT_DEFAULT: + roomSession.sendChatMessage(text, styleId); + break; + case ChatMessageTypeEnum.CHAT_SHOUT: + roomSession.sendShoutMessage(text, styleId); + break; + case ChatMessageTypeEnum.CHAT_WHISPER: + roomSession.sendWhisperMessage(recipientName, text, styleId); + break; + } + } + + useNitroEvent(RoomSessionChatEvent.FLOOD_EVENT, event => + { + setFloodBlocked(true); + setFloodBlockedSeconds(parseFloat(event.message)); + }); + + useObjectSelectedEvent(event => + { + if(event.category !== RoomObjectCategory.UNIT) return; + + const userData = roomSession.userDataManager.getUserDataByIndex(event.id); + + if(!userData) return; + + setSelectedUsername(userData.name); + }); + + useNitroEvent(RoomEngineObjectEvent.DESELECTED, event => setSelectedUsername('')); + + useEffect(() => + { + if(!floodBlocked) return; + + let seconds = 0; + + const interval = setInterval(() => + { + setFloodBlockedSeconds(prevValue => + { + seconds = ((prevValue || 0) - 1); + + return seconds; + }); + + if(seconds < 0) + { + clearInterval(interval); + + setFloodBlocked(false); + } + }, 1000); + + return () => clearInterval(interval); + }, [ floodBlocked ]); + + useEffect(() => + { + if(!isIdle) return; + + let timeout: ReturnType = null; + + if(isIdle) + { + timeout = setTimeout(() => + { + setIsIdle(false); + setIsTyping(false) + }, 10000); + } + + return () => clearTimeout(timeout); + }, [ isIdle ]); + + useEffect(() => + { + if(isTyping) + { + if(!typingStartedSent) + { + setTypingStartedSent(true); + + roomSession.sendChatTypingMessage(isTyping); + } + } + else + { + if(typingStartedSent) + { + setTypingStartedSent(false); + + roomSession.sendChatTypingMessage(isTyping); + } + } + }, [ roomSession, isTyping, typingStartedSent ]); + + return { selectedUsername, floodBlocked, floodBlockedSeconds, setIsTyping, setIsIdle, sendChat }; +} + +export const useChatInputWidget = useChatInputWidgetState; diff --git a/src/hooks/rooms/widgets/useChatWidget.ts b/src/hooks/rooms/widgets/useChatWidget.ts new file mode 100644 index 0000000..a723a51 --- /dev/null +++ b/src/hooks/rooms/widgets/useChatWidget.ts @@ -0,0 +1,191 @@ +import { GetGuestRoomResultEvent, GetRoomEngine, PetFigureData, RoomChatSettings, RoomChatSettingsEvent, RoomDragEvent, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomSessionChatEvent, RoomUserData, SystemChatStyleEnum } from '@nitrots/nitro-renderer'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import { ChatBubbleMessage, ChatBubbleUtilities, ChatEntryType, ChatHistoryCurrentDate, GetConfigurationValue, GetRoomObjectScreenLocation, IRoomChatSettings, LocalizeText, PlaySound, RoomChatFormatter } from '../../../api'; +import { useMessageEvent, useNitroEvent } from '../../events'; +import { useRoom } from '../useRoom'; +import { useChatHistory } from './../../chat-history'; + +const useChatWidgetState = () => +{ + const [ chatMessages, setChatMessages ] = useState([]); + const [ chatSettings, setChatSettings ] = useState({ + mode: RoomChatSettings.CHAT_MODE_FREE_FLOW, + weight: RoomChatSettings.CHAT_BUBBLE_WIDTH_NORMAL, + speed: RoomChatSettings.CHAT_SCROLL_SPEED_NORMAL, + distance: 50, + protection: RoomChatSettings.FLOOD_FILTER_NORMAL + }); + const { roomSession = null } = useRoom(); + const { addChatEntry } = useChatHistory(); + const isDisposed = useRef(false); + + const getScrollSpeed = useMemo(() => + { + if(!chatSettings) return 6000; + + switch(chatSettings.speed) + { + case RoomChatSettings.CHAT_SCROLL_SPEED_FAST: + return 3000; + case RoomChatSettings.CHAT_SCROLL_SPEED_NORMAL: + return 6000; + case RoomChatSettings.CHAT_SCROLL_SPEED_SLOW: + return 12000; + } + }, [ chatSettings ]); + + useNitroEvent(RoomSessionChatEvent.CHAT_EVENT, async event => + { + const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, event.objectId, RoomObjectCategory.UNIT); + const bubbleLocation = roomObject ? GetRoomObjectScreenLocation(roomSession.roomId, roomObject?.id, RoomObjectCategory.UNIT) : { x: 0, y: 0 }; + const userData = roomObject ? roomSession.userDataManager.getUserDataByIndex(event.objectId) : new RoomUserData(-1); + + let username = ''; + let avatarColor = 0; + let imageUrl: string = null; + let chatType = event.chatType; + let styleId = event.style; + let userType = 0; + let petType = -1; + let text = event.message; + + if(userData) + { + userType = userData.type; + + const figure = userData.figure; + + switch(userType) + { + case RoomObjectType.PET: + imageUrl = await ChatBubbleUtilities.getPetImage(figure, 2, true, 64, roomObject.model.getValue(RoomObjectVariable.FIGURE_POSTURE)); + petType = new PetFigureData(figure).typeId; + break; + case RoomObjectType.USER: + imageUrl = await ChatBubbleUtilities.getUserImage(figure); + break; + case RoomObjectType.RENTABLE_BOT: + case RoomObjectType.BOT: + styleId = SystemChatStyleEnum.BOT; + break; + } + + avatarColor = ChatBubbleUtilities.AVATAR_COLOR_CACHE.get(figure); + username = userData.name; + } + + switch(chatType) + { + case RoomSessionChatEvent.CHAT_TYPE_RESPECT: + text = LocalizeText('widgets.chatbubble.respect', [ 'username' ], [ username ]); + + if(GetConfigurationValue('respect.options')['enabled']) PlaySound(GetConfigurationValue('respect.options')['sound']); + + break; + case RoomSessionChatEvent.CHAT_TYPE_PETREVIVE: + case RoomSessionChatEvent.CHAT_TYPE_PET_REBREED_FERTILIZE: + case RoomSessionChatEvent.CHAT_TYPE_PET_SPEED_FERTILIZE: { + let textKey = 'widget.chatbubble.petrevived'; + + if(chatType === RoomSessionChatEvent.CHAT_TYPE_PET_REBREED_FERTILIZE) + { + textKey = 'widget.chatbubble.petrefertilized;'; + } + + else if(chatType === RoomSessionChatEvent.CHAT_TYPE_PET_SPEED_FERTILIZE) + { + textKey = 'widget.chatbubble.petspeedfertilized'; + } + + let targetUserName: string = null; + + const newRoomObject = GetRoomEngine().getRoomObject(roomSession.roomId, event.extraParam, RoomObjectCategory.UNIT); + + if(newRoomObject) + { + const newUserData = roomSession.userDataManager.getUserDataByIndex(roomObject.id); + + if(newUserData) targetUserName = newUserData.name; + } + + text = LocalizeText(textKey, [ 'petName', 'userName' ], [ username, targetUserName ]); + break; + } + case RoomSessionChatEvent.CHAT_TYPE_PETRESPECT: + text = LocalizeText('widget.chatbubble.petrespect', [ 'petname' ], [ username ]); + break; + case RoomSessionChatEvent.CHAT_TYPE_PETTREAT: + text = LocalizeText('widget.chatbubble.pettreat', [ 'petname' ], [ username ]); + break; + case RoomSessionChatEvent.CHAT_TYPE_HAND_ITEM_RECEIVED: + text = LocalizeText('widget.chatbubble.handitem', [ 'username', 'handitem' ], [ username, LocalizeText(('handitem' + event.extraParam)) ]); + break; + case RoomSessionChatEvent.CHAT_TYPE_MUTE_REMAINING: { + const hours = ((event.extraParam > 0) ? Math.floor((event.extraParam / 3600)) : 0).toString(); + const minutes = ((event.extraParam > 0) ? Math.floor((event.extraParam % 3600) / 60) : 0).toString(); + const seconds = (event.extraParam % 60).toString(); + + text = LocalizeText('widget.chatbubble.mutetime', [ 'hours', 'minutes', 'seconds' ], [ hours, minutes, seconds ]); + break; + } + } + + const formattedText = RoomChatFormatter(text); + const color = (avatarColor && (('#' + (avatarColor.toString(16).padStart(6, '0'))) || null)); + + const chatMessage = new ChatBubbleMessage( + userData.roomIndex, + RoomObjectCategory.UNIT, + roomSession.roomId, + text, + formattedText, + username, + { x: bubbleLocation.x, y: bubbleLocation.y }, + chatType, + styleId, + imageUrl, + color); + + setChatMessages(prevValue => [ ...prevValue, chatMessage ]); + addChatEntry({ id: -1, webId: userData.webID, entityId: userData.roomIndex, name: username, imageUrl, style: styleId, chatType: chatType, entityType: userData.type, message: formattedText, timestamp: ChatHistoryCurrentDate(), type: ChatEntryType.TYPE_CHAT, roomId: roomSession.roomId, color }); + }); + + useNitroEvent(RoomDragEvent.ROOM_DRAG, event => + { + if(!chatMessages.length || (event.roomId !== roomSession.roomId)) return; + + const offsetX = event.offsetX; + + chatMessages.forEach(chat => (chat.elementRef && (chat.left += offsetX))); + }); + + useMessageEvent(GetGuestRoomResultEvent, event => + { + const parser = event.getParser(); + + if(!parser.roomEnter) return; + + setChatSettings(parser.chat); + }); + + useMessageEvent(RoomChatSettingsEvent, event => + { + const parser = event.getParser(); + + setChatSettings(parser.chat); + }); + + useEffect(() => + { + isDisposed.current = false; + + return () => + { + isDisposed.current = true; + } + }, []); + + return { chatMessages, setChatMessages, chatSettings, getScrollSpeed }; +} + +export const useChatWidget = useChatWidgetState; diff --git a/src/hooks/rooms/widgets/useDoorbellWidget.ts b/src/hooks/rooms/widgets/useDoorbellWidget.ts new file mode 100644 index 0000000..7cf5051 --- /dev/null +++ b/src/hooks/rooms/widgets/useDoorbellWidget.ts @@ -0,0 +1,44 @@ +import { RoomSessionDoorbellEvent } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { GetRoomSession } from '../../../api'; +import { useNitroEvent } from '../../events'; + +const useDoorbellWidgetState = () => +{ + const [ users, setUsers ] = useState([]); + + const addUser = (userName: string) => + { + if(users.indexOf(userName) >= 0) return; + + setUsers([ ...users, userName ]); + } + + const removeUser = (userName: string) => + { + const index = users.indexOf(userName); + + if(index === -1) return; + + const newUsers = [ ...users ]; + + newUsers.splice(index, 1); + + setUsers(newUsers); + } + + const answer = (userName: string, flag: boolean) => + { + GetRoomSession().sendDoorbellApprovalMessage(userName, flag); + + removeUser(userName); + } + + useNitroEvent(RoomSessionDoorbellEvent.DOORBELL, event => addUser(event.userName)); + useNitroEvent(RoomSessionDoorbellEvent.RSDE_REJECTED, event => removeUser(event.userName)); + useNitroEvent(RoomSessionDoorbellEvent.RSDE_ACCEPTED, event => removeUser(event.userName)); + + return { users, addUser, removeUser, answer }; +} + +export const useDoorbellWidget = useDoorbellWidgetState; diff --git a/src/hooks/rooms/widgets/useFilterWordsWidget.ts b/src/hooks/rooms/widgets/useFilterWordsWidget.ts new file mode 100644 index 0000000..3811682 --- /dev/null +++ b/src/hooks/rooms/widgets/useFilterWordsWidget.ts @@ -0,0 +1,23 @@ +import { RoomFilterSettingsMessageEvent } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { useMessageEvent } from '../../events'; + +const useFilterWordsWidgetState = () => +{ + const [ wordsFilter, setWordsFilter ] = useState(null); + const [ isVisible, setIsVisible ] = useState(false); + + const onClose = () => setIsVisible(false); + + useMessageEvent(RoomFilterSettingsMessageEvent, event => + { + const parser = event.getParser(); + + setIsVisible(true); + setWordsFilter(parser.words); + }); + + return { wordsFilter, isVisible, setWordsFilter, onClose }; +} + +export const useFilterWordsWidget = useFilterWordsWidgetState; diff --git a/src/hooks/rooms/widgets/useFriendRequestWidget.ts b/src/hooks/rooms/widgets/useFriendRequestWidget.ts new file mode 100644 index 0000000..82bf6a2 --- /dev/null +++ b/src/hooks/rooms/widgets/useFriendRequestWidget.ts @@ -0,0 +1,81 @@ +import { RoomObjectCategory, RoomObjectUserType } from '@nitrots/nitro-renderer'; +import { useEffect, useMemo, useState } from 'react'; +import { GetRoomSession, MessengerRequest } from '../../../api'; +import { useFriends } from '../../friends'; +import { useUserAddedEvent, useUserRemovedEvent } from '../engine'; + +const useFriendRequestWidgetState = () => +{ + const [ activeRequests, setActiveRequests ] = useState<{ roomIndex: number, request: MessengerRequest }[]>([]); + const { requests = [], dismissedRequestIds = [], setDismissedRequestIds = null } = useFriends(); + + const displayedRequests = useMemo(() => activeRequests.filter(request => (dismissedRequestIds.indexOf(request.request.requesterUserId) === -1)), [ activeRequests, dismissedRequestIds ]); + + const hideFriendRequest = (userId: number) => + { + setDismissedRequestIds(prevValue => + { + if(prevValue.indexOf(userId) >= 0) return prevValue; + + const newValue = [ ...prevValue ]; + + newValue.push(userId); + + return newValue; + }); + } + + useUserAddedEvent(true, event => + { + if(event.category !== RoomObjectCategory.UNIT) return; + + const userData = GetRoomSession().userDataManager.getUserDataByIndex(event.id); + + if(!userData || (userData.type !== RoomObjectUserType.getTypeNumber(RoomObjectUserType.USER))) return; + + const request = requests.find(request => (request.requesterUserId === userData.webID)); + + if(!request || activeRequests.find(request => (request.request.requesterUserId === userData.webID))) return; + + const newValue = [ ...activeRequests ]; + + newValue.push({ roomIndex: userData.roomIndex, request }); + + setActiveRequests(newValue); + }); + + useUserRemovedEvent(true, event => + { + if(event.category !== RoomObjectCategory.UNIT) return; + + const index = activeRequests.findIndex(request => (request.roomIndex === event.id)); + + if(index === -1) return; + + const newValue = [ ...activeRequests ]; + + newValue.splice(index, 1); + + setActiveRequests(newValue); + }); + + useEffect(() => + { + const newDisplayedRequests: { roomIndex: number, request: MessengerRequest }[] = []; + + for(const request of requests) + { + const userData = GetRoomSession().userDataManager.getUserData(request.requesterUserId); + + if(!userData) continue; + + newDisplayedRequests.push({ roomIndex: userData.roomIndex, request }); + } + + setActiveRequests(newDisplayedRequests); + }, [ requests ]); + + return { displayedRequests, hideFriendRequest }; +} + +export const useFriendRequestWidget = useFriendRequestWidgetState; diff --git a/src/hooks/rooms/widgets/useFurniChooserWidget.ts b/src/hooks/rooms/widgets/useFurniChooserWidget.ts new file mode 100644 index 0000000..428db2c --- /dev/null +++ b/src/hooks/rooms/widgets/useFurniChooserWidget.ts @@ -0,0 +1,132 @@ +import { GetRoomEngine, GetSessionDataManager, RoomObjectCategory, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { GetRoomSession, LocalizeText, RoomObjectItem } from '../../../api'; +import { useFurniAddedEvent, useFurniRemovedEvent } from '../engine'; +import { useRoom } from '../useRoom'; + +const useFurniChooserWidgetState = () => +{ + const [ items, setItems ] = useState(null); + const { roomSession = null } = useRoom(); + + const onClose = () => setItems(null); + + const selectItem = (item: RoomObjectItem) => item && GetRoomEngine().selectRoomObject(GetRoomSession().roomId, item.id, item.category); + + const populateChooser = () => + { + const sessionDataManager = GetSessionDataManager(); + const wallObjects = GetRoomEngine().getRoomObjects(roomSession.roomId, RoomObjectCategory.WALL); + const floorObjects = GetRoomEngine().getRoomObjects(roomSession.roomId, RoomObjectCategory.FLOOR); + + const wallItems = wallObjects.map(roomObject => + { + if(roomObject.id < 0) return null; + + let name = roomObject.type; + + if(name.startsWith('poster')) + { + name = LocalizeText(`poster_${ name.replace('poster', '') }_name`); + } + else + { + const typeId = roomObject.model.getValue(RoomObjectVariable.FURNITURE_TYPE_ID); + const furniData = sessionDataManager.getWallItemData(typeId); + + if(furniData && furniData.name.length) name = furniData.name; + } + + return new RoomObjectItem(roomObject.id, RoomObjectCategory.WALL, name); + }); + + const floorItems = floorObjects.map(roomObject => + { + if(roomObject.id < 0) return null; + + let name = roomObject.type; + + const typeId = roomObject.model.getValue(RoomObjectVariable.FURNITURE_TYPE_ID); + const furniData = sessionDataManager.getFloorItemData(typeId); + + if(furniData && furniData.name.length) name = furniData.name; + + return new RoomObjectItem(roomObject.id, RoomObjectCategory.FLOOR, name); + }); + + setItems([ ...wallItems, ...floorItems ].sort((a, b) => ((a.name < b.name) ? -1 : 1))); + } + + useFurniAddedEvent(!!items, event => + { + if(event.id < 0) return; + + const roomObject = GetRoomEngine().getRoomObject(GetRoomSession().roomId, event.id, event.category); + + if(!roomObject) return; + + let item: RoomObjectItem = null; + + switch(event.category) + { + case RoomObjectCategory.WALL: { + let name = roomObject.type; + + if(name.startsWith('poster')) + { + name = LocalizeText(`poster_${ name.replace('poster', '') }_name`); + } + else + { + const typeId = roomObject.model.getValue(RoomObjectVariable.FURNITURE_TYPE_ID); + const furniData = GetSessionDataManager().getWallItemData(typeId); + + if(furniData && furniData.name.length) name = furniData.name; + } + + item = new RoomObjectItem(roomObject.id, RoomObjectCategory.WALL, name); + + break; + } + case RoomObjectCategory.FLOOR: { + let name = roomObject.type; + + const typeId = roomObject.model.getValue(RoomObjectVariable.FURNITURE_TYPE_ID); + const furniData = GetSessionDataManager().getFloorItemData(typeId); + + if(furniData && furniData.name.length) name = furniData.name; + + item = new RoomObjectItem(roomObject.id, RoomObjectCategory.FLOOR, name); + } + } + + setItems(prevValue => [ ...prevValue, item ].sort((a, b) => ((a.name < b.name) ? -1 : 1))); + }); + + useFurniRemovedEvent(!!items, event => + { + if(event.id < 0) return; + + setItems(prevValue => + { + const newValue = [ ...prevValue ]; + + for(let i = 0; i < newValue.length; i++) + { + const existingValue = newValue[i]; + + if((existingValue.id !== event.id) || (existingValue.category !== event.category)) continue; + + newValue.splice(i, 1); + + break; + } + + return newValue; + }); + }); + + return { items, onClose, selectItem, populateChooser }; +} + +export const useFurniChooserWidget = useFurniChooserWidgetState; diff --git a/src/hooks/rooms/widgets/usePetPackageWidget.ts b/src/hooks/rooms/widgets/usePetPackageWidget.ts new file mode 100644 index 0000000..1de3f91 --- /dev/null +++ b/src/hooks/rooms/widgets/usePetPackageWidget.ts @@ -0,0 +1,75 @@ +import { GetRoomEngine, OpenPetPackageMessageComposer, RoomObjectCategory, RoomSessionPetPackageEvent } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { LocalizeText, SendMessageComposer } from '../../../api'; +import { useNitroEvent } from '../../events'; + +const usePetPackageWidgetState = () => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ objectId, setObjectId ] = useState(-1); + const [ objectType, setObjectType ] = useState(''); + const [ petName, setPetName ] = useState(''); + const [ errorResult, setErrorResult ] = useState(''); + + const onClose = () => + { + setErrorResult(''); + setPetName(''); + setObjectType(''); + setObjectId(-1); + setIsVisible(false); + } + + const onConfirm = () => + { + SendMessageComposer(new OpenPetPackageMessageComposer(objectId, petName)); + } + + const onChangePetName = (petName: string) => + { + setPetName(petName); + if (errorResult.length > 0) setErrorResult(''); + } + + const getErrorResultForCode = (errorCode: number) => + { + if (!errorCode || errorCode === 0) return; + + switch(errorCode) + { + case 1: + return LocalizeText('catalog.alert.petname.long'); + case 2: + return LocalizeText('catalog.alert.petname.short'); + case 3: + return LocalizeText('catalog.alert.petname.chars'); + case 4: + default: + return LocalizeText('catalog.alert.petname.bobba'); + } + } + + useNitroEvent(RoomSessionPetPackageEvent.RSOPPE_OPEN_PET_PACKAGE_REQUESTED, event => + { + if (!event) return; + + const roomObject = GetRoomEngine().getRoomObject(event.session.roomId, event.objectId, RoomObjectCategory.FLOOR); + + setObjectId(event.objectId); + setObjectType(roomObject.type); + setIsVisible(true); + }); + + useNitroEvent(RoomSessionPetPackageEvent.RSOPPE_OPEN_PET_PACKAGE_RESULT, event => + { + if (!event) return; + + if (event.nameValidationStatus === 0) onClose(); + + if (event.nameValidationStatus !== 0) setErrorResult(getErrorResultForCode(event.nameValidationStatus)); + }); + + return { isVisible, errorResult, petName, objectType, onChangePetName, onConfirm, onClose }; +} + +export const usePetPackageWidget = usePetPackageWidgetState; diff --git a/src/hooks/rooms/widgets/usePollWidget.ts b/src/hooks/rooms/widgets/usePollWidget.ts new file mode 100644 index 0000000..6132378 --- /dev/null +++ b/src/hooks/rooms/widgets/usePollWidget.ts @@ -0,0 +1,52 @@ +import { RoomSessionPollEvent } from '@nitrots/nitro-renderer'; +import { DispatchUiEvent, RoomWidgetPollUpdateEvent } from '../../../api'; +import { useNitroEvent } from '../../events'; +import { useRoom } from '../useRoom'; + +const usePollWidgetState = () => +{ + const { roomSession = null } = useRoom(); + + const startPoll = (pollId: number) => roomSession.sendPollStartMessage(pollId); + + const rejectPoll = (pollId: number) => roomSession.sendPollRejectMessage(pollId); + + const answerPoll = (pollId: number, questionId: number, answers: string[]) => roomSession.sendPollAnswerMessage(pollId, questionId, answers); + + useNitroEvent(RoomSessionPollEvent.OFFER, event => + { + const pollEvent = new RoomWidgetPollUpdateEvent(RoomWidgetPollUpdateEvent.OFFER, event.id); + + pollEvent.summary = event.summary; + pollEvent.headline = event.headline; + + DispatchUiEvent(pollEvent); + }); + + useNitroEvent(RoomSessionPollEvent.ERROR, event => + { + const pollEvent = new RoomWidgetPollUpdateEvent(RoomWidgetPollUpdateEvent.ERROR, event.id); + + pollEvent.summary = event.summary; + pollEvent.headline = event.headline; + + DispatchUiEvent(pollEvent); + }); + + useNitroEvent(RoomSessionPollEvent.CONTENT, event => + { + const pollEvent = new RoomWidgetPollUpdateEvent(RoomWidgetPollUpdateEvent.CONTENT, event.id); + + pollEvent.startMessage = event.startMessage; + pollEvent.endMessage = event.endMessage; + pollEvent.numQuestions = event.numQuestions; + pollEvent.questionArray = event.questionArray; + pollEvent.npsPoll = event.npsPoll; + + DispatchUiEvent(pollEvent); + }); + + return { startPoll, rejectPoll, answerPoll }; +} + +export const usePollWidget = usePollWidgetState; diff --git a/src/hooks/rooms/widgets/useUserChooserWidget.ts b/src/hooks/rooms/widgets/useUserChooserWidget.ts new file mode 100644 index 0000000..40202a3 --- /dev/null +++ b/src/hooks/rooms/widgets/useUserChooserWidget.ts @@ -0,0 +1,80 @@ +import { GetRoomEngine, RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { GetRoomSession, RoomObjectItem } from '../../../api'; +import { useUserAddedEvent, useUserRemovedEvent } from '../engine'; +import { useRoom } from '../useRoom'; + +const useUserChooserWidgetState = () => +{ + const [ items, setItems ] = useState(null); + const { roomSession = null } = useRoom(); + + const onClose = () => setItems(null); + + const selectItem = (item: RoomObjectItem) => item && GetRoomEngine().selectRoomObject(GetRoomSession().roomId, item.id, item.category); + + const populateChooser = () => + { + const roomSession = GetRoomSession(); + const roomObjects = GetRoomEngine().getRoomObjects(roomSession.roomId, RoomObjectCategory.UNIT); + + setItems(roomObjects + .map(roomObject => + { + if(roomObject.id < 0) return null; + + const userData = roomSession.userDataManager.getUserDataByIndex(roomObject.id); + + if(!userData) return null; + + return new RoomObjectItem(userData.roomIndex, RoomObjectCategory.UNIT, userData.name); + }) + .sort((a, b) => ((a.name < b.name) ? -1 : 1))); + } + + useUserAddedEvent(!!items, event => + { + if(event.id < 0) return; + + const userData = GetRoomSession().userDataManager.getUserDataByIndex(event.id); + + if(!userData) return; + + setItems(prevValue => + { + const newValue = [ ...prevValue ]; + + newValue.push(new RoomObjectItem(userData.roomIndex, RoomObjectCategory.UNIT, userData.name)); + newValue.sort((a, b) => ((a.name < b.name) ? -1 : 1)); + + return newValue; + }); + }); + + useUserRemovedEvent(!!items, event => + { + if(event.id < 0) return; + + setItems(prevValue => + { + const newValue = [ ...prevValue ]; + + for(let i = 0; i < newValue.length; i++) + { + const existingValue = newValue[i]; + + if((existingValue.id !== event.id) || (existingValue.category !== event.category)) continue; + + newValue.splice(i, 1); + + break; + } + + return newValue; + }); + }); + + return { items, onClose, selectItem, populateChooser }; +} + +export const useUserChooserWidget = useUserChooserWidgetState; diff --git a/src/hooks/rooms/widgets/useWordQuizWidget.ts b/src/hooks/rooms/widgets/useWordQuizWidget.ts new file mode 100644 index 0000000..757f763 --- /dev/null +++ b/src/hooks/rooms/widgets/useWordQuizWidget.ts @@ -0,0 +1,149 @@ +import { AvatarAction, GetRoomEngine, IQuestion, RoomSessionWordQuizEvent } from '@nitrots/nitro-renderer'; +import { useEffect, useState } from 'react'; +import { VoteValue } from '../../../api'; +import { useNitroEvent } from '../../events'; +import { useRoom } from '../useRoom'; +import { usePollWidget } from './usePollWidget'; + +const DEFAULT_DISPLAY_DELAY = 4000; +const SIGN_FADE_DELAY = 3; + +const useWordQuizWidgetState = () => +{ + const [ pollId, setPollId ] = useState(-1); + const [ question, setQuestion ] = useState(null); + const [ answerSent, setAnswerSent ] = useState(false); + const [ questionClearTimeout, setQuestionClearTimeout ] = useState>(null); + const [ answerCounts, setAnswerCounts ] = useState>(new Map()); + const [ userAnswers, setUserAnswers ] = useState>(new Map()); + const { answerPoll = null } = usePollWidget(); + const { roomSession = null } = useRoom(); + + const clearQuestion = () => + { + setPollId(-1); + setQuestion(null); + } + + const vote = (vote: string) => + { + if(answerSent || !question) return; + + answerPoll(pollId, question.id, [ vote ]); + + setAnswerSent(true); + } + + useNitroEvent(RoomSessionWordQuizEvent.ANSWERED, event => + { + const userData = roomSession.userDataManager.getUserData(event.userId); + + if(!userData) return; + + setAnswerCounts(event.answerCounts); + + setUserAnswers(prevValue => + { + if(!prevValue.has(userData.roomIndex)) + { + const newValue = new Map(userAnswers); + + newValue.set(userData.roomIndex, { value: event.value, secondsLeft: SIGN_FADE_DELAY }); + + return newValue; + } + + return prevValue; + }); + + GetRoomEngine().updateRoomObjectUserGesture(roomSession.roomId, userData.roomIndex, AvatarAction.getGestureId((event.value === '0') ? AvatarAction.GESTURE_SAD : AvatarAction.GESTURE_SMILE)); + }); + + useNitroEvent(RoomSessionWordQuizEvent.FINISHED, event => + { + if(question && (question.id === event.questionId)) + { + setAnswerCounts(event.answerCounts); + setAnswerSent(true); + + setQuestionClearTimeout(prevValue => + { + if(prevValue) clearTimeout(prevValue); + + return setTimeout(() => clearQuestion(), DEFAULT_DISPLAY_DELAY); + }); + } + + setUserAnswers(new Map()); + }); + + useNitroEvent(RoomSessionWordQuizEvent.QUESTION, event => + { + setPollId(event.id); + setQuestion(event.question); + setAnswerSent(false); + setAnswerCounts(new Map()); + setUserAnswers(new Map()); + + setQuestionClearTimeout(prevValue => + { + if(prevValue) clearTimeout(prevValue); + + if(event.duration > 0) + { + const delay = event.duration < 1000 ? DEFAULT_DISPLAY_DELAY : event.duration; + + return setTimeout(() => clearQuestion(), delay); + } + + return null; + }); + }); + + useEffect(() => + { + const checkSignFade = () => + { + setUserAnswers(prevValue => + { + const keysToRemove: number[] = []; + + prevValue.forEach((value, key) => + { + value.secondsLeft--; + + if(value.secondsLeft <= 0) keysToRemove.push(key); + }); + + if(keysToRemove.length === 0) return prevValue; + + const copy = new Map(prevValue); + + keysToRemove.forEach(key => copy.delete(key)); + + return copy; + }); + } + + const interval = setInterval(() => checkSignFade(), 1000); + + return () => clearInterval(interval); + }, []); + + useEffect(() => + { + return () => + { + setQuestionClearTimeout(prevValue => + { + if(prevValue) clearTimeout(prevValue); + + return null; + }); + } + }, []); + + return { question, answerSent, answerCounts, userAnswers, vote }; +} + +export const useWordQuizWidget = useWordQuizWidgetState; diff --git a/src/hooks/session/index.ts b/src/hooks/session/index.ts new file mode 100644 index 0000000..e61c7f3 --- /dev/null +++ b/src/hooks/session/index.ts @@ -0,0 +1 @@ +export * from './useSessionInfo'; diff --git a/src/hooks/session/useSessionInfo.ts b/src/hooks/session/useSessionInfo.ts new file mode 100644 index 0000000..122991d --- /dev/null +++ b/src/hooks/session/useSessionInfo.ts @@ -0,0 +1,63 @@ +import { FigureUpdateEvent, GetSessionDataManager, RoomUnitChatStyleComposer, UserInfoDataParser, UserInfoEvent, UserSettingsEvent } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { useBetween } from 'use-between'; +import { SendMessageComposer } from '../../api'; +import { useMessageEvent } from '../events'; + +const useSessionInfoState = () => +{ + const [ userInfo, setUserInfo ] = useState(null); + const [ userFigure, setUserFigure ] = useState(null); + const [ chatStyleId, setChatStyleId ] = useState(0); + const [ userRespectRemaining, setUserRespectRemaining ] = useState(0); + const [ petRespectRemaining, setPetRespectRemaining ] = useState(0); + + const updateChatStyleId = (styleId: number) => + { + setChatStyleId(styleId); + + SendMessageComposer(new RoomUnitChatStyleComposer(styleId)); + } + + const respectUser = (userId: number) => + { + GetSessionDataManager().giveRespect(userId); + + setUserRespectRemaining(GetSessionDataManager().respectsLeft); + } + + const respectPet = (petId: number) => + { + GetSessionDataManager().givePetRespect(petId); + + setPetRespectRemaining(GetSessionDataManager().respectsPetLeft); + } + + useMessageEvent(UserInfoEvent, event => + { + const parser = event.getParser(); + + setUserInfo(parser.userInfo); + setUserFigure(parser.userInfo.figure); + setUserRespectRemaining(parser.userInfo.respectsRemaining); + setPetRespectRemaining(parser.userInfo.respectsPetRemaining); + }); + + useMessageEvent(FigureUpdateEvent, event => + { + const parser = event.getParser(); + + setUserFigure(parser.figure); + }); + + useMessageEvent(UserSettingsEvent, event => + { + const parser = event.getParser(); + + setChatStyleId(parser.chatType); + }); + + return { userInfo, userFigure, chatStyleId, userRespectRemaining, petRespectRemaining, respectUser, respectPet, updateChatStyleId }; +} + +export const useSessionInfo = () => useBetween(useSessionInfoState); diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts new file mode 100644 index 0000000..1e55fb9 --- /dev/null +++ b/src/hooks/useLocalStorage.ts @@ -0,0 +1,44 @@ +import { NitroLogger } from '@nitrots/nitro-renderer'; +import { Dispatch, SetStateAction, useState } from 'react'; +import { GetLocalStorage, SetLocalStorage } from '../api'; + +const useLocalStorageState = (key: string, initialValue: T): [ T, Dispatch>] => +{ + const [ storedValue, setStoredValue ] = useState(() => + { + if(typeof window === 'undefined') return initialValue; + + try + { + const item = GetLocalStorage(key); + + return item ?? initialValue; + } + + catch(error) + { + return initialValue; + } + }); + + const setValue = (value: T) => + { + try + { + const valueToStore = value instanceof Function ? value(storedValue) : value; + + setStoredValue(valueToStore); + + if(typeof window !== 'undefined') SetLocalStorage(key, valueToStore); + } + + catch(error) + { + NitroLogger.error(error); + } + } + + return [ storedValue, setValue ]; +} + +export const useLocalStorage = useLocalStorageState; diff --git a/src/hooks/useSharedVisibility.ts b/src/hooks/useSharedVisibility.ts new file mode 100644 index 0000000..b55855b --- /dev/null +++ b/src/hooks/useSharedVisibility.ts @@ -0,0 +1,44 @@ +import { useCallback, useMemo, useState } from 'react'; + +export const useSharedVisibility = () => +{ + const [ activeIds, setActiveIds ] = useState([]); + + const isVisible = useMemo(() => !!activeIds.length, [ activeIds ]); + + const activate = useCallback(() => + { + let id = -1; + + setActiveIds(prevValue => + { + const newValue = [ ...prevValue ]; + + id = newValue.length ? (newValue[(newValue.length - 1)] + 1) : 0; + + newValue.push(id); + + return newValue; + }); + + return id; + }, []); + + const deactivate = useCallback((id: number) => + { + setActiveIds(prevValue => + { + const newValue = [ ...prevValue ]; + + const index = newValue.indexOf(id); + + if(index === -1) return prevValue; + + newValue.splice(index, 1); + + return newValue; + }); + }, []); + + return { isVisible, activate, deactivate }; +} diff --git a/src/hooks/wired/index.ts b/src/hooks/wired/index.ts new file mode 100644 index 0000000..dc79020 --- /dev/null +++ b/src/hooks/wired/index.ts @@ -0,0 +1 @@ +export * from './useWired'; diff --git a/src/hooks/wired/useWired.ts b/src/hooks/wired/useWired.ts new file mode 100644 index 0000000..5a6cb65 --- /dev/null +++ b/src/hooks/wired/useWired.ts @@ -0,0 +1,140 @@ +import { ConditionDefinition, OpenMessageComposer, Triggerable, TriggerDefinition, UpdateActionMessageComposer, UpdateConditionMessageComposer, UpdateTriggerMessageComposer, WiredActionDefinition, WiredFurniActionEvent, WiredFurniConditionEvent, WiredFurniTriggerEvent, WiredOpenEvent, WiredSaveSuccessEvent } from '@nitrots/nitro-renderer'; +import { useEffect, useState } from 'react'; +import { useBetween } from 'use-between'; +import { IsOwnerOfFloorFurniture, LocalizeText, SendMessageComposer, WiredFurniType, WiredSelectionVisualizer } from '../../api'; +import { useMessageEvent } from '../events'; +import { useNotification } from '../notification'; + +const useWiredState = () => +{ + const [ trigger, setTrigger ] = useState(null); + const [ intParams, setIntParams ] = useState([]); + const [ stringParam, setStringParam ] = useState(''); + const [ furniIds, setFurniIds ] = useState([]); + const [ actionDelay, setActionDelay ] = useState(0); + const [ allowsFurni, setAllowsFurni ] = useState(WiredFurniType.STUFF_SELECTION_OPTION_NONE); + const { showConfirm = null } = useNotification(); + + const saveWired = () => + { + const save = (trigger: Triggerable) => + { + if(!trigger) return; + + if(trigger instanceof WiredActionDefinition) + { + SendMessageComposer(new UpdateActionMessageComposer(trigger.id, intParams, stringParam, furniIds, actionDelay, trigger.stuffTypeSelectionCode)); + } + + else if(trigger instanceof TriggerDefinition) + { + SendMessageComposer(new UpdateTriggerMessageComposer(trigger.id, intParams, stringParam, furniIds, trigger.stuffTypeSelectionCode)); + } + + else if(trigger instanceof ConditionDefinition) + { + SendMessageComposer(new UpdateConditionMessageComposer(trigger.id, intParams, stringParam, furniIds, trigger.stuffTypeSelectionCode)); + } + } + + if(!IsOwnerOfFloorFurniture(trigger.id)) + { + showConfirm(LocalizeText('wiredfurni.nonowner.change.confirm.body'), () => + { + save(trigger) + }, null, null, null, LocalizeText('wiredfurni.nonowner.change.confirm.title')); + } + else + { + save(trigger); + } + } + + const selectObjectForWired = (objectId: number, category: number) => + { + if(!trigger || !allowsFurni) return; + + if(objectId <= 0) return; + + setFurniIds(prevValue => + { + const newFurniIds = [ ...prevValue ]; + + const index = prevValue.indexOf(objectId); + + if(index >= 0) + { + newFurniIds.splice(index, 1); + + WiredSelectionVisualizer.hide(objectId); + } + + else if(newFurniIds.length < trigger.maximumItemSelectionCount) + { + newFurniIds.push(objectId); + + WiredSelectionVisualizer.show(objectId); + } + + return newFurniIds; + }); + } + + useMessageEvent(WiredOpenEvent, event => + { + const parser = event.getParser(); + + SendMessageComposer(new OpenMessageComposer(parser.stuffId)); + }); + + useMessageEvent(WiredSaveSuccessEvent, event => + { + const parser = event.getParser(); + + setTrigger(null); + }); + + useMessageEvent(WiredFurniActionEvent, event => + { + const parser = event.getParser(); + + setTrigger(parser.definition); + }); + + useMessageEvent(WiredFurniConditionEvent, event => + { + const parser = event.getParser(); + + setTrigger(parser.definition); + }); + + useMessageEvent(WiredFurniTriggerEvent, event => + { + const parser = event.getParser(); + + setTrigger(parser.definition); + }); + + useEffect(() => + { + if(!trigger) return; + + return () => + { + setIntParams([]); + setStringParam(''); + setActionDelay(0); + setFurniIds(prevValue => + { + if(prevValue && prevValue.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prevValue); + + return []; + }); + setAllowsFurni(WiredFurniType.STUFF_SELECTION_OPTION_NONE); + } + }, [ trigger ]); + + return { trigger, setTrigger, intParams, setIntParams, stringParam, setStringParam, furniIds, setFurniIds, actionDelay, setActionDelay, setAllowsFurni, saveWired, selectObjectForWired }; +} + +export const useWired = () => useBetween(useWiredState); diff --git a/src/index.scss b/src/index.scss new file mode 100644 index 0000000..5f3ecbb --- /dev/null +++ b/src/index.scss @@ -0,0 +1,26 @@ +@import './assets/styles'; + +html, +body { + padding: 0; + width: 100%; + height: 100%; + overflow: hidden; + user-select: none; + scrollbar-width: thin; + + .image-rendering-pixelated { + image-rendering: pixelated; + image-rendering: -moz-crisp-edges; + } + + * { + -webkit-font-smoothing: antialiased; + } +} + +img { + object-fit: none; +} + +@import './App'; diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..196309d --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,5 @@ +import { createRoot } from 'react-dom/client'; +import { App } from './App'; +import './index.scss'; + +createRoot(document.getElementById('root')).render(); diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts new file mode 100644 index 0000000..6431bc5 --- /dev/null +++ b/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/workers/IntervalWebWorker.ts b/src/workers/IntervalWebWorker.ts new file mode 100644 index 0000000..978c80a --- /dev/null +++ b/src/workers/IntervalWebWorker.ts @@ -0,0 +1,26 @@ +export default () => +{ + let interval: ReturnType = null; + + // eslint-disable-next-line no-restricted-globals + self.onmessage = (message: MessageEvent) => + { + if(!message) return; + + const data: { [index: string]: any } = message.data; + + switch(data.action) + { + case 'START': + interval = setInterval(() => postMessage(null), data.content); + break; + case 'STOP': + if(interval) + { + clearInterval(interval); + interval = null; + } + break; + } + } +} diff --git a/src/workers/WorkerBuilder.ts b/src/workers/WorkerBuilder.ts new file mode 100644 index 0000000..b848893 --- /dev/null +++ b/src/workers/WorkerBuilder.ts @@ -0,0 +1,10 @@ +export class WorkerBuilder extends Worker +{ + constructor(worker) + { + const code = worker.toString(); + const blob = new Blob([ `(${ code })()` ]); + + super(URL.createObjectURL(blob)); + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9912440 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "baseUrl": "./src", + "target": "es6", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": false, + "downlevelIteration": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": false, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": [ + "src", + "node_modules/@nitrots/nitro-renderer/src/**/*.ts", + ] +} diff --git a/vite.config.mjs b/vite.config.mjs new file mode 100644 index 0000000..7d6d25d --- /dev/null +++ b/vite.config.mjs @@ -0,0 +1,32 @@ +// vite.config.js +import react from '@vitejs/plugin-react'; +import { resolve } from 'path'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [ react() ], + resolve: { + alias: { + '@': resolve(__dirname, 'src'), + '~': resolve(__dirname, 'node_modules') + } + }, + build: { + assetsInlineLimit: 102400, + chunkSizeWarningLimit: 200000, + rollupOptions: { + output: { + assetFileNames: 'src/assets/[name].[ext]', + manualChunks: id => + { + if(id.includes('node_modules')) + { + if(id.includes('@nitrots/nitro-renderer')) return 'nitro-renderer'; + + return 'vendor'; + } + } + } + } + } +}) diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..c54b602 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,3068 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.1", "@babel/code-frame@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" + integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== + dependencies: + "@babel/highlight" "^7.24.2" + picocolors "^1.0.0" + +"@babel/compat-data@^7.23.5": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.1.tgz#31c1f66435f2a9c329bb5716a6d6186c516c3742" + integrity sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA== + +"@babel/core@^7.23.5": + version "7.24.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.3.tgz#568864247ea10fbd4eff04dda1e05f9e2ea985c3" + integrity sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.2" + "@babel/generator" "^7.24.1" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.24.1" + "@babel/parser" "^7.24.1" + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.1" + "@babel/types" "^7.24.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.1.tgz#e67e06f68568a4ebf194d1c6014235344f0476d0" + integrity sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A== + dependencies: + "@babel/types" "^7.24.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== + dependencies: + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-module-imports@^7.22.15": + version "7.24.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128" + integrity sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg== + dependencies: + "@babel/types" "^7.24.0" + +"@babel/helper-module-transforms@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" + integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/helper-plugin-utils@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz#945681931a52f15ce879fd5b86ce2dae6d3d7f2a" + integrity sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w== + +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-string-parser@^7.23.4": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" + integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/helper-validator-option@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== + +"@babel/helpers@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.1.tgz#183e44714b9eba36c3038e442516587b1e0a1a94" + integrity sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg== + dependencies: + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.1" + "@babel/types" "^7.24.0" + +"@babel/highlight@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" + integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.24.0", "@babel/parser@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.1.tgz#1e416d3627393fab1cb5b0f2f1796a100ae9133a" + integrity sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg== + +"@babel/plugin-transform-react-jsx-self@^7.23.3": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.1.tgz#a21d866d8167e752c6a7c4555dba8afcdfce6268" + integrity sha512-kDJgnPujTmAZ/9q2CN4m2/lRsUUPDvsG3+tSHWUJIzMGTt5U/b/fwWd3RO3n+5mjLrsBrVa5eKFRVSQbi3dF1w== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-react-jsx-source@^7.23.3": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.1.tgz#a2dedb12b09532846721b5df99e52ef8dc3351d0" + integrity sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.1.tgz#431f9a794d173b53720e69a6464abc6f0e2a5c57" + integrity sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/template@^7.22.15", "@babel/template@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" + integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + +"@babel/traverse@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.1.tgz#d65c36ac9dd17282175d1e4a3c49d5b7988f530c" + integrity sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ== + dependencies: + "@babel/code-frame" "^7.24.1" + "@babel/generator" "^7.24.1" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.24.1" + "@babel/types" "^7.24.0" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" + integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + +"@esbuild/aix-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537" + integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g== + +"@esbuild/android-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9" + integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg== + +"@esbuild/android-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995" + integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w== + +"@esbuild/android-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98" + integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg== + +"@esbuild/darwin-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb" + integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA== + +"@esbuild/darwin-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0" + integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA== + +"@esbuild/freebsd-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911" + integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw== + +"@esbuild/freebsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c" + integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw== + +"@esbuild/linux-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5" + integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A== + +"@esbuild/linux-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c" + integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg== + +"@esbuild/linux-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa" + integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig== + +"@esbuild/linux-loong64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5" + integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ== + +"@esbuild/linux-mips64el@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa" + integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA== + +"@esbuild/linux-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20" + integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg== + +"@esbuild/linux-riscv64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300" + integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg== + +"@esbuild/linux-s390x@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685" + integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ== + +"@esbuild/linux-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff" + integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw== + +"@esbuild/netbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6" + integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ== + +"@esbuild/openbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf" + integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ== + +"@esbuild/sunos-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f" + integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w== + +"@esbuild/win32-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90" + integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ== + +"@esbuild/win32-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23" + integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ== + +"@esbuild/win32-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc" + integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ== + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== + +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== + dependencies: + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@popperjs/core@^2.11.6": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + +"@react-aria/ssr@^3.5.0": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.9.2.tgz#01b756965cd6e32b95217f968f513eb3bd6ee44b" + integrity sha512-0gKkgDYdnq1w+ey8KzG9l+H5Z821qh9vVjztk55rUg71vTk/Eaebeir+WtzcLLwTjw3m/asIjx8Y59y1lJZhBw== + dependencies: + "@swc/helpers" "^0.5.0" + +"@restart/hooks@^0.4.9": + version "0.4.16" + resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.16.tgz#95ae8ac1cc7e2bd4fed5e39800ff85604c6d59fb" + integrity sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w== + dependencies: + dequal "^2.0.3" + +"@restart/ui@^1.6.8": + version "1.6.8" + resolved "https://registry.yarnpkg.com/@restart/ui/-/ui-1.6.8.tgz#61b73503d4690e2f0f58992d4d6ae1e89c276791" + integrity sha512-6ndCv3oZ7r9vuP1Ok9KH55TM1/UkdBnP/fSraW0DFDMbPMzWKhVKeFAIEUCRCSdzayjZDcFYK6xbMlipN9dmMA== + dependencies: + "@babel/runtime" "^7.21.0" + "@popperjs/core" "^2.11.6" + "@react-aria/ssr" "^3.5.0" + "@restart/hooks" "^0.4.9" + "@types/warning" "^3.0.0" + dequal "^2.0.3" + dom-helpers "^5.2.0" + uncontrollable "^8.0.1" + warning "^4.0.3" + +"@rollup/rollup-android-arm-eabi@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.2.tgz#fbf098f49d96a8cac9056f22f5fd80906ef3af85" + integrity sha512-3XFIDKWMFZrMnao1mJhnOT1h2g0169Os848NhhmGweEcfJ4rCi+3yMCOLG4zA61rbJdkcrM/DjVZm9Hg5p5w7g== + +"@rollup/rollup-android-arm64@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.2.tgz#0d2448251040fce19a98eee505dff5b3c8ec9b98" + integrity sha512-GdxxXbAuM7Y/YQM9/TwwP+L0omeE/lJAR1J+olu36c3LqqZEBdsIWeQ91KBe6nxwOnb06Xh7JS2U5ooWU5/LgQ== + +"@rollup/rollup-darwin-arm64@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.2.tgz#78db4d4da5b1b84c22adbe25c8a4961b3f22d3af" + integrity sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA== + +"@rollup/rollup-darwin-x64@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.2.tgz#fcc05af54379f8ee5c7e954987d4514c6fd0fb42" + integrity sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A== + +"@rollup/rollup-linux-arm-gnueabihf@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.2.tgz#2ce200efa1ef4a56ee2af7b453edc74a259d7d31" + integrity sha512-GYbLs5ErswU/Xs7aGXqzc3RrdEjKdmoCrgzhJWyFL0r5fL3qd1NPcDKDowDnmcoSiGJeU68/Vy+OMUluRxPiLQ== + +"@rollup/rollup-linux-arm64-gnu@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.2.tgz#5a24aac882bff9abfda3f45f6f1db2166c342a4a" + integrity sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ== + +"@rollup/rollup-linux-arm64-musl@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.2.tgz#f1fb4c6f961d3f3397231a99e621d199200e4ea9" + integrity sha512-tK5eoKFkXdz6vjfkSTCupUzCo40xueTOiOO6PeEIadlNBkadH1wNOH8ILCPIl8by/Gmb5AGAeQOFeLev7iZDOA== + +"@rollup/rollup-linux-powerpc64le-gnu@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.13.2.tgz#46b2463d94ac3af3e0f7a2947b695397bc13b755" + integrity sha512-zvXvAUGGEYi6tYhcDmb9wlOckVbuD+7z3mzInCSTACJ4DQrdSLPNUeDIcAQW39M3q6PDquqLWu7pnO39uSMRzQ== + +"@rollup/rollup-linux-riscv64-gnu@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.2.tgz#47b932ee59a5395a3a341b0493e361d9e6032cf2" + integrity sha512-C3GSKvMtdudHCN5HdmAMSRYR2kkhgdOfye4w0xzyii7lebVr4riCgmM6lRiSCnJn2w1Xz7ZZzHKuLrjx5620kw== + +"@rollup/rollup-linux-s390x-gnu@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.2.tgz#8e14a1b3c3b9a4440c70a9c1ba12d32aa21f9712" + integrity sha512-l4U0KDFwzD36j7HdfJ5/TveEQ1fUTjFFQP5qIt9gBqBgu1G8/kCaq5Ok05kd5TG9F8Lltf3MoYsUMw3rNlJ0Yg== + +"@rollup/rollup-linux-x64-gnu@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.2.tgz#270e939194b66df77bcb33dd9a5ddf7784bd7997" + integrity sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A== + +"@rollup/rollup-linux-x64-musl@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.2.tgz#e8dd0f3c2046acbda2934490b36552e856a3bc6a" + integrity sha512-M/JYAWickafUijWPai4ehrjzVPKRCyDb1SLuO+ZyPfoXgeCEAlgPkNXewFZx0zcnoIe3ay4UjXIMdXQXOZXWqA== + +"@rollup/rollup-win32-arm64-msvc@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.2.tgz#f8b65a4a7e7a6b383e7b14439129b2f474ff123c" + integrity sha512-2YWwoVg9KRkIKaXSh0mz3NmfurpmYoBBTAXA9qt7VXk0Xy12PoOP40EFuau+ajgALbbhi4uTj3tSG3tVseCjuA== + +"@rollup/rollup-win32-ia32-msvc@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.2.tgz#bc1c5a4fbc4337d6cb15da80a4de95fd53ab3573" + integrity sha512-2FSsE9aQ6OWD20E498NYKEQLneShWes0NGMPQwxWOdws35qQXH+FplabOSP5zEe1pVjurSDOGEVCE2agFwSEsw== + +"@rollup/rollup-win32-x64-msvc@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.2.tgz#851959c4c1c3c6647aba1f388198c8243aed6917" + integrity sha512-7h7J2nokcdPePdKykd8wtc8QqqkqxIrUz7MHj6aNr8waBRU//NLDVnNjQnqQO6fqtjrtCdftpbTuOKAyrAQETQ== + +"@swc/helpers@^0.5.0": + version "0.5.8" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.8.tgz#65d56b1961487fd99795ffd8c68edb7a591571fb" + integrity sha512-lruDGw3pnfM3wmZHeW7JuhkGQaJjPyiKjxeGhdmfoOT53Ic9qb5JLDNaK2HUdl1zLDeX28H221UvKjfdvSLVMg== + dependencies: + tslib "^2.4.0" + +"@tanstack/react-virtual@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.2.0.tgz#fb70f9c6baee753a5a0f7618ac886205d5a02af9" + integrity sha512-OEdMByf2hEfDa6XDbGlZN8qO6bTjlNKqjM3im9JG+u3mCL8jALy0T/67oDI001raUUPh1Bdmfn4ZvPOV5knpcg== + dependencies: + "@tanstack/virtual-core" "3.2.0" + +"@tanstack/virtual-core@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.2.0.tgz#874d36135e4badce2719e7bdc556ce240cbaff14" + integrity sha512-P5XgYoAw/vfW65byBbJQCw+cagdXDT/qH6wmABiLt4v4YBT2q2vqCOhihe+D1Nt325F/S/0Tkv6C5z0Lv+VBQQ== + +"@types/babel__core@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.5.tgz#7b7502be0aa80cc4ef22978846b983edaafcd4dd" + integrity sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ== + dependencies: + "@babel/types" "^7.20.7" + +"@types/estree@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + +"@types/json-schema@^7.0.12": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + +"@types/node@^20.11.30": + version "20.12.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.2.tgz#9facdd11102f38b21b4ebedd9d7999663343d72e" + integrity sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ== + dependencies: + undici-types "~5.26.4" + +"@types/prop-types@*": + version "15.7.12" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" + integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== + +"@types/react-dom@^18.2.22": + version "18.2.23" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.23.tgz#112338760f622a16d64271b408355f2f27f6302c" + integrity sha512-ZQ71wgGOTmDYpnav2knkjr3qXdAFu0vsk8Ci5w3pGAIdj7/kKAyn+VsQDhXsmzzzepAiI9leWMmubXz690AI/A== + dependencies: + "@types/react" "*" + +"@types/react-slider@^1.3.6": + version "1.3.6" + resolved "https://registry.yarnpkg.com/@types/react-slider/-/react-slider-1.3.6.tgz#6f5602be93ab1cb3d273428c87aa227ad2ff68ff" + integrity sha512-RS8XN5O159YQ6tu3tGZIQz1/9StMLTg/FCIPxwqh2gwVixJnlfIodtVx+fpXVMZHe7A58lAX1Q4XTgAGOQaCQg== + dependencies: + "@types/react" "*" + +"@types/react-transition-group@^4.4.6": + version "4.4.10" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.10.tgz#6ee71127bdab1f18f11ad8fb3322c6da27c327ac" + integrity sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@>=16.9.11", "@types/react@^18.2.67": + version "18.2.73" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.73.tgz#0579548ad122660d99e00499d22e33b81e73ed94" + integrity sha512-XcGdod0Jjv84HOC7N5ziY3x+qL0AfmubvKOZ9hJjJ2yd5EE+KYjWhdOjt387e9HPheHkdggF9atTifMRtyAaRA== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + +"@types/semver@^7.5.0": + version "7.5.8" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" + integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== + +"@types/warning@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.3.tgz#d1884c8cc4a426d1ac117ca2611bf333834c6798" + integrity sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q== + +"@typescript-eslint/eslint-plugin@^7.3.1": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.5.0.tgz#1dc52fe48454d5b54be2d5f089680452f1628a5a" + integrity sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ== + dependencies: + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "7.5.0" + "@typescript-eslint/type-utils" "7.5.0" + "@typescript-eslint/utils" "7.5.0" + "@typescript-eslint/visitor-keys" "7.5.0" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/parser@^7.3.1": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.5.0.tgz#1eeff36309ac2253c905dd4a88b4b71b72a358ed" + integrity sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ== + dependencies: + "@typescript-eslint/scope-manager" "7.5.0" + "@typescript-eslint/types" "7.5.0" + "@typescript-eslint/typescript-estree" "7.5.0" + "@typescript-eslint/visitor-keys" "7.5.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.5.0.tgz#70f0a7361430ab1043a5f97386da2a0d8b2f4d56" + integrity sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA== + dependencies: + "@typescript-eslint/types" "7.5.0" + "@typescript-eslint/visitor-keys" "7.5.0" + +"@typescript-eslint/type-utils@7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.5.0.tgz#a8faa403232da3a3901655387c7082111f692cf9" + integrity sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw== + dependencies: + "@typescript-eslint/typescript-estree" "7.5.0" + "@typescript-eslint/utils" "7.5.0" + debug "^4.3.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/types@7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.5.0.tgz#0a284bcdef3cb850ec9fd57992df9f29d6bde1bc" + integrity sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg== + +"@typescript-eslint/typescript-estree@7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.5.0.tgz#aa5031c511874420f6b5edd90f8e4021525ee776" + integrity sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ== + dependencies: + "@typescript-eslint/types" "7.5.0" + "@typescript-eslint/visitor-keys" "7.5.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "9.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.5.0.tgz#bbd963647fbbe9ffea033f42c0fb7e89bb19c858" + integrity sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "7.5.0" + "@typescript-eslint/types" "7.5.0" + "@typescript-eslint/typescript-estree" "7.5.0" + semver "^7.5.4" + +"@typescript-eslint/visitor-keys@7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.5.0.tgz#8abcac66f93ef20b093e87a400c2d21e3a6d55ee" + integrity sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA== + dependencies: + "@typescript-eslint/types" "7.5.0" + eslint-visitor-keys "^3.4.1" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +"@vitejs/plugin-react@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz#744d8e4fcb120fc3dbaa471dadd3483f5a304bb9" + integrity sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ== + dependencies: + "@babel/core" "^7.23.5" + "@babel/plugin-transform-react-jsx-self" "^7.23.3" + "@babel/plugin-transform-react-jsx-source" "^7.23.3" + "@types/babel__core" "^7.20.5" + react-refresh "^0.14.0" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.9.0: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +aria-query@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" + integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== + dependencies: + dequal "^2.0.3" + +array-buffer-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" + integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== + dependencies: + call-bind "^1.0.5" + is-array-buffer "^3.0.4" + +array-includes@^3.1.6, array-includes@^3.1.7: + version "3.1.8" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" + integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + is-string "^1.0.7" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array.prototype.findlast@^1.2.4: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" + integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" + +array.prototype.findlastindex@^1.2.3: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d" + integrity sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" + +array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" + integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.toreversed@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz#b989a6bf35c4c5051e1dc0325151bf8088954eba" + integrity sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.tosorted@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz#c8c89348337e51b8a3c48a9227f9ce93ceedcba8" + integrity sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg== + dependencies: + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.1.0" + es-shim-unscopables "^1.0.2" + +arraybuffer.prototype.slice@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" + integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.2.1" + get-intrinsic "^1.2.3" + is-array-buffer "^3.0.4" + is-shared-array-buffer "^1.0.2" + +ast-types-flow@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" + integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== + +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + +axe-core@=4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf" + integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ== + +axobject-query@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" + integrity sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg== + dependencies: + dequal "^2.0.3" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.22.2: + version "4.23.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" + integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== + dependencies: + caniuse-lite "^1.0.30001587" + electron-to-chromium "^1.4.668" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + +call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +caniuse-lite@^1.0.30001587: + version "1.0.30001603" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001603.tgz#605046a5bdc95ba4a92496d67e062522dce43381" + integrity sha512-iL2iSS0eDILMb9n5yKQoTBim9jMZ0Yrk8g0N9K7UzYyWnfIKzXBZD5ngpM37ZcL/cv0Mli8XtVMRYMQAfFpi5Q== + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +"chokidar@>=3.0.0 <4.0.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +classnames@^2.3.2: + version "2.5.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +csstype@^3.0.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +damerau-levenshtein@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" + integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== + +data-view-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" + integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" + integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" + integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +debug@^2.6.6: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +dequal@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-helpers@^5.0.1, dom-helpers@^5.2.0, dom-helpers@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + +electron-to-chromium@^1.4.668: + version "1.4.723" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.723.tgz#827da30c96b316684d352c3d81430029df01bb8e" + integrity sha512-rxFVtrMGMFROr4qqU6n95rUi9IlfIm+lIAt+hOToy/9r6CDv0XiEcQdC3VP71y1pE5CFTzKV0RvxOGYCPWWHPw== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2: + version "1.23.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" + integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== + dependencies: + array-buffer-byte-length "^1.0.1" + arraybuffer.prototype.slice "^1.0.3" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + data-view-buffer "^1.0.1" + data-view-byte-length "^1.0.1" + data-view-byte-offset "^1.0.0" + es-define-property "^1.0.0" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-set-tostringtag "^2.0.3" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.4" + get-symbol-description "^1.0.2" + globalthis "^1.0.3" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + hasown "^2.0.2" + internal-slot "^1.0.7" + is-array-buffer "^3.0.4" + is-callable "^1.2.7" + is-data-view "^1.0.1" + is-negative-zero "^2.0.3" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.3" + is-string "^1.0.7" + is-typed-array "^1.1.13" + is-weakref "^1.0.2" + object-inspect "^1.13.1" + object-keys "^1.1.1" + object.assign "^4.1.5" + regexp.prototype.flags "^1.5.2" + safe-array-concat "^1.1.2" + safe-regex-test "^1.0.3" + string.prototype.trim "^1.2.9" + string.prototype.trimend "^1.0.8" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.2" + typed-array-byte-length "^1.0.1" + typed-array-byte-offset "^1.0.2" + typed-array-length "^1.0.6" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.15" + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.1.0, es-errors@^1.2.1, es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-iterator-helpers@^1.0.15, es-iterator-helpers@^1.0.17: + version "1.0.18" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz#4d3424f46b24df38d064af6fbbc89274e29ea69d" + integrity sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-errors "^1.3.0" + es-set-tostringtag "^2.0.3" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + globalthis "^1.0.3" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + internal-slot "^1.0.7" + iterator.prototype "^1.1.2" + safe-array-concat "^1.1.2" + +es-object-atoms@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" + integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" + integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== + dependencies: + get-intrinsic "^1.2.4" + has-tostringtag "^1.0.2" + hasown "^2.0.1" + +es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" + integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== + dependencies: + hasown "^2.0.0" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +esbuild@^0.20.1: + version "0.20.2" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1" + integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g== + optionalDependencies: + "@esbuild/aix-ppc64" "0.20.2" + "@esbuild/android-arm" "0.20.2" + "@esbuild/android-arm64" "0.20.2" + "@esbuild/android-x64" "0.20.2" + "@esbuild/darwin-arm64" "0.20.2" + "@esbuild/darwin-x64" "0.20.2" + "@esbuild/freebsd-arm64" "0.20.2" + "@esbuild/freebsd-x64" "0.20.2" + "@esbuild/linux-arm" "0.20.2" + "@esbuild/linux-arm64" "0.20.2" + "@esbuild/linux-ia32" "0.20.2" + "@esbuild/linux-loong64" "0.20.2" + "@esbuild/linux-mips64el" "0.20.2" + "@esbuild/linux-ppc64" "0.20.2" + "@esbuild/linux-riscv64" "0.20.2" + "@esbuild/linux-s390x" "0.20.2" + "@esbuild/linux-x64" "0.20.2" + "@esbuild/netbsd-x64" "0.20.2" + "@esbuild/openbsd-x64" "0.20.2" + "@esbuild/sunos-x64" "0.20.2" + "@esbuild/win32-arm64" "0.20.2" + "@esbuild/win32-ia32" "0.20.2" + "@esbuild/win32-x64" "0.20.2" + +escalade@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-import-resolver-node@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== + dependencies: + debug "^3.2.7" + is-core-module "^2.13.0" + resolve "^1.22.4" + +eslint-module-utils@^2.8.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz#52f2404300c3bd33deece9d7372fb337cc1d7c34" + integrity sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q== + dependencies: + debug "^3.2.7" + +eslint-plugin-import@^2.29.1: + version "2.29.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz#d45b37b5ef5901d639c15270d74d46d161150643" + integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw== + dependencies: + array-includes "^3.1.7" + array.prototype.findlastindex "^1.2.3" + array.prototype.flat "^1.3.2" + array.prototype.flatmap "^1.3.2" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.9" + eslint-module-utils "^2.8.0" + hasown "^2.0.0" + is-core-module "^2.13.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.fromentries "^2.0.7" + object.groupby "^1.0.1" + object.values "^1.1.7" + semver "^6.3.1" + tsconfig-paths "^3.15.0" + +eslint-plugin-jsx-a11y@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz#2fa9c701d44fcd722b7c771ec322432857fcbad2" + integrity sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA== + dependencies: + "@babel/runtime" "^7.23.2" + aria-query "^5.3.0" + array-includes "^3.1.7" + array.prototype.flatmap "^1.3.2" + ast-types-flow "^0.0.8" + axe-core "=4.7.0" + axobject-query "^3.2.1" + damerau-levenshtein "^1.0.8" + emoji-regex "^9.2.2" + es-iterator-helpers "^1.0.15" + hasown "^2.0.0" + jsx-ast-utils "^3.3.5" + language-tags "^1.0.9" + minimatch "^3.1.2" + object.entries "^1.1.7" + object.fromentries "^2.0.7" + +eslint-plugin-react-hooks@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" + integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== + +eslint-plugin-react@^7.34.1: + version "7.34.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz#6806b70c97796f5bbfb235a5d3379ece5f4da997" + integrity sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw== + dependencies: + array-includes "^3.1.7" + array.prototype.findlast "^1.2.4" + array.prototype.flatmap "^1.3.2" + array.prototype.toreversed "^1.1.2" + array.prototype.tosorted "^1.1.3" + doctrine "^2.1.0" + es-iterator-helpers "^1.0.17" + estraverse "^5.3.0" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.1.2" + object.entries "^1.1.7" + object.fromentries "^2.0.7" + object.hasown "^1.1.3" + object.values "^1.1.7" + prop-types "^15.8.1" + resolve "^2.0.0-next.5" + semver "^6.3.1" + string.prototype.matchall "^4.0.10" + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.57.0: + version "8.57.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" + integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.0" + "@humanwhocodes/config-array" "^0.11.14" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fast-deep-equal@3.1.3, fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +function.prototype.name@^1.1.5, function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +get-symbol-description@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" + integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== + dependencies: + call-bind "^1.0.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globalthis@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1, has-proto@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +ignore@^5.2.0, ignore@^5.2.4: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + +immutable@^4.0.0: + version "4.3.5" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.5.tgz#f8b436e66d59f99760dc577f5c99a4fd2a5cc5a0" + integrity sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +internal-slot@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" + integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.0" + side-channel "^1.0.4" + +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +is-array-buffer@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" + integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + +is-async-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" + integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== + dependencies: + has-tostringtag "^1.0.0" + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-core-module@^2.13.0, is-core-module@^2.13.1: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-data-view@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" + integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== + dependencies: + is-typed-array "^1.1.13" + +is-date-object@^1.0.1, is-date-object@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-finalizationregistry@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6" + integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== + dependencies: + call-bind "^1.0.2" + +is-generator-function@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== + +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== + +is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" + integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== + dependencies: + call-bind "^1.0.7" + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" + integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== + dependencies: + which-typed-array "^1.1.14" + +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +is-weakset@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" + integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +iterator.prototype@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" + integrity sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w== + dependencies: + define-properties "^1.2.1" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + reflect.getprototypeof "^1.0.4" + set-function-name "^2.0.1" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5: + version "3.3.5" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" + integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== + dependencies: + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + object.assign "^4.1.4" + object.values "^1.1.6" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +language-subtag-registry@^0.3.20: + version "0.3.22" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" + integrity sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w== + +language-tags@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.9.tgz#1ffdcd0ec0fafb4b1be7f8b11f306ad0f9c08777" + integrity sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA== + dependencies: + language-subtag-registry "^0.3.20" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +load-script@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/load-script/-/load-script-1.0.0.tgz#0491939e0bee5643ee494a7e3da3d2bac70c6ca4" + integrity sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA== + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +minimatch@9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.4, object.assign@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" + integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== + dependencies: + call-bind "^1.0.5" + define-properties "^1.2.1" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.entries@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" + integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +object.fromentries@^2.0.7: + version "2.0.8" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" + integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + +object.groupby@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" + integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + +object.hasown@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.4.tgz#e270ae377e4c120cdcb7656ce66884a6218283dc" + integrity sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg== + dependencies: + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + +object.values@^1.1.6, object.values@^1.1.7: + version "1.2.0" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" + integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +possible-typed-array-names@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" + integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== + +postcss@^8.4.38: + version "8.4.38" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" + integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.2.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prop-types-extra@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/prop-types-extra/-/prop-types-extra-1.1.1.tgz#58c3b74cbfbb95d304625975aa2f0848329a010b" + integrity sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew== + dependencies: + react-is "^16.3.2" + warning "^4.0.0" + +prop-types@15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +prop-types@^15.6.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +react-bootstrap@^2.2.2: + version "2.10.2" + resolved "https://registry.yarnpkg.com/react-bootstrap/-/react-bootstrap-2.10.2.tgz#3b609eb0170e31b3d9ace297d3a016c202a42642" + integrity sha512-UvB7mRqQjivdZNxJNEA2yOQRB7L9N43nBnKc33K47+cH90/ujmnMwatTCwQLu83gLhrzAl8fsa6Lqig/KLghaA== + dependencies: + "@babel/runtime" "^7.22.5" + "@restart/hooks" "^0.4.9" + "@restart/ui" "^1.6.8" + "@types/react-transition-group" "^4.4.6" + classnames "^2.3.2" + dom-helpers "^5.2.1" + invariant "^2.2.4" + prop-types "^15.8.1" + prop-types-extra "^1.1.0" + react-transition-group "^4.4.5" + uncontrollable "^7.2.1" + warning "^4.0.3" + +react-dom@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.0" + +react-icons@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.0.1.tgz#1694e11bfa2a2888cab47dcc30154ce90485feee" + integrity sha512-WqLZJ4bLzlhmsvme6iFdgO8gfZP17rfjYEJ2m9RsZjZ+cc4k1hTzknEz63YS1MeT50kVzoa1Nz36f4BEx+Wigw== + +react-is@^16.13.1, react-is@^16.3.2, react-is@^16.8.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + +react-refresh@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" + integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== + +react-slider@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/react-slider/-/react-slider-2.0.6.tgz#8c7ff0301211f7c3ff32aa0163b33bdab6258559" + integrity sha512-gJxG1HwmuMTJ+oWIRCmVWvgwotNCbByTwRkFZC6U4MBsHqJBmxwbYRJUmxy4Tke1ef8r9jfXjgkmY/uHOCEvbA== + dependencies: + prop-types "^15.8.1" + +react-transition-group@^4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + +react-youtube@^7.13.1: + version "7.14.0" + resolved "https://registry.yarnpkg.com/react-youtube/-/react-youtube-7.14.0.tgz#0505d86491521ca94ef0afb74af3f7936dc7bc86" + integrity sha512-SUHZ4F4pd1EHmQu0CV0KSQvAs5KHOT5cfYaq4WLCcDbU8fBo1ouTXaAOIASWbrz8fHwg+G1evfoSIYpV2AwSAg== + dependencies: + fast-deep-equal "3.1.3" + prop-types "15.7.2" + youtube-player "5.5.2" + +react@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== + dependencies: + loose-envify "^1.1.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +reflect.getprototypeof@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" + integrity sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.1" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + globalthis "^1.0.3" + which-builtin-type "^1.1.3" + +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + +regexp.prototype.flags@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" + integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== + dependencies: + call-bind "^1.0.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.22.4: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^2.0.0-next.5: + version "2.0.0-next.5" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" + integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rollup@^4.13.0: + version "4.13.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.13.2.tgz#ac57d2dc48e8f5562f5a6daadb9caee590069262" + integrity sha512-MIlLgsdMprDBXC+4hsPgzWUasLO9CE4zOkj/u6j+Z6j5A4zRY+CtiXAdJyPtgCsc42g658Aeh1DlrdVEJhsL2g== + dependencies: + "@types/estree" "1.0.5" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.13.2" + "@rollup/rollup-android-arm64" "4.13.2" + "@rollup/rollup-darwin-arm64" "4.13.2" + "@rollup/rollup-darwin-x64" "4.13.2" + "@rollup/rollup-linux-arm-gnueabihf" "4.13.2" + "@rollup/rollup-linux-arm64-gnu" "4.13.2" + "@rollup/rollup-linux-arm64-musl" "4.13.2" + "@rollup/rollup-linux-powerpc64le-gnu" "4.13.2" + "@rollup/rollup-linux-riscv64-gnu" "4.13.2" + "@rollup/rollup-linux-s390x-gnu" "4.13.2" + "@rollup/rollup-linux-x64-gnu" "4.13.2" + "@rollup/rollup-linux-x64-musl" "4.13.2" + "@rollup/rollup-win32-arm64-msvc" "4.13.2" + "@rollup/rollup-win32-ia32-msvc" "4.13.2" + "@rollup/rollup-win32-x64-msvc" "4.13.2" + fsevents "~2.3.2" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-array-concat@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" + integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + has-symbols "^1.0.3" + isarray "^2.0.5" + +safe-regex-test@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" + integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-regex "^1.1.4" + +sass@^1.72.0: + version "1.72.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.72.0.tgz#5b9978943fcfb32b25a6a5acb102fc9dabbbf41c" + integrity sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA== + dependencies: + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" + +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== + dependencies: + loose-envify "^1.1.0" + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.4: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.1, set-function-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.4, side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +sister@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/sister/-/sister-3.0.2.tgz#bb3e39f07b1f75bbe1945f29a27ff1e5a2f26be4" + integrity sha512-p19rtTs+NksBRKW9qn0UhZ8/TUI9BPw9lmtHny+Y3TinWlOa9jWh9xB0AtPSdmOy49NJJJSSe0Ey4C7h0TrcYA== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + +string.prototype.matchall@^4.0.10: + version "4.0.11" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" + integrity sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-symbols "^1.0.3" + internal-slot "^1.0.7" + regexp.prototype.flags "^1.5.2" + set-function-name "^2.0.2" + side-channel "^1.0.6" + +string.prototype.trim@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" + integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-object-atoms "^1.0.0" + +string.prototype.trimend@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" + integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-api-utils@^1.0.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + +tsconfig-paths@^3.15.0: + version "3.15.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" + integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^2.4.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +typed-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" + integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-typed-array "^1.1.13" + +typed-array-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" + integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-byte-offset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" + integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-length@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" + integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + +typescript@^5.4.2: + version "5.4.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.3.tgz#5c6fedd4c87bee01cd7a528a30145521f8e0feff" + integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +uncontrollable@^7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-7.2.1.tgz#1fa70ba0c57a14d5f78905d533cf63916dc75738" + integrity sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ== + dependencies: + "@babel/runtime" "^7.6.3" + "@types/react" ">=16.9.11" + invariant "^2.2.4" + react-lifecycles-compat "^3.0.4" + +uncontrollable@^8.0.1: + version "8.0.4" + resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-8.0.4.tgz#a0a8307f638795162fafd0550f4a1efa0f8c5eb6" + integrity sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +use-between@^1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/use-between/-/use-between-1.3.5.tgz#8f9db513414d204e0046c5692828e209cec4d564" + integrity sha512-IP9eJfszZr0aah/6i/pzaM7n/QgMPwWKJ+mnWqT5O0qFhLnztPbkVC6L7zI6ygeBIMJHfmUGvsw0b28pyrEGSA== + +vite@^5.1.6: + version "5.2.7" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.7.tgz#e1b8a985eb54fcb9467d7f7f009d87485016df6e" + integrity sha512-k14PWOKLI6pMaSzAuGtT+Cf0YmIx12z9YGon39onaJNy8DLBfBJrzg9FQEmkAM5lpHBZs9wksWAsyF/HkpEwJA== + dependencies: + esbuild "^0.20.1" + postcss "^8.4.38" + rollup "^4.13.0" + optionalDependencies: + fsevents "~2.3.3" + +warning@^4.0.0, warning@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-builtin-type@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.3.tgz#b1b8443707cc58b6e9bf98d32110ff0c2cbd029b" + integrity sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw== + dependencies: + function.prototype.name "^1.1.5" + has-tostringtag "^1.0.0" + is-async-function "^2.0.0" + is-date-object "^1.0.5" + is-finalizationregistry "^1.0.2" + is-generator-function "^1.0.10" + is-regex "^1.1.4" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.9" + +which-collection@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + +which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.9: + version "1.1.15" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" + integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.2" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +youtube-player@5.5.2: + version "5.5.2" + resolved "https://registry.yarnpkg.com/youtube-player/-/youtube-player-5.5.2.tgz#052b86b1eabe21ff331095ffffeae285fa7f7cb5" + integrity sha512-ZGtsemSpXnDky2AUYWgxjaopgB+shFHgXVpiJFeNB5nWEugpW1KWYDaHKuLqh2b67r24GtP6HoSW5swvf0fFIQ== + dependencies: + debug "^2.6.6" + load-script "^1.0.0" + sister "^3.0.0"

=}Y{jUbKv|>{iBlCS#_r4Mx0C{4rZ{ zKTWDhsz@rgxHP)7>}}ax8LiE}%>X*7^rrQbDgzsN>*Z4FuW98rWjWWjthueRrRpWo zW!Ms5wDy}2`?FX|`%h+{ERkH8sZbF8~pmacHWRWNIob-&?N zmX@A|DK{!dl^brgD^+b5=e%v#Z?!)OWzgw05xpU5h)p6;J6tkOasvTBI zm3b9$=eOzwe)K8c$;kb16PIh|TRiuuef=-b6X`SbnfKq~Lqj?YWj&oRC6SK6snMk{ zvCSv)x~hE<#^$iUi%GP40;%18!i}KtF?5CPvjh`D!n=aL?exoE-HF`Hb zamKoEV4J$DKJBEmr#xwS^6X5LiavUZp`7 z@ZoRr%Z)4pSGsQJSZ9AR#ByMp?}zWl#K)S34M?SHNo8~Q%=Wdfm;@a9J1z!|9p+@$ zXLoLw5UY2Tc5d%P&%7Q~czq?;UjF{br#|EYYIRhb_I0X^*L+2YCDJk!+psPq?JAvY zS2t(HF4dpqxIs03K%J`4JNV_QQM86me*Qji|V&Lcu^8UQuU%fQFxooVgrE0aL ziK4hxnbaMPY2_A$7VRD>5BtG!Ob*8Qt|^bIlz5er&)pC6r`!9{OF2ul%fTEO(tVD8 zkMaqf2a(0>rBciiYH0|w7z>q`KXu^ZCF}{8_gDfiC(=CXGL)-db8PVwOsbBfBC)TG}YQ=!(Svn_tz~GFi&a3 zZerIw{vD`rkE}cI_K9zj=1Zme>{b7~g2G;srcw9pcl33!NpY$=th=4Z$D!DM)wJ^5 zt3%s^_5H->%CB}`leU>VnHOI)7DbubN%CUW+AHp;)@k@ZBGN3gQCy9(rL53+AJ-`E zJ#JaPZI@mZ(k>EY?iG5oqd%;p0phhqYeTad`DANma@)}oBW*w5;QTi#hg*v+Hm-`5 z$dS(W^qbhs!&UBR-~Nx8GcVtjmUlI~4SS4vY^=>m<~lH>m8GwxGx*R?O~0d`w|%#L z_*c;Dt;{FyMMu8n?!K0yCPEA5QUHaJfn`;6{z2e~wMHj4=YmcRiT^OZQqZt}WU`O`e0zE$>`MxEZ#W?*HRnl) z)SnqAVnJi@x+`J+4x{gG3p`+POrI3LuHL3jxUu&>V6Tq2%3}B4E~{GYmiK1Brp9^7 z7t7w(SFKr9%{RQ7cY@j*f<2BlxO0^Wvj^>uezB7##&cCQXWRFKe2==14(HitL+**5 z5e8bP0!4Q!+br8|9U!)KrjWDIkZh7X@7A0{y(DUvdi z2BPSrwsKXpR8^O>@11R&79)qyhG|A_)szX0NbDrJC`h4qE`FYI6c@y$m7$O?@0bkGl{ zyQYba<@|IMBSns%fZ$7K+PN2LPND7XUE0o zZM(uBCX0tb3vnu)^6WJD4n zECDlNjy|1nm^D9b)`T3;aYOw~R#_Of8CN$;1+_EP9xd!~ul?zZTIr)mT z2@K-2!p=(HBi;i|sABEHGe2@SXalve0hC{$#L~zGrxfdZr_!qhAHK({4&Egz@6gaL{!Dhs5Z z%f`k=u^D8bM$(uNVG%(RcgHvXEkpD@`+ENCsJNdVp~is=hBYTg*otivq*u8DIFijR zlbHP+D2IP4yXd{b^_rX}!71{r=#C*vWF-?qB0@em17kPAC5Wf3yNG}|$d^%opeSM* zN;_gG^Q`%}V?4=P*jjX8kUTNwL4%_yp*Rwr z<-gx&%?R#zx8n)TkQ$7*MxOA*>qcldH0(A7dkSom4Pz!80QbPvJI&~+fSs%dY>Q;j z{x!x37I@jrEw~Tj$7-mwAb>Gz!6I%3(=q4uQ3btzN)B-IPoK5%T2cRZs>b>jdR4kk GVgCo|iCsYe literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/models/model_z.png b/src/assets/images/navigator/models/model_z.png new file mode 100644 index 0000000000000000000000000000000000000000..0809c916b96f7c368f60dfd69728c693aa3bf1db GIT binary patch literal 4087 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 F;=e!=M=$^Y literal 0 HcmV?d00001 diff --git a/src/assets/images/navigator/thumbnail_placeholder.png b/src/assets/images/navigator/thumbnail_placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..be26f84767d2a6287b8ba3f23328b9ba6d4fbd6d GIT binary patch literal 1801 zcmV+k2ln`hP)g2qjnz!va1 zi&rtbrvN=&*+ZUjAtK!i%B6Cy$K4IJE>H1iLjd2d2}@+dIv`n_{;__5mLq z9WDO5^~@pDXaU{?yA`qz`0()11wv!A2Cs_U0yzZigB}9B8g@-&O9oq@TZ0#eu9eLe zV2p!HGVi z{l55N87a_bXJ@_OQ!aQ`R+m7w07LdtY|LgFfQ-=anhRY6JQ+I&IVOX7S0UA84h>NP zx-@t+bZR!o4060KYlGi?0$c+cBe#Y}XLT%cN*5v3WnV^1*fpR_44PG-t$T9Pj)#=p zp-#h?&4_JO5Br+}NwhlXqgrk#V9$)TMRUpbMW zHLz4@{+Sg#2(4vvs6=3!CY1%gD=jS}wRO^lfaP+bL!WD9$Oj&k)uC+0Rwz_cIT~N) z=JpF0A3u2^{yjIh|LM)+dx}Fs^R^KFJ<(a63vH>n0LX)`&*w@1OInr=ZNnr%hXz3o z9qJlx{sF7+K!F|x`%D)G^C6U)l{i$NZ1q==lkmUMS%8Zbu6@2 zlO=4r49{kY&r6Y%^(zIf^~;tvjsnuGrr5rky^r<}n{N<|3kz-C z8}yY9X@`T>U9B_W87sAZysn3!wN7CuXoqb%wxMOTY0v~Nem*NxED4?p|mi>Dn3{CU#Q+VNxlXWquq z8tlX7T?w{!2u*{7O){hD0VSzCz|+pRP&9bJY4_%eKho(~W+n;+-Ea>Icke*Nl?E)d*O zI!XNc;_(ySc1mD4$H5q zKKbj03+)&N1^)8i&s|)5u(SB!!JlSeTpp**%}p1jU}FyaSKDFnwR;Zv?~gj{^O=1f zmFDBheZ~$1mvP7l{rVNl$>uk2x>)SKB?iTY_xnFzTI~IFsRIUF zK&GgCBhjfecouZZ(YwQ~^*%Hk5PQ(e2my0!{uy8+Fh|v(W5GimO7uac+ynm<_~7BX z*jEGayWhRj0|qb%D>PjmKEM9-stb}qd5&sqfcO@K>*`E>8f9T($-raLE|(t*IfL5>H2(hTMAL%~6L220l{(%|4F zOGi;?c!h|m!BXI$XBi~Vl?iEm12PA<)bMO*(|H|Rq!vGtfh{#yD|C`(4^L2YX-x73drHQlA-;4rG#)`YnRgGOeOdM8(+tjh^UP9 ztna|JK~Bm3*cu)JF9I!{5P5#1No)~{TH5j+Ms8`^SZ z7XAZ{AHM=e^b}xPbHxp&Oi;6Px{8zx&c3KK=Q=~o@RO4h7nTgt2DK1a?)+TftZTw^ zE>Z3>{P_6QzzJQ703HGkEk8sJ9_97m6da%tedsw<~^0LoM1^ATMce@^Qzjn zW1|_4=$4XFt4*8yz3-$9xAfsHfhTo0$Jjx$74T&5Pbz`a3~vd%5_jC?B?Vqv0i~F00000NkvXXu0mjfBEV%1 literal 0 HcmV?d00001 diff --git a/src/assets/images/nitro/nitro-dark.svg b/src/assets/images/nitro/nitro-dark.svg new file mode 100644 index 0000000..20cc533 --- /dev/null +++ b/src/assets/images/nitro/nitro-dark.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + diff --git a/src/assets/images/nitro/nitro-light.svg b/src/assets/images/nitro/nitro-light.svg new file mode 100644 index 0000000..5706684 --- /dev/null +++ b/src/assets/images/nitro/nitro-light.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + diff --git a/src/assets/images/nitro/nitro-n-dark.svg b/src/assets/images/nitro/nitro-n-dark.svg new file mode 100644 index 0000000..f8d0ebd --- /dev/null +++ b/src/assets/images/nitro/nitro-n-dark.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + diff --git a/src/assets/images/nitro/nitro-n-light.svg b/src/assets/images/nitro/nitro-n-light.svg new file mode 100644 index 0000000..4dd94fc --- /dev/null +++ b/src/assets/images/nitro/nitro-n-light.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/src/assets/images/notifications/frank.gif b/src/assets/images/notifications/frank.gif new file mode 100644 index 0000000000000000000000000000000000000000..211634f74533f4dd21c474dbdbcde385ee3d675e GIT binary patch literal 1204 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 V(Y~5D$Q0iQK%-ocOnY4Xe*yh$$o>ET literal 0 HcmV?d00001 diff --git a/src/assets/images/pets/pet-package/pterosaur_egg.png b/src/assets/images/pets/pet-package/pterosaur_egg.png new file mode 100644 index 0000000000000000000000000000000000000000..43ee1418ab722ebea561ceb72fe7381d61408c7e GIT binary patch literal 1631 zcmb`H`#;kS6vsFBrZul}sYPy~+7MPtjc2oFr*+F(=c zyPnDkY5n_A>6=GaMju~$(adc~uruDh>%kih0Mdu57&dE`yTT)%xs}e-{th7*sRD3NrD`>E*9Ed*>d(H-6yGU!e zOdpG@DJNoB4lZOHGSxQyqC=UG5yrtEjz6^@9tdUgN#(Dy?9*Ju0PDc>%LI337epQ6ko zDpqF-4w8C5UjOy@k4A%|7W(X-Z%Yw28ZnnkVyA6wS4;ma8*@LqdvP(=OL@2?a2}W; z*JN=RembbYh_W#{+p^m*GE*2+4~ojsMKH~kim5~S|NNy=+1WT|pfO)D6QVkCDGht~ zPF}-GuuV3;=0WrDdVp#jc&=@P(VT!j#EtlQ${V2(UNgEuj5vZy_|si9J?DAP)raQQ zAbc>KHzzrC5cBrk0s*gI2hJkQEyGq-e|n&{u5tsAsy;d6V1m6JPUa`z?pP(xAi9Cvuqe%f!e~L`{W*TfE*t&29pW>NeX}V0CB+j2wn_%H zH}Ds4OJvDwVkKj?wO_U-PA+;S953?K6-C8&J)e3pd$E@zd1{?^3p%w@jH~=x6W#e_ zbja!ez*_Kxr(bVdI*I=R?A*L?l{5M3<=6+V8T1}vGO54UG`$;I%;j6oL?xi~Zde=xA2x+SD6zo%g&=xYfv|9rG}~x}qBosx?2dh@Pr{ ziPq+kk6C*^Ov39JWv@g0pXY2sZjCeTd1o6=tCo*-H90dp!;0DD<_%_I zJGzzpTuf&4ZG$B}#%-u)XyI{DI#8 z)kvUyd!p)|-T}?96wQY6<=hs_GeG@!<%uH0=bE3DzA}9@EGv=f!E^j2G8Qeh@y19O zk(m`ii8?2PWU?s$)y-HmqCBDl(G~?frM#Rs{BrS~?wY4DxF*&)i1jnTlM+wZlKk{` zhl2K}sQob!vj;BjE)-VACu`AO-DYij2m%mwO_62S*=3Mo<0I{gf~rLKn(*9Xk2f11 zotRhB*3?2`f#ls)#_rkc8BnbCfU-c6rBETr5c@YYL+DyP(~xiM;V^E8oOC%U(Iw-aY#8Ar4%!z@1x1^6>J<26862AFg(w@%{zE C?iq*x literal 0 HcmV?d00001 diff --git a/src/assets/images/pets/pet-package/val11_present.png b/src/assets/images/pets/pet-package/val11_present.png new file mode 100644 index 0000000000000000000000000000000000000000..3d371b5b3747985100af5278106f3c87b5462982 GIT binary patch literal 2720 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 N002ovPDHLkV1jkP!14e9 literal 0 HcmV?d00001 diff --git a/src/assets/images/prize/prize_background.png b/src/assets/images/prize/prize_background.png new file mode 100644 index 0000000000000000000000000000000000000000..ec9c0306536734ee07dc697eec63304e2c0721c6 GIT binary patch literal 5861 zcmVb^P)nj;8Ecx^DG}HpDAnJMN9f;L5&TCF3{MZ=8k{mA)V3%HcsekX<$2qrUtN>Xu z01LR442VTOr-hE?nff`cp?M^cXR-*=g%@7f8~_2?GA;mTx40jH^q?ij2`Q=R0S~ zNJ9?mr%*fCJX@0=Xf&$5mH_$jh?IJlLt{9vt`@Bh^6WpM?bnI z0OtXl5TrbT_tCb_ydNK*Tb_{YFYxtMJJtOAS~Dh|2V(l0M$h+&gC_7i3mczh$-tXo z!T)&HJO!bSrDGCoC$ixC@+8^T%YNCG>%{a^fD5tdr+zdh95Qnyn^7ekn`KzDjoEfA zo33g{q*1J84B|uv3PCDQX6cy1Vn6Hq=(B>WT5A9;7lL*w!+Y(t&)XWsoug*vG2^xv zN3Q157QoXV<4v_LA`wp}9aBN3!8s_?;e7+h3=w!=Zjk+Y4gzg}RqZX;C7M|q+(^Xm ztzP>EA)CSo20dnVaj#@O9#wnaWha+QDz(zS#&m9)5#uTNmk5PvBqUan7o ztYOkICLO?RIXgZwE?Y?mLq5LO3g}AtGm3nDGV#R> zBr*Nv{D&ftXd#(+vL*u2Fm>@`v9)F5EE~76M7i(8z`#Txvm_juFU;B`rhAQ;>-DTz z2nYcOAiZ@v$a;`FoO^o%$k#~7^xys}$enx)&m&N86-^Z|(bUm@qE_Pl=)UtM0+=O# ztO3$8HWH&DPW-KTj6LG~IIva8Xkj5}VY?*}(Ljpj<_UQ;31L|6TO;7D15myqAl>EM z_Pas8>D)Wt61kUU$GyBCkK=iGK7Nlh6*YJbU$6nn zEpb*eQl^e_OH*2k3kk-IDGr<|0ICVA2CiVDAk)2xP4lfUGZ@>yA%JakZYSJp2mDDp z9|U;_D1yHITdbLQJ-n_02}DJ10RKRWEf;b;>BFC?p@@+o8Cx-LsYWuz z;gE@8WhpMSn^IO;!Z+lUYwT zWj0gn?Q6{=ndT8`p)zuomD!Xz_nl(Wx!FP_i`k5jEz#MduOh!?0lc40bl0N*$|mRb z0zmr^u%9@$|5@h_Y>s9BX5RnqGYlx6Pn!CO(Mkbwn`$K7x93_3ftCqlHwusqlE+UD zBt!10##hL50Bjx>OKYV@nc0@~^GjKmOOarK#A;&3z1K5?k`G)j0B#fjL7VnIE?S4+ z{UZSO9Ngn)aKC>A`48s~qPzg|Z?OO8aQt~bkHGv9f1kASK>_k^G3~W{UkZU%NS4zi zIrm&CUMP^pH=?^T{$GE;_{8R66&vCK^ioC(ePvmLYRZR23o#udEnFpG%q_yecd-eh z<`95CV3XbdcMKi^cjy-&FM|Bqx&Gfe_ufmq?T62Y{uAVv4CsM>2Kjr|Uc3&v10tUL z*w^Cy@V>n_3S$?A}hFNGZEY%(p3j4%q!50!EFO^j6 z8rA}g1JO?r3nPh?*aS%f0f7AgMBl%##`XV-K|B0AkXN01|9?RKm$!%i8|1&>_{#v~ zulRiY-hKllx(4*I$n+Rzl7GC3&6>iVtJq3>$TKs^WF~~Tu?kBM)?ih2*pno!6WRiU z2G+#L;tMgfATqg8v~WF}8JY3J>;w1xEo&eF_zTvw_g(?OUUTlq|2cQGLu9LS$GZ6a zku7}uz2EWq_`S+cMI(PighPMqj&F-Lt`FMSBicwo_(FbOC76z>Astk|n5j8t;>E#9 zCLVjRd9neKiVRX@{qu$nUq)j|wo(Cqd;!9ER5k~HwwtbIrRAi(ti zaBn(ye7kcWpu7zNpO5wM@%Mkv=M#{qk)grxy!~)5?IX!?jK8*5Z8(@Pz|QMvxR*YkqKx` z-}6io=mU?u4${E@p4bV10cfWV0f7CmJML5Nu|4q3#VHQ48X6-@~K6wy;1HcB3 zgM0|@Pr?@DqvH(ZhX?sQ^qJ%zUuEBj*TbM331;{A`FQ4-X3MhT?>Fpf;;P=t5;`I*v6aPtjgj@*3 zukmwWsf<9pCX~piV5c}8=r`BJsUiM2(!|Y16JHOSNFesJ#tjUniSKgQgWn@^f$LOWU=Z<7LQTX!4K08` zT*rSZjoDPu#EvBWQvgjhC&!%gSz5!JxCv9*;L6q~PEIs&A^SucnNwon7?CycK?ZT} zQ|uElETQ=DRbl40*w-Pcpzj+v!N3iuPyB$%23Z=R=MU7;wXq|CEKtw6&|#5vyva0h~-ZHF%)P& z#$_&KQDWy8IcPq*g#+y47~bq>Z966GeCil$V8qTlnaz=%@%wlkV&`Z1`gk9V22ytJ zHCfA61#xCP_F{)rvms7%mAYbaO!cV|rJP{9E{+pBO$`y_FD%Pb`OBt!_i)_iuS00YUGFo(vMb#Q)SoUv%yN26?4@-U{)SZ?P$_MIeS(J;qnEPvnt zI?7n~Nq`O86PmGXd6SUJM#-OV=h~tziDAZ~16cI@QA7qY}MXs{jk9JXcy z?x;;f$Cs6%7aG5~%9!+5IQ7Ho3JyHsFaXALRKDs5iF|SY~^^63KLuS z_+eAU!S5e7Y`bDj3cN>$Ys`k2&xlsC-)DtVVp7=6r8PyYCW6-Fc_^N>S61xBlX)3m!_&C=o ziBRA^+aekZq z&(;*boK&_SXjU&^ouUKX!&KAT_zWAOU9e0nvRD?8R=Up z1S8dLY7voQ1e)27jb@PQaW=*M&oJ4bdG`HO0DU1DWBD2TKR?Fu6n}rW0J!6RF=H8H z!2p|lp%Tg!(%-0|&2$5z>4q>PeK-#XWmo{7w*N*o(YB6sm@#K17z(`AH>Sg-cgtv> zXe4GBezfly)=UCaM*47zpK(8iF{FJ@3KPp{9|4Hn5qC(MC!>A6*P6;rAQV5ekqK=I zVLr^68ZynyO-Y9dIwpl-f(~uqoM*4jR^uB$%adYL_MuH{-r)TOBExMXX~~(`lT)DW zm==Z#JX2r6W=)mul}QK0dg*|W_7UF!@d(!gF?FNvhfUG}v57O0-H%G;nul(gM83`@ zyiJKjT1lvfQOwwntISGTk!|y5Aua)$Kzm5>;prQilKP@I>pIh9K0VRKMPcBI{G)9p zS<8Vi=C-Is6i43A6>tO;OIcVx^YRdzEgk3f5$+b-V0v+G4!t;2cJF)iUtQ23hI_V1AAeqfQGN^Sgk0GOI zci8s7pcFC}1nLUALE^y`aaR~+C$l}7#NZ{qP#9Syr?sv)>@EmpX=Nz{k#0`C+O|(y zQ$fyR_}_0dVUGZbr7~(8{sNmF#*y1@mHx!rr2p^^X|>xRZFC!w{>0npe7XmAm{IBf zdLG-s>k|_jBB2;ot2LbB#Z%e}nSz5Mlc5zIP6wo3C^MfpB^mLL(Lb$icBBzkgzgB{ zN)P55&b2e0vR2meaUza2e0>5>w2yjjt`r}reTj2J%ds|ig5?A)Eac?LgR=6-+K;mn zzs;m=(=9uw&*_w@7F|n{o^2tW9@Dc;z1gcJzLehWUhUhyLEOQ0#&nyF_AM3?k)G{N z1E6Q)iLnOe&P*L4nGB6+N;!jAg{Z<4gEvElC55%M`BSx$8jY?FGXiw>0Fn^N_0r_7 zpV4GLFBSA=GkZY$wC8&k_9J;6rWa9qwZr7}n*4T=O6$mc;AvY+{2)y_pE>Q{)@CO= zi$H;#8)ih&OsOS)P6}6@bso!d(J-0ILVy*Mc`QvbfoPG$fQ`xY3)9RQHM`qB?KFHF z!Z^yunU~mlbt7X`{tP0s14Z=CY?!DP9^~5EVwZUk(@FASPW~=l(Itz zV%p4pc~f+4f49!I(rLM~2$a~THio%-I?J@fOp5eoJ3X3Z>L;Z8wvY*75@#h{aHBNX zG_othPrs!rYY&U}0_|5>$DCVf+&J}pcJrxM$YFtJ>+%=W&>Y_{^%8@qe%+2MjVg%BU{#4GuV4{{LaEfntB~kQBw9fyzglmI!f0J& zwsxJIyk{xpEznl1k{^%SdO3c2LRefVJTQhq9159slnB)Ern6##A%VIu`P`dmx)zQx z*^0TQ>E^@kStpazv2|eVFb`0}hG-Q!%Zim`l_o7{M1(ZKj4~Et(G^cB z#$*$%Q?m`OW=P9ieX=Vz+35;uF1}&35^HE&n;e(bsC1n$o!we#ONgS)EV$O13u3xn z*g3maShDnAa|EK9(t$q=2fw1KP*g=}3&XV9IFP$g+qmdY35&axsx3}gOrG)F1Tk4s zFIg0_qlI46Ws@IObEfe020<)a>%+iCCZ#jT(;0p4S>TEJDiBm|~h<<^{r|y3*YFXzGbS vEe_GQ{n3LsW(ekZI+OkEH5`9>uD$*r;of+t3uZ}msh(;q*FZl=Eg8r5*&IivDX~&YOWI>we@9M)zWGq8 zL~32vw(onhrd;)5N?Kwf6ZtTLA{P|rxTy==}Ix|G(`-~F*>!O@tMOpL`TYsZIxQLwJLC$PfZdsOAYfat1m_>@5!T4PD zh@8jUUBY*p=Q$-N;wCsahX4Qo literal 0 HcmV?d00001 diff --git a/src/assets/images/profile/icons/online.gif b/src/assets/images/profile/icons/online.gif new file mode 100644 index 0000000000000000000000000000000000000000..3a79838bd329181393f7e91ac17fd74b09d95b8d GIT binary patch literal 666 zcmZ?wbhEHb)L;-`*v!eWbLY;Ej*h6Ps8!)C|7S4#Kg)IZ?p+23hW}syWS{}X|J;7A zA;Hd$0j@@R2F#2=X~mx`tS1;a8FWBOLB=w$v>BY7$+Fktb?)~6(-rz;Guj#&*RTc5 zTQ)(e(z$}YlEhSXen9e`ppZNI&HUZ^7fmb zPDv{4xSvRWTlB~>(o7N|;QpAOSJI%{*tor+J_N}B## zg`fU@`eoVSMS&q6t$t^3cnUEhEaqTfXOIS4tY&c1bF~TQ^PDYz#kW@~bj;0&tW5V< z6|;I_)JMe)t#8WW-r2-!G9k1g+$>|@nUJ!|EbwsQyBIS@6@|D5l}jEe3Lcqxbdw0! zE>5r~5w5lbx|(rsg;et9`?IGrsx)+nXZW;VQF}Xkg*Nx`4K8nL<{h_~ccJ|=XS?}y zqrx`}XSA;PvU0N9%1Imu>k+Y;268ztHqY+ers!aP4N9 caD(Ig2>~yD|7BrzlBlV#Z*1=D5@fIj0HK@smH+?% literal 0 HcmV?d00001 diff --git a/src/assets/images/profile/icons/tick.png b/src/assets/images/profile/icons/tick.png new file mode 100644 index 0000000000000000000000000000000000000000..ec8c52fdf71f359dbd22ddeae746f2df2459e03f GIT binary patch literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^+(695!3HFgJ}hYlQXZZzjv*DddV3hT7!){;ynA1# z5TBDFkvNOD>BY7NwluLn36C0L9BT?L^2s{>Q52qPD)23$t1B*PLf1LbkR&miqY_&l cNrv2JDa>e9Jhg1gZJ?P9p00i_>zopr09Ai08UO$Q literal 0 HcmV?d00001 diff --git a/src/assets/images/room-spectator/room_spectator_bottom_left.png b/src/assets/images/room-spectator/room_spectator_bottom_left.png new file mode 100644 index 0000000000000000000000000000000000000000..01688cb295d0e7551042935ed2f4f3109aba7c77 GIT binary patch literal 461 zcmeAS@N?(olHy`uVBq!ia0vp^(LkKX!3HEf8uQy37#OE`x;TbZ+3@i2r9VQgauiG$dG6K)(p$WA zfY2Bh`DR&W!}Md@@<3wy)VA4A65X&CWQ=vv?%P1-Z-t!K>l$+JvL{BDv*tXQy0J0* zz`vT^e}D1qIkZXm7e__QUdHMjFTTv2bl|^g)l1_`?eCiX7uf6li@Oor9oJ>oET6bO z;!A|kFNuFXjMp`jwad;ey8ftN)Zk2$QJ-%1d*eyXv!fmS6kAWv^3yBpbiJ;zBqefl z>bp|6z5}+=k;Rd{wZ~lJRcD{)IKp&;bI)t5?sc8K+nLJ#&sj2=X`@0J)BDAzH>D)< zUrc>opOsRbaJWr(Ln1dY6j;r28hNF+9l*jrKR2IYH`k`;ayMgv(Zt~C>gTe~DWM4f DB_q~? literal 0 HcmV?d00001 diff --git a/src/assets/images/room-spectator/room_spectator_bottom_right.png b/src/assets/images/room-spectator/room_spectator_bottom_right.png new file mode 100644 index 0000000000000000000000000000000000000000..59c8ef2c2daef66a18a2a2dd18dea786943ace29 GIT binary patch literal 456 zcmeAS@N?(olHy`uVBq!ia0vp^(LkKX!3HEf8uQy37#RCKT^vIyZoR#}F}K-4qAhWj zM$oG-aSFNL5@mM1nb>(kq^Dpaw}2Ac=i(>SHX5VgvaN**K^72w3d4zy9obBEK)eM#_ zONxXXT>zn0*t_`Tt7^25Y8F+H{$gTe~ HDWM4fUf$6q literal 0 HcmV?d00001 diff --git a/src/assets/images/room-spectator/room_spectator_middle_bottom.png b/src/assets/images/room-spectator/room_spectator_middle_bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..ba6fdecccebd3460ca5d32675b070dbddbbab852 GIT binary patch literal 98 zcmeAS@N?(olHy`uVBq!ia0vp^j6kf$!3HE*_k3grQktGFjv*Ddk`odVew@E>@#4q% x2d9Xhh+3-L@3^P_|37=?h`YX(>l|Gf7`EN_?kMv1F9T{~@O1TaS?83{1OP^IAYK3f literal 0 HcmV?d00001 diff --git a/src/assets/images/room-spectator/room_spectator_middle_left.png b/src/assets/images/room-spectator/room_spectator_middle_left.png new file mode 100644 index 0000000000000000000000000000000000000000..6d9aaa7958b41a907aacae0e1025f191f43c5418 GIT binary patch literal 94 zcmeAS@N?(olHy`uVBq!ia0vp^!a&T(!3HF4{HDeLDOFDw$B>F!$vZU9f1NLJ=D>ju t|Ns8}{{Oe@jld0>+K2!5_3>@6VOaQ6#dBs-r7BPZgQu&X%Q~loCIAFlAkzQ< literal 0 HcmV?d00001 diff --git a/src/assets/images/room-spectator/room_spectator_middle_right.png b/src/assets/images/room-spectator/room_spectator_middle_right.png new file mode 100644 index 0000000000000000000000000000000000000000..9d963b3d111202dc6ba066b300ee3e378186d025 GIT binary patch literal 94 zcmeAS@N?(olHy`uVBq!ia0vp^!a&T(!3HF4{HDeLDOFDw$B>F!$q5MwKki?=`0@Uf tsSoS-b69iL{r~s3e!?H$O1(t}3}&7xo*jP_76LUec)I$ztaD0e0sy?l9^n80 literal 0 HcmV?d00001 diff --git a/src/assets/images/room-spectator/room_spectator_middle_top.png b/src/assets/images/room-spectator/room_spectator_middle_top.png new file mode 100644 index 0000000000000000000000000000000000000000..f6559cee0eb6b9c10edbe88989d77f6781520a82 GIT binary patch literal 95 zcmeAS@N?(olHy`uVBq!ia0vp^j6f{R!3HD)xzixJtr z2d=!_-1X+TmG;Kd-_B^o9Ae>CY`M7NefgJ|9Z$3Qru*~Uf4_bAw9Ezu>1~JF)^12V z9Cl;+Q9i{N^NuO>KVD%i)#q@&fB(I>_w%f4AIvTO9J%rN=Zu8( zm=iNMray6i@WEL3cMtdfij%ir7+<<{DB1AWzVo+Uq)v~j;9Qedm-WwDSyQ}D#E8-3 z-jb-YGl#YxD2ol3UoHAZnLGYaY-8=aC2`-Twf=XFJ761MG!z$;~z zb7RBpW8VtbK-oYkUTL#8CSXNC7En5o8$^J`fsE+mY*1mKNOS^B7%tBW)?y!9$H3lL W`0exJA! z9_ubY7um^Q>gP25^~B(UmeLpn)oH0^jk1+<%`b^x$=}B|ao>Ia$fQXM2ikNuBy#86 z*uYwL{f7PIr8c`aJg+<#G|%?M%g<9bO?mqI?N@%QHws@eXRSJzDE=sJ|6x9-{WjAl z2Ql;Q-gJ@s%hSD5zaOhUomP3wEkEy@Mc>ZUs+en0i_Md_|Kse42rF6qJz@L2XQ!>Q z7?<|erMy{oTB>bf2BW*#ubCTHPcO`7{C*)@`tSUW;o={p_p{Wly|{tgh4bbR9bJ-0V7-{zGz z19E=<^f&~R1qmN+g9!Drw?h~(5jK#*#KUGAsu^&}z?5#_ebcw`{K>AuZ`}tiSb?F= N;OXk;vd$@?2>@0kz9#?x literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/avatar-info/preview-background.png b/src/assets/images/room-widgets/avatar-info/preview-background.png new file mode 100644 index 0000000000000000000000000000000000000000..dea4f08dd49c0ddbc562e6eb17c854411d830cab GIT binary patch literal 7756 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 IM6N<$f^1`jw*UYD literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/camera-widget/btn_down.png b/src/assets/images/room-widgets/camera-widget/btn_down.png new file mode 100644 index 0000000000000000000000000000000000000000..76f25da1d9243426aa9bb4852f3121c80e910385 GIT binary patch literal 1000 zcmV>P)S_5?q5U31f=@VAwx@ z&;}@kWx*lHfH0N`Kn#w?W`+d6ei zB|ZuL`M|!uSo7_9{C<5U4OSS$0Nmks(LX=`4+3}(`Pa(}Y4pO72H*ytgnodZ(Hr1B z}I3P0g@ zgWnCk#83F0;CDqo|b)<;FJ;8P2zf>*abI(mVBS-#^ywq4r# z2Q+`W5ke=vHlKw|*sj3i@#Y4Xd`ZTfYK61-*bbL1(u8N$VSOZTGAUeQ(fgv*$V zmzEn#x#X*8Kf%4Clko*RC0t#6pYukA=)(QaC4kDb=n9>MA9$+@m*_40+X9=jxJIwx zs{)w39ntwQgxU-0e8;;1INE@#yx(DWKw}3mY0J6r;M@RilQ&sh4xAc*C8bW~b3BOrE#5CD7I5U#CrXFk}y)@n_++cqwPi^ERRmY zrXmBv-~^Bp9F5HkVYvWQ;K1>L`n}Wu!lH}8jZUr>0faK_E4j>M{VXwn5cn+gkB{G-06v8J`l9WU-%SX>6`zfM1Aj#C&C(Gvb(Rl)mK}gM zJ{tWAK!Pj@V-!F%d=z>U-0Etk6+$ln1%6m`H*i7#q4448-r$`8LgB;Fqkz8(0El1c ztKpMIOeS;%K6K=;+MWfSi;r$SsqN=m09nw5_>k6<+TKfkb#x9ss`c!)XGIs_t6DE@ zdrI^kKC<=Xwr3wPvs+(ncWQmL-M016F=P25q^LW|ggS0mV`jIuQ(G??H`Vqzt;dd; z9oo)3$&$v6dd%$Jc?BK0#8O+8u2myY};BvXxwLS_sAOMQd&60eF(gZ## zxFP^4{G9votAGmvi2Fv!s^~;~dw9tSc6IatzS*1IsyUwpy@Rhlv9|EV@Y&EC`05iY zblhY`KjE8ygpb|&9pN=&Ml){yypRpxsUOoOjTzV%6E>Lf)4rHct;e*T{XM8B_#Io% zY&&brKwF>iKTt2rciRwc*S0<#KIK_9h_;JcpAmnAj&8fS^_lP~=(h)GAlt5LeFl6A z`UxJ^c2(FL7>n-cV*G`dstAKl#m}%fOE`22zA6Cc<%k}SVX3vC9$)w{fItgyonQB|E1~ETFS1AhPqE77`9MV>D3=v|^%}NW)AiNCpF=o+3+cB0y{%U=ZlUzT z=zsO_k2UI-;TA<9^&es`PrZ1N5+?uv002ov JPDHLkV1g3Zz}Wx* literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/camera-widget/cam_bg.png b/src/assets/images/room-widgets/camera-widget/cam_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..d6cf994d063352f748ce25d259c4dd3e54645754 GIT binary patch literal 2166 zcmcguZAcVp9G|t_b$3bIyLH{t%uHBLiPW~K$PS!NQDN3*ZI-;~)q}AXqvJYzIgjHS zd%e5DPj~JLwP-BUKte5Ymm$Hmas)elsPRKqgm4uYT45O^;r*Xy+-GN$=u6#)XP)2x z|NH%4o@a*VnKN8NZI&a?K~YpzeO+}UMVWXQw{2GN{Bz)CCq>!5tgmMHBO~Lg66oL- zpA3wgE|4lO-7F|cWZiE%St8~YukYy@E&J)*x<3vIJ3ZmV(BO-lq0?7aZWgAlP4$;Q z`;(n{p|`caHOAZa6jTQ} zf1Q#cmd3mn1t#F;U5!45b~Ughe3`h97sfBXr8K6^2U}M6GtOmVmc126%qtG(zP^ez9-!k z;^yaq6E9K-27*4NXE%)^NsX2%lsN})}L)ee+P?=n%A8@AA! zTF%chWGEnnx4gxPhp(arwla}};2UDGAZ1ijsn; zFI*V`=o&Xd0JM05PZdY8ACylnl3T3lOc0_kr7r9kyGCh6fQsAY784OGUTLDxU@bLR zOAJ;nw&=6b0j(`{jX1VS+r+yzxzdb__=}~9Y$EjG{*kbc!fQst3?j5T)t{pSo+@sE zIMzThSvGl^GSkbYi406+Vj>$8lbMLPy$lf>4r3x|LJ^ZzOypsr1rxt%gas41nAi%t z;;!OMW_S=_g0BvHFo5?*F%~C02mnj-U|>+AM8<6YV_N+pK5g0El??5>|6o(cNbPX4 z_1ErDZ!{8q*TU7n5xS;a5(F;{F_fBg&Ce}wnfXhISD5+~Y^!82S?VgV0^Q38Mgn>2+qg(zwMC@c#)2Mf+Lq}@& z3CB;4?;J0^o0Ozf=T?ptj5w>T_K$tLH`Bt6;1CUA+xU+FN4m)D&o2=oPRe((w;|KS zrslqXbRBjPzsq?%aJ|33Y9|WA78-)o8{Au@y%-U^5W$|Ca}GJpxA3ECa@seq`8D$S z$~41c6{&JH@wPr&XgboVMrCh@6tMA4js-)NJoy@;7`@J8Izzx8`-NJ;XlaeCeBP2tm&3Jq&xQWU?St{WzLk zTi|GAJy$K4>um=oMT#?1@mHW`aC(*5>VC7V20LsjX*O9~*6S|s_*hUoit%jx6jOi` z)q3Zk#`py)Uj2%1Ic8_t4^dRlhm%tp`+PhzPDDNY)Ea9YN_{ShIiL_Jgv2qsS~Ry_ zype-?+JigbUP5A{^&7ta*^UxTZr8DG)QKeQK6K!pa5h@0(3~g+J)h-yCl5}@`^srA zMB6UtSQ5s^aD7m4!KI-%@7S&q8Fp>D%=fh>aq=p`Y57hR;X7y9X%U*IgnkJg6Jo9y z^6=XQL<8}N`c{5%40>DWyYJf&4P7@!!`ZSYq{rl>4be{`Px7cDBOfxKZe@3&ZzY@+ zG7K>nzFqtAMHHdpjg7_?7u$=6O?K;{@80e{=f#OK9RFf~XqYxwY7&;fpbMf;aG)3` z4hWvLLEIwR^l!NfNGrsAfW;5I?j^!5eXfO$9kdhxgrbmq_%}VTf-u1?g^JIv5cqH|s`2Rq~wYivjY&m6n z_JqX9Xo)2G4WDOGkV~J_qaz)W_@0#8X?lR=HVIyK7XjG<%?ncG8$*G=&{51coO^@G zY}x6$IVZ!Ak%X}9t4_SMi9q&=!#7;$eCO-YGMEDt6>psr@Z$;F$3y$sUEHN6PKMxr zob1~h&4FsweRjAonT?izI3<=LPTn6;`}J)UGxq6@*%Y?yQf)h6u)SeP3?0QdO3c5( z-%vbvOZ{=2xetmMVIfK0AMUi697SNVAHfOJJSAU6c8ezSSXg+=g<8^#znf_wQ5aD< zc5Z{XG>7;~PC|TEGCc}(<5uSj4$;>rc9%Z$lp`i#<~_;bc80*Ehn(Xg?8y5lQS#|T zE}TsEcHU&=5?)cV?Uk|Z>_`Magw{WyUe&g>*mt4tE^Ku7*-sQE%No&8+;i^cTipYH zBOkWi2TD&@tAj~l;IFKw7vxC44~Uh6(LZ>#Re)l?h0`!K0#J(hNA(zj!&2uA z4IecB$#*xYC>e)b`?5|5r6qGky!(A6pxFKGmwxas{n{<}{SnSQj4>6QDBy<) z!CRwl@-9hX-BOk!)g+UB&nRnT0PYau+6#GVKo|7 zqf4*+hK1F7{qx{L|7Dn0O+qY>@@UaK#r2YatS%6l`^%-tG&ZWIH0Zx^GRvPyaiSQB zS7$#RVaN4Zt~7w9m~3H38uEa~yc(327(5!|k@-8fUZepb=_emU z`s@)|B^!uht(|xu35=@ABx+9RQxx4Z!;sb7CwKQtOd{fB`$Z~ zN(&r5CJu0lqu%xAfpa)|?9`>mWs=!Dk*LRMU^eQ)jk;nOxd)PEm8LHAeHoj0Fj+F7 zkGZAoWgP4{TcpRu3|?>>ry+P)VnG@L(f2{YNv9J(ILOfY@0f1|OV|XKaH@3}e~XQZ zzFF2|8bv5*dAyAsnFrSQPzdgFlG za6q)gQ3#2pplIds_tkc79Pj}SNK3Ee!JM%HM-3h{`u71BD%7KxUXOR&VnZZ=NMC)U z)@<8P#+N7N z_48SO#(*uo-T@oVE7RY%G>lZbbe!G?6h!dQde?R!G7J22Stl-3TiY+t^yiQt|`5z$+fa29ic|8Z)0ZVlVYd^W67FNzO zw0uqR&Eg@L9f0Z%*~i%%Of3SbIVGuHLZa|GA$Y}?53g*{{MVQy`LFx90|*WwP)K3y z;QI{{0)T|M=ZH3N!HICPr18ED&E8#T>LQ@1yjdcojt5*N&)XQ=-%Pd2o#a3n$+NQ~ zQ3haie4_3+EztO2y%+M5gBMc<3Iq!MKZUYU``L@dCh&Hfzy#|MphfIxlW}0_c+34H!wI|( zZ>hU@NZnERK~PIy1QTK@%=Rh1p=%$8jUCAg=pzl%-ku7v>`BsfBcT`_2vtYmgMJ9U z@ayp7O`t@ZvIM6ML-2OnSSF5OcS&W#CVd4ozp9K@BV76x5eIJoU62iQA9KGZ%8v6B z4LtRcR=S0i)@=qhAn<8F%t9=GboNI+Kmwk7|7)MKu>aX-f}}s#+aX$97VAY;rhYAs zWM(K*7-qdYhmRp~aGE9V!*`v!Sf**#j=$LuG|sl$%wFfNphcQUS}jA02W zkNxF9G|A<3p!fnJ$;EGXt4Aj!KxelqielvU)OB4o13s92XaaS8igb!%7OnNJ3ukPn zZNGaZ<>l`U(6B*Dr5ec41u@w;?He@WdvE93(%nl@OqV-5-y4QldP$8gyae|DRaXhI z^C-vhDCW1TmDPST;rQ|d_h+{8t(OyK@ds%Whpu^peKi6PT+Z!$g$=PB&Y>3FgfCA% ztArnXfcZYf+g15+0*b}=wn>wBJPM{e1d#&lMrIbj_TSL^!Tt5sw}K}bzeN#)G+^q5 z>>T_ZP!@K6-O+Ul>C2XJg(H=4%l5Ini!DdKS1xC~;S&i_NPP~L(i5?*LfK0}N+nCm z^dd-&GEZCVPHcVj&xE^jL-C7JtYNh|!8+Krp?K|18a3YXC?6q?PYiG{#r+c|SF)rc zL7sJcs}q(}9JM##9+KvH@J1r9l4a_$Ui*J&~8cAg~hUf6lO z&niF?OFf#_^77mtrsNgHTUv`GAS&SJj{?@co3K>H#J-#7jz*78ZE2XE;&9o6kvlt* zyL1lRn1EG!xTKV+b+6&Sc$!xLp9{vkoQBUAQ-ud4uxiI|Hg109d8qgkH@I$zOJ^~- zKD}7i1mh6A^n=^o@i`4Gt+9mk{7Cp;9yQt0nlDUy4(w|FY|Tf52BPZQzAcHiN0S@X z#mSAY%li&dJymQ<(!f)=@icm1aOe2rFsr-yfUrEE+ty?kS6?P16q$ZMd7=nc8v3@z z+xq16X-Ja$63Y9;et&ETo;m5Xdn6m;1Ymh0H}jY;E|tTugbXo<+kz4(%5Xfdk%~x(QNAeGpXhTfPxvIlhzbJ%8ddY;+esZRhwn5n@@M`oy*X4zPLP z4GGpS@T_ymKL0pi3E~IZ{UKfl7=CmBpMC_k+~;_AYhvqB?;}sz!B|{xse|hf2I~;U znWjrnjgb#+|D+Zw2o5xCxktidh7AYBg)1qBjn)>9(}&zZ8FB++Wt6uBDJtvmI0XI) zwHw}Gpc~@3y8}zOOHZ@%0byz2_CpMa2AzqfN_RwAO5g0-9@-s+x7+-0*F&l?j$G^y{q?JG%yX_pPj3hY=J8(}rdDRIP6RH(a6DU>|j_ z>{xM9_CGk!48S#m{&+04F5NiM*odG6UQDTLYs@J*Zp5YcJ;16S-izloJswW4JqwRd zX$^(1L#K?mP2`ODdV=-gYyH3UQ!Xf`N>X^bVyz@-cSttGvK(arQaDErBoea8rF~%- zS!(rM6+?1+7aCZ3rZlJcg}KSM^9b99uBheFyAk&Itw5(DdtP_1jG$ zzb>mb$I;VMB~VbQ;-OM?7eL4lO>1R#j?_MLP@E6e4D5I)=-)cj>r&JJk}%8Vqk103 zcjrM_lrI~z)5BJjWTA{z)NoLJm}`tS;?v9u#`&>bJv9gU&s>VXl((WNDO$3GcYEvO zc=+UCvVv4XtHLj#=sIy=9*<&-H_!W%az%;XJVd?2dQNLK6lXuh5>EY>*RIH#qcO$bj&)P906bZ`qMyxn+lGp8{m_3J(R&D-J1} zncMc{1xFs&Pwyt-*bQzn4!!#mQXh6>G=2PYuEGs4nZD7FiAEFs);XPeJ{^U@bLsaO~jFlko9yXSf->!@0 zsD=#-Zr5|-*O*a`IfKWI5XSo}N|fG>CqQBpd!!I2V@taJdeQQtC@W ztKm)aEr{RoOI~3c?<=Wj>gula+l7LFvOGtEC95d?x2k9(aStzhRB1EE%f+A``Fs$| ze8Ij~ICK`w-QOn`jXCq=aL0bf%C$9<*3I|&$D^ngQj`8J&tHY4i8=aKB+>X zR)vGb{{2#lv^7)}jaUT#5s02mPN=i(}sgko0I^S42X$3qxt>$HqZ<;*9w zwGl=OCBT42o?@9Dbvq(3YTzG=9sKD>uQ9IBNg%GVHi2i1oZFs(`ScT%M{6xdAISU&hf9X{S(;kk&MH zn9V}n!75Zxhp7%6#@@&eAjyxf-k>399)xAKgH#7LBMamQ2!S74yFFnU`a7u{o={$5L*Gkt)WV4Xsj%@!gx?wzW#_?QNwhqG_{SM|0@jR4MO!!xM%wB zoY7A`!+@~5%_@vT2uKzd!bE++8gk|lmXmF5pR4&#I-#+ISV9grG7a)L!8<8`GRmGZ zd?H}`n2YD}EfA_?WI7=}IsNebHzU{O%LB@W6)sr03bbIe_-kn8^_>~|b6$yJW1?kG z?Ln(Z3;{49gbp2u$QjCdc1c9}BV|7AO(mZZj_#UzY)j}55XBY>8#ZcBUfWn_zC2Bm zG^=E=Tije0asu~*_O0Tw_M`i~^V?#-JU{o=t0B=+(U$)M=nCt+&xxY4Keb7aYF-l~ z=M=AnJ=R9b0sivb=S}=12|LQp#wGO8RxI=`vB+yxehqyZS5W0G6pnu#=>Gda%x-ZM z(6^oUZK5)^mDlcCDam%f+VQ;mw!eSYgrC7!x9_sPK$VmtS)TZQ&f1LMf^=B+_o=zs z$eSRK7YHL+N=}Y>-jkTNvDUNsX~TeRPwl8>BnAV~?#y{_sib5z?b%f)*I5G5`LqS_ zZru5@oQ~)LuzrUe_0y(=_h#^9OJ#}A9NSX$QQSKR?BXLH*1Pjap zA4rjN2c%*9VcYHgP^e;|%LmE}+< z=gFX~fiJTjMxh*~pxWihkLXZ-9zuwL^!V+$)uDd60z0?<*%^eE8lML{$Af{lnOFwH z&XS?#u=8Dqzoo-48!`QY4qIqF=Pe+?Z^7>Lzp~y-Kj1TC(OaNSL&uK*$e-F;mo2o3 z!%_+ONZakE&d~rK%tIOB`o1rzT=~-s#>qgl!tnED*dt>2 z60{os6y}hRX7C-e1eT6G>HNh5kr?09V)&3l^s;DZJqSxUr7|1b8E7^=Ib{x*Clz`M zmVM|w#`{8Fft^6I8VgM)p7)MuEYquR9|t>f8Wd|0KgP+9+$a6?32-mmCXp)k`Y33> z&vRF3vZZoNNCQJGTv%;a2~ci{$AWz9$d^3tbperQ*Rr8)dL23|c09lH?$CdLseHKC ztdxznQ()LMICv1I3;F?fck~{JgIK zZK9Z8I}b+m_Z&f?46OPuWO1Yv^*CB<69_~TWT2TI11;AVoSyt3-oVWVpdEw*B6k^n z08O1o9)&jcM9?Z*2u2C;Q^MW_(RhkKwFCU5f9wSP#lv!bwPiFLxJxJAt4ZqBLF%!UMvPc=x{r5cKii^djQa8z!fkU z2P&@h3$b(t05djwRRZ9jRvG|8x4+N+pJ4#>0Kg+}g`gqx2>?Iodc6cd=z}}3w~GOw z2mm?{yWU|#FyLrrn-IkA{m|RnAMVu(fGGfYr;Qx|B>?d3*f0PZ0pP=iQi%PE@R#CQ zRd;AcK`x_+e53|NBq+;VYLzVrKp6tBfKma}WfmZ~?$0KIAUHrG`m#H0Qv;{eNM9O& zQ^0pRXdBssw8>Bs9`)ygy*GpOpe+R!R0}ew?Yj$m=Rhd^mR}0y9}6eP)~j9vI~p~ zTtb0Waq`hhIavSi2Ryi!zz@a)wdn7D+wWR55!_%%$bKUaXz-y@z>-D;+=25P@BhCA ziq}OeQ5Z$6%4nwEJrtUuJs*w+#hOYSEboXU?9(WKB=wF zOKQCA$R*I0i73LeSK9o>e+ER4L3ZGkr2eR%10+_3ZX*Vu3=qgBG}bSd$dJE? zZ?io}+b(eTKBzl%c#ChMlH?pw8=Fu!>n_0m^Thn$ z5)f~K_U>fD6xB-u$5%&s2Lm>o;FxftQ2TaT*l2eJYaRxV48kSdhpi*{I#;Z9RU6mm>fvdMrQmPTK8wT!92NJ zn*eYW0RP9-h6BNY1jGD$>*GKOND&a#B1%p}w;y7sfQHsP@NcGI?i;=CY*4h&7sKo9qKr3Vjyflbld1ChFjF4=-343K z_O5kMbXNGb1Z*hHxzTyih%vOwK5=oizT<>w&4^7F9S#&?cTv-SH7UZ2mrI}?YZ;!; ziA}^Nx;HD&&gSih*mIdRk_c-=8nz5=O87?9FFBfB-I-hAXiRyzr`GBiFhO1Nbsk=^ zCiF$jHkIh$DC2vbBixi~G9dl&?c9lpb6&e!AI^WtuO1w^aju3#nr3{1bOD0V3BL0u z(azW*X1%VHw|edV*?q-O5e(lV8jE{RWhS-rZ}yHckHwyoUW*>wW537Pg?=5oaU9AD zJ;)g9N;-d`$+x&KhAG~N&^4&kNO*Y#BofNiUhIO@kVZ6JwQQ0(`_czle$sH-+@av9; zrum?IIhnYqbhqik#s^6YuTtJ1XPo5VRKm*S<`QXopd{%@Sh40>Qqj{&&Z5nd_^|^tRxEpEo{A{w4IoX}@1MbBGpobFtrzL`;<=2`sF) zPp&$3?OXY~cDd?mpL1QUG~EFW>moI4A}6k%80(Ot8T(^BVEzZ0svX+dkSVhAqUu=C z&uH81>fJBjB#maS``MLhtuH?DDY+rTVH!hJlPAe9#$kunmwSUv{#n1Y-d^kX?uYf! z-XB9dRy^Vvx;T1baKU)(5nvKl$vp<#yf;GsHq(PDbc?t^|AEt7K!1%+`j(;-%klMm zk4p3NTD5Z%9|N0^GmnPThLo{#d!}khEr+63RtHzwb0|H7YcpE47kuUK+O!z+w`r{D zw=!L*4s}|sOP8>qV_imeFmxVGuRHBR$ICf$4iums8J@fFUgCxsmn$2+O^npL=o~g} zEo~vCynDUC(@Dx3caU#EM2tdzs64%NNf-7QlgIzI+&Uf?x`CXG*<(6^cwx7L)Qn&C z=@-WFcfWj1*GHEY;MAm&;g{R(R>~q=u88#-CeaWbQB~_aadao8R%F7mym&2lNV#i7 zA&SW}d8P>>)_LR{Kcf-1xF#7WbG_~}l^3t^j43>1L7k*3Uy#=}#%Y38lSBvo)^Q$~LG*?2|-9k{LRy$1x zNhVWyQ-DNu{fdLVQv_sdmYw- zTF3oj6dcp4N(Y~=H4iDjl)B4o0m6EVbY0z@)Ry{7tpD zr|&xWNRR}2f^`6$7o9$>kPkX?OvsZtRpl&eEmVBm1HW_-zBQvvZW9pe)Qr{#x26-$ z6p|RrZVtom%Zw|L_v&gXvm!qe;SLis{TvE>zG+q|K6cVN_IU2}suVL#Y273&qtcsR zucDabBQLWMhb1Uzt4XCq4^6*NQLJcX-v4AczWzzyK#^io^f*l^ijVQxO@j$eSP?nL z$N17j{#bOrTC1gZEwz|_jK7sYb!1>^>0d;!az&>dl;VmK80l4HY!#_zz3Tlv=Xnbk zwZl{^#VBU~-K;?g$Q5%fu(UwBy5rb`9eaM7jyr*JvH4~akj#sGJAws z*!OWfzsF&xbG5zuYQ(29N%Cy%niX{Nn)wb$kxQyb*;*EAl#lNTKeuXFtNlPZSK6yh z#XYz16REGyVCZXvQy3TM4ps1Rt&vh!p%g7W;Ok5P#LTSzhf|Y{#Sum!^YXIH8M11X zt8Sz6Pu}ZBO5rs=3Q4~5MVIXd3moVkBFvpvT>84IZn4?|vIr?3^zuMK;8mhRsR#X9 zh4TDAhO22!#$92Tx3m$v7_`$p#WHTj6Dprgq5T@-B^e^I+(c2D|m#J%d(SjSr`{KH_5tn??--+wD1%`54rU>@O!r_pc>?DC_sAm_Cr* zCYEwagzl^0*4VWa%;VU)RJ7_%ZYZu-^|FA(whm2RcKRdo+uR4&rASG}TkFmz^_z1L}r*^`BiYc9>i(KAcLi3t@2 zH(z;?*-Oo9j)Ck-5i-%tgSC)=VaLXe!3korWDyoPHDh8KzHSHdwac4(bmmn8Y1XWZ@*T~7EnE2mj1CK$P-2Ae2-J49Gjl_DE`J`W`>$1hCX z!>MZ7+;n{0F2-YAsvJeFfjASR;YYE2Jb>#7Zg-x1Hj2Cc_}nfQ^n}itP<-VT8L^~v z)v2N9S7a(JAH5>P3SM_r`kOoKxPK7grMi0=c5JEWT5lr0yi&OxZZG*rJT*jrdNy@1 zGuiQOvLoY2oIsjf{%)!Rnj-J@Id!+uc<{`U47sYNQ4rcLsP`0BKfdZH&C^6~WQaJ< zyzD~%ek7rIEsA-xDOL}%i9E>FX7rPYE}uM_i>;`El9UGJ^)#ohv#qd=QRD>{rby^N z&qR;_<`mkV(kctyaIyDSqOe7e78J~u_P~BoR%2Q#KbcRS*4Dhb_R%krpjqzWZ)m&T zK-^*5%&JP+p*&g`QM);MfRf){;=a^RC`k9$Sizuc^_E*>LkZ4 zJ_fhud%d}jmn!yWUcTBIRhEOFE4TNn_2?_b9|cgtOTj2sU5n8{h&2$$TdvBbXeVOP zaeh(`z6y8w7+ZR^Xvc?-xLC5(=2+;3#^SdvSGTqt$Dsp06;yvJ^VCKQIoVYyW_@(9XVI7co z6Q__Ixr~ds<%O{t?VhQOi|y#gR#t&SSIrY zVaj8ugi>Fy{!W8j=CnnI9Fcx=JM@{rs*9fwB)&*F(+JK8(?YBjBO7 zh3jZiV^nfT8qM8+5wkEQt!to$LBA+=HSonHVmrRLB*reP(3i}!6+}ZtH(%J` zP0{82@1|29=j~nSBYn;I-#m$NQ3HSbvgL@bT!i01q=_sw9nxM$2EUJ@MGo;+uqjoj z(m60<;YfZ}oZhXEbZ7dS#EtUleYFJ>js%D!tY(X18g+mdnHOP@>SqtK3dASYxt1p)2o~iJWI4q zwH&zYJhzbjSc4)6**Z0FX={tOwQ&5vFZa$NaPpF&cU;tx7vBxlzkHD4pqg%(If)Q) zUAby>otpV^>m%;7D+|q9YiDHFFEEJ@EO#Cc`t?9$wL1x&-dXRs*ca6FeE7=f%7Zw~ zv><$t{&;YFYi{txfgp|EDxthZ(UQIjVB$K=YLMA+$b@8|T`H3T=o!D2m z6o)366)ld0e8m7#crtghKAK<+b8DBo{sk#`jZ8*dHdESV>g|RL!)j~1ixl^D~ z=fF)rnPiWJNf^8i6^G4=ILbvahr6SGKbq;5Ex{L(8M&&nzTA9+HU~#;5f40ep|(j; zDpq`Vx2a_t57ky!Y8G@yP4YOGcnjQEHN!Hys}T-}+UKzocy)Pkh`oZGyHE@hqMF~$ z{Xsv^@0rB zL}&F4m@%*8G>_dVaL{vDFEC*8EsvaW&)md_(Hx{Hr^fHPt$y0pk#n`J zWq4{?Bg**4@|<6MJ#+HQOmcna!lu^|gS%=@%UokGN@O6{iX><3UiVAQXhc;D9}Nte zu1Wv&Y3S##`KXn+J=CqTL z*SH{0TdwNLh-2x;M@cdts}>d2=bX;3{fZl$^|9$~_m+E-&?@-GZ~ewU^@BB)_Q(0w zv3=EFgXI&r2}I)qB2z^tpgISI@}ImLkMu};%77y>Hi=DG?<6}5;~Q5 z6(PLbl*W@?ozOon;hFcITAevR(ZQSz**x=HX%#hx3So= zl@=&Qsb9Zydm#C<@heC25Pz(n1kTEc88ce_f$kX^9K*^q6{ap($wd*!zE&BK`Ai~x zcp3I|p|xe~b^dL`7x8?QHkp|~$pn?vSPtf89g+sgTZ2+R8DaF52igq8$2D*3JH4w# zj#t_Tjs{EoOCq?@=VFw#iKL*OvPoNZI|fc6TfJAu&ae*1zOB z22U79@6~m<_%Uy+lJAYLn@yf^vL7P1ZPmEPr;&V0D*M)S(3K+oHr~XfI<5QWu?Ghg zQ?~w`#2zT3^DusxMX^B>%NnsYih1gVP!RoF_}zd5oRs!SFCEUBQ)(5`!^oNQYYH69 zi(Af8nljf&#QB`R$9E3DS+Te^?e44e`tQyWC%y({Qan@uA$8kB|86829*n3B{?KTw z*-~iWuP2?^u_|A=!~0e8nVo#koTLY%*K3S-e(hONYds>C%QgIZ-lfIi^y}|SwUn0Z zDq831a=GLNVk3eG9R<;YdRe@*K*V!}~{!SkjjYZEcwL;h)c}ug?6k zyuZDHYKA>WQC4f3kV~nb3|bv`Uhk18tk6i4UCEW4_Mf=sJ8kPUTx=*%Ja#k=Ig@bC zN#Y_ zaWNpNg)=qyns?`oDr!bsS-%Bg+IG6Aury;5NcN`S57t7ci}cs~Bjcbp@Eze+IO1oN)OchT5q2Fv#>mj>1&_JUsF9ls4ppx%kG5 zDM5HLaslx2Fw!{zPb^~LM#rAin=sXZ5{z}T8~zf3!B1v>`!05*FpMg#SxWg7=8pXw zufM=?+hK&d@doHW^W(R0dAElg=?|40boV!2CSc&=STPF!31*-F8Jizx-F|~c7Um30 zV(C0t^>Ca3z@;8A0lZ_OQj8P7|g{OQl|$)5D82Kj5$?c2%>U24e%Dyg=;>#1J8h8|9Lrf8Ge5DY8T|> zVX%$%nQOq}Rk)wDdhX&QP-qwym%^oAD=}>Q;dTQ&!z8!s zxQoNEV;8%N-!8wez@>|T*TtyETUqxOL}3(XaS%!X;0U~?RDkXPXvSPpSt{HQ*ihyF z=M=Hd3LU}eH~{zc4%6VeUU@1fOGKjPiSWbN1JzRjKLdtgf)sYF^h-3-0ySboBq1Hv t%8#)WKoBvoO95_guxy_7Z!~gE%Zd>!Am8OB3@@J9u)3x?h1zFs{x27+UiAO~ literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/camera-widget/viewfinder.png b/src/assets/images/room-widgets/camera-widget/viewfinder.png new file mode 100644 index 0000000000000000000000000000000000000000..ab6a9b24f6bfbe61befeddf8e75e1690811a46e6 GIT binary patch literal 959 zcmeAS@N?(olHy`uVBq!ia0y~yV6+2a4mO~Oh((P70|RrLr;B4q#jUq@eNVk|5NNx& z#!UJQ)9Hza8z#Q{a=x0c<1l|vC_k6@`$C`=28P=!SFQefI(V!0x6YN)DH|(_58ARW zyJ38~A8NPV+=HY? zq2`7$*dAIE5Bx%7*T&CVmCkcne(CarvpQlvg;&y(WQq3Bv{hy!3{t>X-)^+da%@`v1?sM~#X{vlv{5lGnoFt1q~FISuQ zCG3B7&&`$BrBfOO>}#&F0woiCdj2S*N2pc`=9l%#X|um6hrgfX)-x)yALJser}% zJO7x?#Rw*xA$^9Yz$J~%LJ1zys1X2(A8whz{^{zke==dQj;cZj)~$N=Nn}H|%iEc$ zF2Il$uxsIO;eW{d(D{c0kPAxOH%uXkyOjkhh@@si`ZU3Zhv!AQA?X1|Do6b5>F)dA lJM~sne)@Lm9LU8E>-f1hoe(_ia##<<^>p=fS?83{1OQ0_I2!-} literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/dimmer-widget/dimmer_banner.png b/src/assets/images/room-widgets/dimmer-widget/dimmer_banner.png new file mode 100644 index 0000000000000000000000000000000000000000..fdc6e9fabae522025b080149b464c349738c3014 GIT binary patch literal 1041 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/exchange-credit/exchange-credit-image.png b/src/assets/images/room-widgets/exchange-credit/exchange-credit-image.png new file mode 100644 index 0000000000000000000000000000000000000000..eef5da6cb5e1bcc91db1e615b5600064da805d73 GIT binary patch literal 9507 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 B4|V_m literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/furni-context-menu/monsterplant-preview.png b/src/assets/images/room-widgets/furni-context-menu/monsterplant-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..8d3d771ef46781f618464db09215eff640b7aa74 GIT binary patch literal 2048 zcmbW2`9Bj39LJX{rqSVyJn@v9BoT6@FgbD#Ifh}vMrh8+7$L*s%n?bvupvid_Atwa z4G{^`9GiF?QPP1td;W#z^Ll+g@7L#-&+Gf!_wgM&OAzoZ5C8yxz*aXMxpd>&S&)~T zmmdQXxi}W-Xn6yGAC+C>7QP@eTQdNl6)Sw;%g^oYcWj(*ap?z#XXWMQWyDq$roMa< z(%wyY-d2jvfOvT8IRt~T3AXtKLH8h+y4&l5y=@!uk3$hwmvwaBknn?qrz7viQ(`lbUS3_Byx3Qo&>#0u6g`CC#*h)Sp>celS&k$m&_c*>Ckl zfK`AhQUcD}pk!x>qV$;B0yGVwF_~+M3({@P3O-t=9c@oemxa-KaDP?SG8hb4388cO zhrsw&PDlS=0Aj{^9d}14+{!%?01%=4=VLTvJq!R4cmlp@=ImzKF%62J za68Gr5X=*N-ZUE5h|daBnZ;mL-)lzyJUR*)D>Vcoj1i{$8)m@Nl@TqJRfFuXl}6{z zYbPFsAP2DlG&x*KbopAGN*K|wNV3lv)`JwpuUim7IrUckJCO5E=f#A1?u9 zL5iE^*gOX6^q;YDsZ?UrUjp>h0R2CP;N^S6JbEufnh*H``j&UxUKy8$_krm7r{%Ew zu(b%Z_Cj9vcn;VNK}IMJLq(QFq)8k&vOqJMRU%tJy z^@^3*ro6}}mDyK80_W4&jtNp*YJD4Db*?d*K1kRrv`M=q5cTBKjB zs1m2CQPW+6iWp+;jE^V6!7AU2vUc=6TdtjJQK`^e^kaIO&!Z?dY;E$CY9o~FRIWJq z4}H&l{8n$foa~icmLjtBg4~JL?g-3B;)1!ENQBElX2tbnbe2j!RO9%mfX=1RjI{EL z4yP@XI^AU_D%}qwMWdB>r+un@9S|o7SY?$;vGC zO-=dc6fr@Z|Nfbk2=ANqK(|Fb?~JTr=}DDiu$*NvL@g-tiM46kC|j%;5#T#p`p*Mi>Ri;{gH`enzT z?&8LIROQ#5v5?REs=nlV3F~w6{-XI@v=+=p+Se8{VI(CDeQ%CHkp;}D(^UoI0sU9d z$D2G-5J6;q#?$nqtaUz4gWox$(Z`Z=;CEi(LQa314!wx|?qL?}jZHre#6t%q^COho z**vZdI-#?cCl`g(sCpOt)%=b%VyLBA1N}qyx zz}tt{ReGgiiK$#FF523-2h5zhz~)co_PAu18rl7FK$07yXs@iV(Cv9 zR%TDA%aEu-n!FpHc~iiRK#F7QvVu=@~e z8z`n|8HWZ&_~7{G`MYP>7hYdaX6`E^zE`x=_?{^mu|^Fz;`HJKowM<)@=qaoX@X=ik96b8*ISBu$L@Xy&^-ITY zgUqCzRGz)^Oq?)q>Xp3AL&30N-n-<4{a+(W^ElB;r}wJ*bXU^e+1pC55}_k^V!zsw zzV#apmO!!rab$(7G5z-PbDO9hZNkSVG=HrC8v*5jh-BG;+3ht4;A__}%h*409~@TJ zn2GxhhIRZ{W6#2aAIoAfL3NQ8n0$-gE+J$A!a;j-;CZk3ae13=g2>Osm-gOdJS+f9 z@zD04mN#MUvcY3i?MXy41~Y--F)+>Fru$XqMmFJk<$t9mDZSz90zWiyN90(L?(U+ zoN>PCz!%y`3;3fOx+_s%pvCrE&`~cSPZ$|ZS-)=+ogQHdZ2Tg!AW5HM+ZdBfi6(Vu z>_hJ2-o8YVNnBajf_g2TQ}20{ogE_2R_tLRtLH+pWKtCDrhIm+_n^$#?cXbbpD_eE ztA|W#P&3<_vp;5AVhfm;N7bp$psRJ`8T!=2s4n%cHC^B0cbrep+4&kwyC1Nh(wn>U zDQR<{#sr>C1M<}U%S8x)@!(iel3XF~RP84}q`=}C7^A>P$^Y~D>I(ZjkeYv)SIYkH zhS0L%RFqMxjOO0N?XgOwA@c6#4}~sNZ}H$E(d>5;v{G|(rkNA|LzI~lwlva4VRF9} N0DQ~tCjJIAWP@ literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/mannequin-widget/mannequin-spritesheet.png b/src/assets/images/room-widgets/mannequin-widget/mannequin-spritesheet.png new file mode 100644 index 0000000000000000000000000000000000000000..45e11f346dcf87cc6d35244e1940d23995ba07c0 GIT binary patch literal 5719 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 JPDHLkV1o8Ili&aV literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3033020977be1a2dfe98bdb42d51f4538dc3bd87 GIT binary patch literal 680 zcmV;Z0$2TsP){W9?Yu8`}x6c~}}<$jc|=>qq?c z^;vNZ>a~Q5Z?{{d)9K{3-|xNpf~>*VVgWasO;8tr5s;S0|(QCKcdC|Ep z0Bb;iph}JbXuV#$#bV*5a02)~lm*gqzu&z8f=H*f+pSwJmyuh511MdT9Q+I_1=Qpi z;AXR#2O|vOc+D$tS*R4y<#KV)=hMf-;oz#O@=~_|M+YIe$_@gxfs3G!&*es(1|7D3pzKEe96z zfrB5RAe$eE77G8Fhl(eYiMw8}VRFWA93vdARhKMW zBN*p1L2?uDt}t`3>U`dl&%~tT5O4bDe`}91WOl(m|RsmHeqr z&};IidS$9d_BmXHoY1ur$1(-d!LpA5Haqew5X3iCxVBRgXAX6YQ&Q`>QYZ1$8}s4+ zHHnuT^Kf?A!_|dN-aeN-H87SjEEGK)mpJ+nl7r*_u9wyknA&V}Vu+8C8r)bu-x}d^ zfOM?O58f=?7`6x^xUp@j1h^C+A&1(A$^wpI<1PtkA&1=REJ9}VcKHE36C2i>EEie; O00005Jg!SYhW>Kf>jU^K}G~b=tU5b5g?I}AQ2!Dk@8-Rba~g8s_Jgf$HwlGO5@Mi z?m2y{`a3>2=+>=UxBqr{c=*-gn=bF-rU2gF-dulrdRp=O`}@K70$>IxvIro+U0+`h zzEki#NNOJ+AA>(XKYt(1A0HoA{O;~<@CY2k+w^ren}YHH!CMsua93AXgDbRO1+E5^ zXYcRtgBuVW0S10^bK~YTgLZy?{sY=pgNr~#N&+Bwt-=hN`NhS>;2f3)4ZhXjGDSs@ z)IL8yzg}KmzK`Y)4-YGTdwcssT?Xy+^mK3rWYBPqHV|6@D+@gjl7V3_DGZ^p6^H5;@DSO%*# zETg0!C43G?e%+ukB!*_!0CmjNkpMo! zkUVI7O_M+aFs=y>MawlAtO%TEQ7Z`u9xQGR8B_!bk71~IKpO>2VR4NstPEbMJ5ig% zG=FK%(hyWgoQF|Xu6(4MwV`>y5c^z99uzz;bt&|KZCzL|b%WDAg=ikL=&fpl7 zmPbKFkGVVwZ4zMUC40F+)usjWU@0ifXPum!d>`TVT)|~Iipx-Hj79_VU@@?$cB3$e zU;zR^F%)$LXGexss-#Hy3T!QCc9JDlPhH$}YyRM5+X^dF8NcsA%R+7ul>r#fR%XDs zIoEu`g|=e^i&b`XbhP5Ct_)h1H)~Zk2{6oFa#QZX^M)c^+sIiSEC70aeO++|=Rqr} zY*t_%ECuF`-58iREU_cQvI|yN1}8Fr%b-QwnOXB#NnH#}AA2(d9I618!Lp-nHsSXa zS_`mQR0d#J+p}XWFr8vy)kW&^V0mRyXmhJ<6fmsy=^RuBjGr?whGmcK;xkwtv@t5% z7%=Z~1k8>K46p#9rZuv2JeB=gVEh`McVLacaO}Xk!o~w@$zgk!^w(`A_o7=-+Yi%D zY}6Oq$&PF7&`5_#(E{qu4vivc>x?D6$;P<`-`Py>*3VYx96M$#_G)^kG#{4DiRskK zk`st6d2KYX(iC+xU7gLZqkua#MGa`tB1p{&^Ik22)BuZ?;j{4Y3Om;_8~@JY%WS2! z$!HngOTbZ$Gq^bya%PF|T z2pZQ!3p9PLMr|H2hBkJ~rYC|)U;!A%f8P{5BeWWD{Q6F8JNJO~8ygmYR_b2t)U7k? zKwAeKo2RfWEVu6FtXgRO_PKWi@%als{z;ltjIA1r|=lyUjH4I8t;`*<+LiyOXNEP$}`<; vqF_pWFa7+cuV-;n043jadAoJ%wpI2QHV9BE9t?&*00000NkvXXu0mjf-JODz literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9d1635d83ea95063669963bb6724495bb57eaea5 GIT binary patch literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^Vn8g!!3HGbRrPd%RJNy!V@SoEtyd26HYflT;xDC#cp>*f#7Xk*QIai{9S36%_P;fqX@FY<5)Y(CfjkKwAxtyhLmpLqhU OW$<+Mb6Mw<&;$U|mON+x literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..900f99b4d7014aa4554a7e03a2ab7a580c550352 GIT binary patch literal 111 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`W}YsNAr*6y6A}`B^fNDJp7ZM8 zeO?}ygFAQ*EX}*p;Fx$IO;}i@=QKmiD>q&V5w-^hB-j!TGh}WIWH?#Wdm3m0gQu&X J%Q~loCIF@%BK`mX literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ec5fef47dce0ad5cb6ebb5e32da844a8bb0bd313 GIT binary patch literal 114 zcmeAS@N?(olHy`uVBq!ia0vp^LO?9S!3HE7rssMADN9cm$B>FS$rVxY|K-Kg9GEMv zHgV)IOn0=n+9zd_C1A?4tIhe7lvwkJRkEfTR}``&Tey>rImFo*LM>kH_*_t<1T=)f M)78&qol`;+04t#%-v9sr literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0e3449d161276ae0e82e7a956d76098c099fbde7 GIT binary patch literal 309 zcmV-50m}Y~P)*{D6Ir^!I3%(k26bHQU=n1@vIP%QLU`~n zCFGafei%%GNc-V1>wXxl_5KMP2L?a+l`r0%aj`RuuAg*FN)P59uw*u%*G6$(EtfM}eu9k^c!(T>!@h1zp|N vcE-<~CMAsP@|P^+bUDPX@Q^jbBA-F-zh0bQ>+IP;TNpfD{an^LB{Ts5<%TfM literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-blue.png b/src/assets/images/room-widgets/stickie-widget/stickie-blue.png new file mode 100644 index 0000000000000000000000000000000000000000..9a14182e515e449d5e6fe27417fc085859e50395 GIT binary patch literal 401 zcmeAS@N?(olHy`uVBq!ia0vp^JArr;2Q!e=+%j)1kYX$ja(7}_cTVOdki(Mh=_Wunup>$LgbmML}{cq!Q_Wsr1*Wb>XWB+*a=lMS#{V}(* zs6YDsS^dYIKkI99|Mm5s?f-G@Pfz~Y^B~IpnRDFp<*$FSZ54k1?%*y93t$K^c)I$z JtaD0e0suDw%me@c literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-christmas.png b/src/assets/images/room-widgets/stickie-widget/stickie-christmas.png new file mode 100644 index 0000000000000000000000000000000000000000..82b4732fa8c57e023ac61336c798ba32bf622b7b GIT binary patch literal 1272 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�v~n=?I0_(WlwAn@duV^Jm-?w~x+k=33l#}t#C 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-green.png b/src/assets/images/room-widgets/stickie-widget/stickie-green.png new file mode 100644 index 0000000000000000000000000000000000000000..5e73c74733bd4c64de6da0e677e7c54f4947a3b7 GIT binary patch literal 401 zcmeAS@N?(olHy`uVBq!ia0vp^JArr;2Q!e=+%j)1kYX$ja(7}_cTVOdki(Mh=lBSEK+1zj?YihE&A8J=f^#6v*Ie81Vm}Hh0%2HH(wJw>Yb$%kHP# zGk!nw`u+bxv41@I^ZXx={+QcY z)F1u+tp4N9pY=7l|N8pR_W!u{rzijHc@Sm)%sKA)^4GuEwhF(0cW{@51uz5{JYD@< J);T3K0RYHh%dY?c literal 0 HcmV?d00001 diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-heart.png b/src/assets/images/room-widgets/stickie-widget/stickie-heart.png new file mode 100644 index 0000000000000000000000000000000000000000..455238513ae84ee27c35e49718b3549aefe70781 GIT binary patch literal 945 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