diff --git a/.gitignore b/.gitignore index 154341fb1..985afd10a 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ Thumbs.db .env public/renderer-config* public/ui-config* +renderer-config* +ui-config* diff --git a/README.md b/README.md index e3a3e01b8..98b590ef5 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ Thanks to everyone that has helped out contributing to this UI: - Laynester - Dennis - Object +- Habbobba +- Tardelivinicius # Nitro Client installation @@ -70,5 +72,5 @@ To build a production version of Nitro just run the following command yarn build:prod ``` -- A `build` folder will be generated, these are the files that must be uploaded to your webserver +- 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/public/renderer-config.json.example b/public/renderer-config.json.example index dbcbcc8c6..854a4ddf9 100644 --- a/public/renderer-config.json.example +++ b/public/renderer-config.json.example @@ -88,7 +88,7 @@ "bearbaby", "terrierbaby", "gnome", - "gnome", + "leprechaun", "kittenbaby", "puppybaby", "pigletbaby", @@ -97,9 +97,7 @@ "pterosaur", "velociraptor", "cow", - "LeetPen", - "bbwibb", - "elephants" + "dragondog" ], "preload.assets.urls": [ "${asset.url}/bundled/generic/avatar_additions.nitro", diff --git a/public/ui-config.json.example b/public/ui-config.json.example index b8cfc707f..72f1ffbc0 100644 --- a/public/ui-config.json.example +++ b/public/ui-config.json.example @@ -133,6 +133,7 @@ "catalog.asset.icon.url": "${catalog.asset.url}/icon_%name%.png", "catalog.tab.icons": false, "catalog.headers": false, + "catalog.gifts.show.my.face": true, "chat.input.maxlength": 100, "chat.styles.disabled": [], "chat.styles": [ diff --git a/src/App.scss b/src/App.scss index 1fb9ec31b..80268e517 100644 --- a/src/App.scss +++ b/src/App.scss @@ -16,22 +16,22 @@ $grid-border-color: $muted; $grid-active-bg-color: #ececec; $grid-active-border-color: $white; -$toolbar-height: 55px; +$toolbar-height: 51px; $achievement-width: 375px; $achievement-height: 405px; -$avatar-editor-width: 660px; -$avatar-editor-height: 470px; +$avatar-editor-width: 500px; +$avatar-editor-height: 520px; -$catalog-width: 570px; +$catalog-width: 600px; $catalog-height: 635px; -$inventory-width: 528px; -$inventory-height: 320px; +$inventory-width: 500px; +$inventory-height: 342px; $navigator-width: 425px; -$navigator-height: 560px; +$navigator-height: 500px; $navigator-min-height: 205px; $nitro-room-creator-width: 585px; @@ -41,7 +41,7 @@ $chat-input-style-selector-widget-width: 210px; $chat-input-style-selector-widget-height: 500px; $user-profile-width: 470px; -$user-profile-height: 460px; +$user-profile-height: 538px; $nitro-widget-custom-stack-height-width: 275px; $nitro-widget-custom-stack-height-height: 220px; @@ -75,7 +75,7 @@ $camera-editor-height: 500px; $camera-checkout-width: 350px; -$room-info-width: 230px; +$room-info-width: 236px; $nitro-group-creator-width: 383px; $nitro-mod-tools-width: 170px; diff --git a/src/api/catalog/CatalogGetTypePrice.ts b/src/api/catalog/CatalogGetTypePrice.ts new file mode 100644 index 000000000..fee193378 --- /dev/null +++ b/src/api/catalog/CatalogGetTypePrice.ts @@ -0,0 +1,11 @@ +export const getTypePrice = (type: string) => +{ + const priceState = { + 'price_type_none' : 'grid-bg', + 'price_type_credits' : 'credits-bg', + 'price_type_activitypoints' : 'duckets-bg', + 'price_type_credits_and_activitypoints' : 'grid-bg' + }; + + return priceState[type]; +} diff --git a/src/api/catalog/index.ts b/src/api/catalog/index.ts index 6c5b9e2ea..49d91a3b2 100644 --- a/src/api/catalog/index.ts +++ b/src/api/catalog/index.ts @@ -1,4 +1,5 @@ export * from './BuilderFurniPlaceableStatus'; +export * from './CatalogGetTypePrice'; export * from './CatalogNode'; export * from './CatalogPage'; export * from './CatalogPageName'; diff --git a/src/api/friends/FriendListTabs.ts b/src/api/friends/FriendListTabs.ts new file mode 100644 index 000000000..bf391f725 --- /dev/null +++ b/src/api/friends/FriendListTabs.ts @@ -0,0 +1,6 @@ +export class FriendListTabs +{ + public static readonly YOUR_FRIENDS: string = 'friendlist.tip.tab.1'; + public static readonly REQUESTS: string = 'friendlist.tip.tab.2'; + public static readonly SEARCH_HABBOS: string = 'friendlist.tip.tab.3'; +} diff --git a/src/api/friends/MessengerFollowFriendFailedType.ts b/src/api/friends/MessengerFollowFriendFailedType.ts new file mode 100644 index 000000000..3450bbcfd --- /dev/null +++ b/src/api/friends/MessengerFollowFriendFailedType.ts @@ -0,0 +1,7 @@ +export class MessengerFollowFriendFailedType +{ + public static readonly NOT_IN_FRIEND_LIST = 0; + public static readonly FRIEND_OFFLINE = 1; + public static readonly FRIEND_NOT_IN_ROOM = 2; + public static readonly FRIEND_BLOCKED_STALKING = 3; +} diff --git a/src/api/friends/index.ts b/src/api/friends/index.ts index ce1ed60a0..2f78f8daf 100644 --- a/src/api/friends/index.ts +++ b/src/api/friends/index.ts @@ -1,5 +1,7 @@ +export * from './FriendListTabs'; export * from './GetGroupChatData'; export * from './IGroupChatData'; +export * from './MessengerFollowFriendFailedType'; export * from './MessengerFriend'; export * from './MessengerGroupType'; export * from './MessengerIconState'; diff --git a/src/api/utils/GetLocalStorage.ts b/src/api/utils/GetLocalStorage.ts index 769df6d7a..66faf54ff 100644 --- a/src/api/utils/GetLocalStorage.ts +++ b/src/api/utils/GetLocalStorage.ts @@ -2,7 +2,7 @@ export const GetLocalStorage = (key: string) => { try { - JSON.parse(window.localStorage.getItem(key)) as T ?? null + return JSON.parse(window.localStorage.getItem(key)) as T ?? null } catch(e) { diff --git a/src/api/utils/LocalizeShortNumber.ts b/src/api/utils/LocalizeShortNumber.ts index 30975ecac..d5d9daf2e 100644 --- a/src/api/utils/LocalizeShortNumber.ts +++ b/src/api/utils/LocalizeShortNumber.ts @@ -19,7 +19,7 @@ export function LocalizeShortNumber(number: number): string for(const power of powers) { - let reduced = abs / power.value; + let reduced = parseFloat((abs / power.value).toFixed(0)); reduced = Math.round(reduced * rounder) / rounder; diff --git a/src/assets/flash/buttons/button_gray.png b/src/assets/flash/buttons/button_gray.png new file mode 100644 index 000000000..b2ca49c8d Binary files /dev/null and b/src/assets/flash/buttons/button_gray.png differ diff --git a/src/assets/flash/buttons/button_gray_active.png b/src/assets/flash/buttons/button_gray_active.png new file mode 100644 index 000000000..0b60ede90 Binary files /dev/null and b/src/assets/flash/buttons/button_gray_active.png differ diff --git a/src/assets/flash/buttons/button_gray_hover.png b/src/assets/flash/buttons/button_gray_hover.png new file mode 100644 index 000000000..c6170ec9b Binary files /dev/null and b/src/assets/flash/buttons/button_gray_hover.png differ diff --git a/src/assets/flash/camera/camera_button_left.png b/src/assets/flash/camera/camera_button_left.png new file mode 100644 index 000000000..dfc113040 Binary files /dev/null and b/src/assets/flash/camera/camera_button_left.png differ diff --git a/src/assets/flash/camera/camera_button_right.png b/src/assets/flash/camera/camera_button_right.png new file mode 100644 index 000000000..5d78f71f9 Binary files /dev/null and b/src/assets/flash/camera/camera_button_right.png differ diff --git a/src/assets/flash/camera/report.png b/src/assets/flash/camera/report.png new file mode 100644 index 000000000..db3867c83 Binary files /dev/null and b/src/assets/flash/camera/report.png differ diff --git a/src/assets/flash/camera/report_click.png b/src/assets/flash/camera/report_click.png new file mode 100644 index 000000000..763d2fcb0 Binary files /dev/null and b/src/assets/flash/camera/report_click.png differ diff --git a/src/assets/flash/camera/report_hover.png b/src/assets/flash/camera/report_hover.png new file mode 100644 index 000000000..ba87577da Binary files /dev/null and b/src/assets/flash/camera/report_hover.png differ diff --git a/src/assets/flash/catalogue/chevron_left_gift.png b/src/assets/flash/catalogue/chevron_left_gift.png new file mode 100644 index 000000000..0ea9ffc53 Binary files /dev/null and b/src/assets/flash/catalogue/chevron_left_gift.png differ diff --git a/src/assets/flash/catalogue/chevron_right_gift.png b/src/assets/flash/catalogue/chevron_right_gift.png new file mode 100644 index 000000000..e9a6b12e2 Binary files /dev/null and b/src/assets/flash/catalogue/chevron_right_gift.png differ diff --git a/src/assets/flash/catalogue/credits.png b/src/assets/flash/catalogue/credits.png new file mode 100644 index 000000000..c9e12512c Binary files /dev/null and b/src/assets/flash/catalogue/credits.png differ diff --git a/src/assets/flash/catalogue/duckets_bg.png b/src/assets/flash/catalogue/duckets_bg.png new file mode 100644 index 000000000..1155b3443 Binary files /dev/null and b/src/assets/flash/catalogue/duckets_bg.png differ diff --git a/src/assets/flash/catalogue/item-counter-bg.png b/src/assets/flash/catalogue/item-counter-bg.png new file mode 100644 index 000000000..fb4dff0ba Binary files /dev/null and b/src/assets/flash/catalogue/item-counter-bg.png differ diff --git a/src/assets/flash/catalogue/marketplace_bg.png b/src/assets/flash/catalogue/marketplace_bg.png new file mode 100644 index 000000000..83d2d3eff Binary files /dev/null and b/src/assets/flash/catalogue/marketplace_bg.png differ diff --git a/src/assets/flash/friendlist/friend_active_search_bg.png b/src/assets/flash/friendlist/friend_active_search_bg.png new file mode 100644 index 000000000..c5f6dc543 Binary files /dev/null and b/src/assets/flash/friendlist/friend_active_search_bg.png differ diff --git a/src/assets/flash/friendlist/friend_message.png b/src/assets/flash/friendlist/friend_message.png new file mode 100644 index 000000000..ae6f930d5 Binary files /dev/null and b/src/assets/flash/friendlist/friend_message.png differ diff --git a/src/assets/flash/friendlist/friendlist_arrow_black_down.png b/src/assets/flash/friendlist/friendlist_arrow_black_down.png new file mode 100644 index 000000000..04cd1ed0c Binary files /dev/null and b/src/assets/flash/friendlist/friendlist_arrow_black_down.png differ diff --git a/src/assets/flash/friendlist/friendlist_arrow_black_right.png b/src/assets/flash/friendlist/friendlist_arrow_black_right.png new file mode 100644 index 000000000..e25eb99eb Binary files /dev/null and b/src/assets/flash/friendlist/friendlist_arrow_black_right.png differ diff --git a/src/assets/flash/friendlist/friendlist_arrow_white_down.png b/src/assets/flash/friendlist/friendlist_arrow_white_down.png new file mode 100644 index 000000000..6202307e2 Binary files /dev/null and b/src/assets/flash/friendlist/friendlist_arrow_white_down.png differ diff --git a/src/assets/flash/friendlist/friendlist_arrow_white_right.png b/src/assets/flash/friendlist/friendlist_arrow_white_right.png new file mode 100644 index 000000000..65148fee2 Binary files /dev/null and b/src/assets/flash/friendlist/friendlist_arrow_white_right.png differ diff --git a/src/assets/flash/friendlist/search_friend.png b/src/assets/flash/friendlist/search_friend.png new file mode 100644 index 000000000..d1fd3caf0 Binary files /dev/null and b/src/assets/flash/friendlist/search_friend.png differ diff --git a/src/assets/flash/friendlist/search_friend_clicked.png b/src/assets/flash/friendlist/search_friend_clicked.png new file mode 100644 index 000000000..0198c2b25 Binary files /dev/null and b/src/assets/flash/friendlist/search_friend_clicked.png differ diff --git a/src/assets/flash/friendlist/search_friend_hover.png b/src/assets/flash/friendlist/search_friend_hover.png new file mode 100644 index 000000000..861669c8f Binary files /dev/null and b/src/assets/flash/friendlist/search_friend_hover.png differ diff --git a/src/assets/flash/habboclub/hc_gift_monthly.png b/src/assets/flash/habboclub/hc_gift_monthly.png new file mode 100644 index 000000000..70e872ee9 Binary files /dev/null and b/src/assets/flash/habboclub/hc_gift_monthly.png differ diff --git a/src/assets/flash/hotelview/halloffame/sheet.png b/src/assets/flash/hotelview/halloffame/sheet.png new file mode 100644 index 000000000..51c887460 Binary files /dev/null and b/src/assets/flash/hotelview/halloffame/sheet.png differ diff --git a/src/assets/flash/infostand/pets/energy.png b/src/assets/flash/infostand/pets/energy.png new file mode 100644 index 000000000..73de3159e Binary files /dev/null and b/src/assets/flash/infostand/pets/energy.png differ diff --git a/src/assets/flash/infostand/pets/experience.png b/src/assets/flash/infostand/pets/experience.png new file mode 100644 index 000000000..6720f81cc Binary files /dev/null and b/src/assets/flash/infostand/pets/experience.png differ diff --git a/src/assets/flash/infostand/pets/happiness.png b/src/assets/flash/infostand/pets/happiness.png new file mode 100644 index 000000000..9ff1a19af Binary files /dev/null and b/src/assets/flash/infostand/pets/happiness.png differ diff --git a/src/assets/flash/infostand/pets/petting.png b/src/assets/flash/infostand/pets/petting.png new file mode 100644 index 000000000..ec558e4f9 Binary files /dev/null and b/src/assets/flash/infostand/pets/petting.png differ diff --git a/src/assets/flash/infostand/pets/wellbeing.png b/src/assets/flash/infostand/pets/wellbeing.png new file mode 100644 index 000000000..9e8e52f91 Binary files /dev/null and b/src/assets/flash/infostand/pets/wellbeing.png differ diff --git a/src/assets/flash/inventory/allow_recycle.png b/src/assets/flash/inventory/allow_recycle.png new file mode 100644 index 000000000..550ca111e Binary files /dev/null and b/src/assets/flash/inventory/allow_recycle.png differ diff --git a/src/assets/flash/inventory/allow_trade.png b/src/assets/flash/inventory/allow_trade.png new file mode 100644 index 000000000..b47fb880b Binary files /dev/null and b/src/assets/flash/inventory/allow_trade.png differ diff --git a/src/assets/flash/inventory/item.png b/src/assets/flash/inventory/item.png new file mode 100644 index 000000000..01a3d2d14 Binary files /dev/null and b/src/assets/flash/inventory/item.png differ diff --git a/src/assets/flash/inventory/not_allow_recycle.png b/src/assets/flash/inventory/not_allow_recycle.png new file mode 100644 index 000000000..febfdc87e Binary files /dev/null and b/src/assets/flash/inventory/not_allow_recycle.png differ diff --git a/src/assets/flash/inventory/not_allow_trade.png b/src/assets/flash/inventory/not_allow_trade.png new file mode 100644 index 000000000..dc07c7944 Binary files /dev/null and b/src/assets/flash/inventory/not_allow_trade.png differ diff --git a/src/assets/flash/inventory/selected_item.png b/src/assets/flash/inventory/selected_item.png new file mode 100644 index 000000000..838b3b4f3 Binary files /dev/null and b/src/assets/flash/inventory/selected_item.png differ diff --git a/src/assets/flash/modtools/button.png b/src/assets/flash/modtools/button.png new file mode 100644 index 000000000..b68c6daf9 Binary files /dev/null and b/src/assets/flash/modtools/button.png differ diff --git a/src/assets/flash/modtools/button_active.png b/src/assets/flash/modtools/button_active.png new file mode 100644 index 000000000..98a54de9e Binary files /dev/null and b/src/assets/flash/modtools/button_active.png differ diff --git a/src/assets/flash/modtools/button_hover.png b/src/assets/flash/modtools/button_hover.png new file mode 100644 index 000000000..8207d12e1 Binary files /dev/null and b/src/assets/flash/modtools/button_hover.png differ diff --git a/src/assets/flash/navigator/promote_room.png b/src/assets/flash/navigator/promote_room.png new file mode 100644 index 000000000..3f912afd5 Binary files /dev/null and b/src/assets/flash/navigator/promote_room.png differ diff --git a/src/assets/flash/navigator/room-creator/tiles_room_selected.png b/src/assets/flash/navigator/room-creator/tiles_room_selected.png new file mode 100644 index 000000000..81c92cf3c Binary files /dev/null and b/src/assets/flash/navigator/room-creator/tiles_room_selected.png differ diff --git a/src/assets/flash/navigator/saves-search/delete_search.png b/src/assets/flash/navigator/saves-search/delete_search.png new file mode 100644 index 000000000..4c35559c9 Binary files /dev/null and b/src/assets/flash/navigator/saves-search/delete_search.png differ diff --git a/src/assets/flash/navigator/saves-search/delete_search_click.png b/src/assets/flash/navigator/saves-search/delete_search_click.png new file mode 100644 index 000000000..657ceb333 Binary files /dev/null and b/src/assets/flash/navigator/saves-search/delete_search_click.png differ diff --git a/src/assets/flash/navigator/saves-search/delete_search_hover.png b/src/assets/flash/navigator/saves-search/delete_search_hover.png new file mode 100644 index 000000000..ad6ccf931 Binary files /dev/null and b/src/assets/flash/navigator/saves-search/delete_search_hover.png differ diff --git a/src/assets/flash/navigator/saves-search/search_save.png b/src/assets/flash/navigator/saves-search/search_save.png new file mode 100644 index 000000000..975fb78c0 Binary files /dev/null and b/src/assets/flash/navigator/saves-search/search_save.png differ diff --git a/src/assets/flash/room/report-room.png b/src/assets/flash/room/report-room.png new file mode 100644 index 000000000..37672aa67 Binary files /dev/null and b/src/assets/flash/room/report-room.png differ diff --git a/src/assets/flash/toolbar/findfriend-bg.png b/src/assets/flash/toolbar/findfriend-bg.png index adb7aaf54..dafdd3fd3 100644 Binary files a/src/assets/flash/toolbar/findfriend-bg.png and b/src/assets/flash/toolbar/findfriend-bg.png differ diff --git a/src/assets/flash/wardrobe/hd.png b/src/assets/flash/wardrobe/hd.png new file mode 100644 index 000000000..307d6f799 Binary files /dev/null and b/src/assets/flash/wardrobe/hd.png differ diff --git a/src/assets/flash/wardrobe/head.png b/src/assets/flash/wardrobe/head.png new file mode 100644 index 000000000..977012d21 Binary files /dev/null and b/src/assets/flash/wardrobe/head.png differ diff --git a/src/assets/flash/wardrobe/legs.png b/src/assets/flash/wardrobe/legs.png new file mode 100644 index 000000000..5978a3af7 Binary files /dev/null and b/src/assets/flash/wardrobe/legs.png differ diff --git a/src/assets/flash/wardrobe/torso.png b/src/assets/flash/wardrobe/torso.png new file mode 100644 index 000000000..a58918ba2 Binary files /dev/null and b/src/assets/flash/wardrobe/torso.png differ diff --git a/src/assets/flash/wardrobe/wardrobe.png b/src/assets/flash/wardrobe/wardrobe.png new file mode 100644 index 000000000..6913da749 Binary files /dev/null and b/src/assets/flash/wardrobe/wardrobe.png differ diff --git a/src/assets/images/catalog/hc_mini.png b/src/assets/images/catalog/hc_mini.png new file mode 100644 index 000000000..353f1cd51 Binary files /dev/null and b/src/assets/images/catalog/hc_mini.png differ diff --git a/src/assets/images/groups/icons/group_icon_room.png b/src/assets/images/groups/icons/group_icon_room.png new file mode 100644 index 000000000..a3fa3a582 Binary files /dev/null and b/src/assets/images/groups/icons/group_icon_room.png differ diff --git a/src/assets/images/icons/favourite-room-active.png b/src/assets/images/icons/favourite-room-active.png new file mode 100644 index 000000000..b892a7dd3 Binary files /dev/null and b/src/assets/images/icons/favourite-room-active.png differ diff --git a/src/assets/images/icons/favourite-room-inactive.png b/src/assets/images/icons/favourite-room-inactive.png new file mode 100644 index 000000000..4b9f44756 Binary files /dev/null and b/src/assets/images/icons/favourite-room-inactive.png differ diff --git a/src/assets/images/icons/room-promote.png b/src/assets/images/icons/room-promote.png new file mode 100644 index 000000000..cc83666b8 Binary files /dev/null and b/src/assets/images/icons/room-promote.png differ diff --git a/src/assets/styles/bootstrap/_buttons.scss b/src/assets/styles/bootstrap/_buttons.scss index 4596abb48..c0d5fcf59 100644 --- a/src/assets/styles/bootstrap/_buttons.scss +++ b/src/assets/styles/bootstrap/_buttons.scss @@ -54,6 +54,34 @@ } +.btn-gray { + border-image-source: url("@/assets/flash/buttons/button_gray.png"); + color: #fff; + font-family: ubuntuBold; + + &:hover { + border-image-source: url("@/assets/flash/buttons/button_gray_hover.png"); + } + + &:active { + border-image-source: url("@/assets/flash/buttons/button_gray_active.png"); + } +} + +.btn-mod-tools { + border-image-source: url("@/assets/flash/modtools/button.png"); + color: #fff; + font-family: ubuntuBold; + + &:hover { + border-image-source: url("@/assets/flash/modtools/button_hover.png"); + } + + &:active { + border-image-source: url("@/assets/flash/modtools/button_active.png"); + } +} + // scss-docs-end btn-variant-loops @@ -168,6 +196,10 @@ &:active { border-image-source: url(@/assets/flash/buttons/volter_normal_btn_active.png); } + + &.active { + border-image-source: url(@/assets/flash/buttons/volter_normal_btn_active.png); + } } .notification-buttons { @@ -197,6 +229,9 @@ outline: 1px solid #000; border: 3px solid #fff; min-height: 22px; - min-width: 23px; + min-width: 27px; + &.active { + border: 5px solid #fff; + } } diff --git a/src/assets/styles/icons.scss b/src/assets/styles/icons.scss index a310b9f09..ee8df67dd 100644 --- a/src/assets/styles/icons.scss +++ b/src/assets/styles/icons.scss @@ -290,19 +290,26 @@ } &.icon-lock-open { - background: url(@/assets/flash/inventory/lock_open.png); + background: url('@/assets/flash/inventory/lock_open.png'); background-repeat: no-repeat; height: 43px; width: 29px; } &.icon-lock-locked { - background: url(@/assets/flash/inventory/lock_locked.png); + background: url('@/assets/flash/inventory/lock_locked.png'); background-repeat: no-repeat; height: 44px; width: 36px; } + &.icon-confirmed { + background: url('@/assets/flash/inventory/confirmed.png'); + background-repeat: no-repeat; + height: 18px; + width: 18px; + } + &.icon-sign-heart { background: url('@/assets/images/icons/sign-heart.png'); width: 15px; @@ -345,12 +352,61 @@ height: 14px; } + &.icon-favourite-room-active { + background: url('@/assets/images/icons/favourite-room-active.png'); + width: 14px; + height: 14px; + } + + &.icon-favourite-room-inactive { + background: url('@/assets/images/icons/favourite-room-inactive.png'); + width: 12px; + height: 11px; + } + + &.icon-room-promote { + background: url('@/assets/images/icons/room-promote.png'); + width: 16px; + height: 19px; + } + + &.icon-notification_arrow_left { + background: url('@/assets/flash/notifications/notification_arrow_left.png'); + width: 18px; + height: 19px; + } + + &.icon-notification_arrow_down { + background: url('@/assets/flash/notifications/notification_arrow_down.png'); + width: 18px; + height: 19px; + } + + &.icon-nitro-card-header-close { + background: url('@/assets/flash/messenger/close.png'); + width: 20px; + height: 20px; + } + + &.icon-group_icon_room { + background: url('@/assets/images/groups/icons/group_icon_room.png'); + width: 21px; + height: 16px; + } + &.icon-camera-small { background: url('@/assets/images/icons/camera-small.png'); width: 17px; height: 15px; } + &.icon-report-room { + background: url('@/assets/flash/room/report-room.png'); + width: 19px; + height: 19px; + margin-right: 10px; + } + &.icon-small-room { background: url('@/assets/images/icons/small-room.png'); width: 17px; @@ -765,7 +821,7 @@ } &.icon-tickets { - background-image: url('@/assets/images/icons/tickets.png'); + background-image: url('@/assets/flash/modtools/tickets.png'); width: 17px; height: 17px; } @@ -818,6 +874,114 @@ height: 38px; } + &.icon-search_save { + background-image: url('@/assets/flash/navigator/saves-search/search_save.png'); + width: 18px; + height: 18px; + } + + &.icon-hc_mini { + background-image: url('@/assets/images/catalog/hc_mini.png'); + width: 18px; + height: 18px; + } + + &.icon-sheet { + background-image: url('@/assets/flash/hotelview/halloffame/sheet.png'); + width: 16px; + height: 12px; + } + + &.icon-tiles { + background-image: url('@/assets/flash/navigator/room-creator/tiles.png'); + width: 16px; + height: 12px; + } + + &.icon-tiles_room_selected { + background-image: url('@/assets/flash/navigator/room-creator/tiles_room_selected.png'); + width: 16px; + height: 12px; + } + + &.icon-friendlist_arrow_black_down { + background-image: url('@/assets/flash/friendlist/friendlist_arrow_black_down.png'); + width: 16px; + height: 12px; + } + + &.icon-friendlist_arrow_black_right { + background-image: url('@/assets/flash/friendlist/friendlist_arrow_black_right.png'); + width: 16px; + height: 12px; + } + + &.icon-friendlist_arrow_white_down { + background-image: url('@/assets/flash/friendlist/friendlist_arrow_white_down.png'); + width: 16px; + height: 12px; + } + + &.icon-friendlist_arrow_white_right { + background-image: url('@/assets/flash/friendlist/friendlist_arrow_white_right.png'); + width: 16px; + height: 12px; + } + + &.icon-friend_message { + background-image: url('@/assets/flash/friendlist/friend_message.png'); + width: 16px; + height: 14px; + } + + &.icon-hc_gift_monthly { + background-image: url('@/assets/flash/habboclub/hc_gift_monthly.png'); + width: 16px; + height: 14px; + } + + &.icon-chevron_left_gift { + background-image: url('@/assets/flash/catalogue/chevron_left_gift.png'); + width: 10px; + height: 10px; + } + + &.icon-chevron_right_gift { + background-image: url('@/assets/flash/catalogue/chevron_right_gift.png'); + width: 10px; + height: 10px; + } + + &.icon-credits { + background-image: url('@/assets/flash/catalogue/credits.png'); + width: 21px; + height: 21px; + } + + &.icon-recyclable { + background-image: url('@/assets/flash/inventory/allow_recycle.png'); + width: 21px; + height: 21px; + } + + &.icon-not-recyclable { + background-image: url('@/assets/flash/inventory/not_allow_recycle.png'); + width: 29px; + height: 21px; + } + + &.icon-tradeable { + background-image: url('@/assets/flash/inventory/allow_trade.png'); + width: 31px; + height: 15px; + } + + &.icon-not-tradeable { + background-image: url('@/assets/flash/inventory/not_allow_trade.png'); + width: 40px; + height: 16px; + } + &.spin { animation: rotating 1s linear infinite; } diff --git a/src/common/AutoGrid.tsx b/src/common/AutoGrid.tsx index c387892ad..b204eabd3 100644 --- a/src/common/AutoGrid.tsx +++ b/src/common/AutoGrid.tsx @@ -1,22 +1,24 @@ import { CSSProperties, FC, useMemo } from 'react'; import { Grid, GridProps } from './Grid'; +import {SpacingType} from "./types"; export interface AutoGridProps extends GridProps { columnMinWidth?: number; columnMinHeight?: number; + gap?: SpacingType; } export const AutoGrid: FC = props => { - const { columnMinWidth = 40, columnMinHeight = 40, columnCount = 0, fullHeight = false, maxContent = true, overflow = 'auto', style = {}, ...rest } = props; + const { columnMinWidth = 40, columnMinHeight = 40, gap = 2, columnCount = 0, fullHeight = false, maxContent = true, overflow = 'auto', style = {}, ...rest } = props; const getStyle = useMemo(() => { let newStyle: CSSProperties = {}; newStyle['--nitro-grid-column-min-height'] = (columnMinHeight + 'px'); - + if(columnCount > 1) newStyle.gridTemplateColumns = `repeat(auto-fill, minmax(${ columnMinWidth }px, 1fr))`; if(Object.keys(style).length) newStyle = { ...newStyle, ...style }; @@ -24,5 +26,5 @@ export const AutoGrid: FC = props => return newStyle; }, [ columnMinWidth, columnMinHeight, columnCount, style ]); - return ; + return ; } diff --git a/src/common/card/NitroCardHeaderView.tsx b/src/common/card/NitroCardHeaderView.tsx index 3af8bfcb1..06e0cee83 100644 --- a/src/common/card/NitroCardHeaderView.tsx +++ b/src/common/card/NitroCardHeaderView.tsx @@ -7,14 +7,16 @@ interface NitroCardHeaderViewProps extends ColumnProps headerText: string; isGalleryPhoto?: boolean; noCloseButton?: boolean; + isInfoToHabboPages?: boolean; hideButtonClose?: boolean; onReportPhoto?: (event: MouseEvent) => void; + onClickInfoHabboPages?: (event: MouseEvent) => void; onCloseClick: (event: MouseEvent) => void; } export const NitroCardHeaderView: FC = props => { - const { headerText = null, isGalleryPhoto = false, noCloseButton = false, hideButtonClose = false, onReportPhoto = null, onCloseClick = null, justifyContent = 'center', alignItems = 'center', classNames = [], children = null, ...rest } = props; + const { headerText = null, isGalleryPhoto = false, noCloseButton = false, isInfoToHabboPages = false, hideButtonClose = false, onReportPhoto = null, onClickInfoHabboPages = null, onCloseClick = null, justifyContent = 'center', alignItems = 'center', classNames = [], children = null, ...rest } = props; const getClassNames = useMemo(() => { @@ -40,6 +42,9 @@ export const NitroCardHeaderView: FC = props => } + { isInfoToHabboPages && + + } { (!hideButtonClose) && } diff --git a/src/common/card/NitroCardView.scss b/src/common/card/NitroCardView.scss index 7ee695bbb..03abbf5f5 100644 --- a/src/common/card/NitroCardView.scss +++ b/src/common/card/NitroCardView.scss @@ -49,6 +49,22 @@ $nitro-card-tabs-height: 35px; } } + + .nitro-card-header-info-habbopages { + cursor: pointer; + background-image: url("@/assets/flash/boxes/card/questionmark.png"); + width: 19px; + height: 20px; + right: 32px; + + &:hover { + background-image: url("@/assets/flash/boxes/card/questionmark_hover.png"); + + &:active { + background-image: url("@/assets/flash/boxes/card/questionmark_click.png"); + } + } + } } .nitro-card-tabs { @@ -80,11 +96,12 @@ $nitro-card-tabs-height: 35px; .nitro-card-header-text { + position: absolute; color: $black; min-height: 21px; - font-size: 10px; - margin-top: 3px; + font-size: 12px; text-shadow: 0px 1px 0px #fff, 1px 0px 1px #aaaaaa; + left: 8px; } .nitro-card-header-close { @@ -252,7 +269,7 @@ $nitro-card-tabs-height: 35px; border: 1px solid #000; -webkit-box-shadow: inset 0px 1px 0px 0px #555555; box-shadow: inset 0px 1px 0px 0px #555555; - background-size: 1px 18px; + background-size: 1px 21px; margin-top: -1px; } @@ -263,7 +280,7 @@ $nitro-card-tabs-height: 35px; border: 1px solid #000; -webkit-box-shadow: inset 0px 1px 0px 0px #dc8530; box-shadow: inset 0px 1px 0px 0px #dc8530; - background-size: 1px 18px; + background-size: 1px 21px; } .friend-headers { @@ -273,17 +290,25 @@ $nitro-card-tabs-height: 35px; border-bottom: none; -webkit-box-shadow: inset 0px 1px 0px 0px #59bfff; box-shadow: inset 0px 1px 0px 0px #59bfff; - background-size: 1px 18px; + background-size: 1px 21px; } .nitro-card-accordion-set-content { background: #fff; } + .nitro-card-accordion-set-content-gray { + background: #B6B6B6; + } + .friend-tab:nth-of-type(odd) { background: #eeeeee; } + .friend-tab-search:nth-of-type(odd) { + background: #9F9F9F; + } + .friend-active { background: #3cb8ff !important; } @@ -297,6 +322,15 @@ $nitro-card-tabs-height: 35px; border-image-slice: 7 7 7 7 fill; border-image-width: 7px 7px 7px 7px; border-image-repeat: repeat repeat; + max-height: 100%; + width: 100% + } + + .friend-active_search-tab { + border-image-source: url("@/assets/flash/friendlist/friend_active_search_bg.png"); + border-image-slice: 7 7 7 7 fill; + border-image-width: 7px 7px 7px 7px; + border-image-repeat: repeat repeat; max-height: 30px; width: 100% } @@ -363,6 +397,21 @@ $nitro-card-tabs-height: 35px; } } + .search-friend-icon { + background-image: url('@/assets/flash/friendlist/search_friend.png'); + background-repeat: no-repeat; + width: 100%; + height: 22px; + + &:hover { + background-image: url('@/assets/flash/friendlist/search_friend_hover.png'); + } + + &:active { + background-image: url('@/assets/flash/friendlist/search_friend_clicked.png'); + } + } + .nitro-card-header { min-height: 25px; max-height: 25px; diff --git a/src/common/card/accordion/NitroCardAccordionSetInnerView.tsx b/src/common/card/accordion/NitroCardAccordionSetInnerView.tsx index 4bc4b4e97..2af7c2404 100644 --- a/src/common/card/accordion/NitroCardAccordionSetInnerView.tsx +++ b/src/common/card/accordion/NitroCardAccordionSetInnerView.tsx @@ -1,7 +1,6 @@ import { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { Column, ColumnProps, Flex, Text } from '../..'; +import { Base, Column, ColumnProps, Flex, Text } from '../..'; import { useNitroCardAccordionContext } from './NitroCardAccordionContext'; -import { FaChevronDown, FaChevronUp } from 'react-icons/fa'; export interface NitroCardAccordionSetInnerViewProps extends ColumnProps { @@ -25,7 +24,7 @@ export const NitroCardAccordionSetInnerView: FC { - const newClassNames = [ 'nitro-card-accordion-set' ]; + const newClassNames = [ '' ]; if(isOpen) newClassNames.push('active'); @@ -44,40 +43,43 @@ export const NitroCardAccordionSetInnerView: FC - { - const newClosers = [ ...prevValue ]; + { + const newClosers = [ ...prevValue ]; - newClosers.push(closeFunction); + newClosers.push(closeFunction); - return newClosers; - }); + return newClosers; + }); return () => { setClosers(prevValue => - { - const newClosers = [ ...prevValue ]; + { + const newClosers = [ ...prevValue ]; - const index = newClosers.indexOf(closeFunction); + const index = newClosers.indexOf(closeFunction); - if(index >= 0) newClosers.splice(index, 1); + if(index >= 0) newClosers.splice(index, 1); - return newClosers; - }); + return newClosers; + }); } }, [ close, setClosers ]); return ( - - { headerText } - { isOpen && } - { !isOpen && } - - { isOpen && - - { children } - } + + + { headerText } + { isOpen && } + { !isOpen && } + + { isOpen && + + { children } + + } + ); } diff --git a/src/common/card/accordion/NitroCardAccordionSetView.tsx b/src/common/card/accordion/NitroCardAccordionSetView.tsx index 7f4f7d304..042ae0054 100644 --- a/src/common/card/accordion/NitroCardAccordionSetView.tsx +++ b/src/common/card/accordion/NitroCardAccordionSetView.tsx @@ -1,17 +1,19 @@ import { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { FaCaretDown, FaCaretUp } from 'react-icons/fa'; -import { Column, ColumnProps, Flex, Text } from '../..'; +import { Base, Column, ColumnProps, Flex } from '../..'; +import { FriendListTabs, LocalizeText } from '../../../api'; import { useNitroCardAccordionContext } from './NitroCardAccordionContext'; export interface NitroCardAccordionSetViewProps extends ColumnProps { headerText: string; isExpanded?: boolean; + friendlistTab?: FriendListTabs; + setShowHoverText?: (text: string) => void; } export const NitroCardAccordionSetView: FC = props => { - const { headerText = '', isExpanded = false, gap = 0, classNames = [], children = null, ...rest } = props; + const { headerText = '', isExpanded = false, friendlistTab = null, setShowHoverText = null, gap = 0, classNames = [], children = null, ...rest } = props; const [ isOpen, setIsOpen ] = useState(false); const { setClosers = null, closeAll = null } = useNitroCardAccordionContext(); @@ -70,13 +72,13 @@ export const NitroCardAccordionSetView: FC = pro return ( - + setShowHoverText(LocalizeText(`${ friendlistTab }`)) } onMouseLeave={ () => setShowHoverText('') } onClick={ onClick }>
{ headerText }
- { isOpen && } - { !isOpen && } + { isOpen && } + { !isOpen && }
{ isOpen && - + { children } } diff --git a/src/common/draggable-window/DraggableWindow.tsx b/src/common/draggable-window/DraggableWindow.tsx index 8dd290b38..b96ded0ea 100644 --- a/src/common/draggable-window/DraggableWindow.tsx +++ b/src/common/draggable-window/DraggableWindow.tsx @@ -253,7 +253,7 @@ export const DraggableWindow: FC = props => { if(!uniqueKey) return; - const localStorage = JSON.parse(window.localStorage.getItem(`nitro.windows.${ uniqueKey }`)) as WindowSaveOptions; + const localStorage = GetLocalStorage(`nitro.windows.${ uniqueKey }`) as WindowSaveOptions; if(!localStorage || !localStorage.offset) return; diff --git a/src/common/index.scss b/src/common/index.scss index 9c96a7240..6139981a5 100644 --- a/src/common/index.scss +++ b/src/common/index.scss @@ -64,7 +64,7 @@ position: absolute; top: 1px; animation: MoveUpDown 1.7s linear infinite; - + @keyframes MoveUpDown { 0%, 100% { top: 1px; @@ -111,6 +111,8 @@ .catalog-grid-active { + width: 44px; + &.active { border-image-source: url(@/assets/flash/catalogue/item_grid_highlight.png); border-image-slice: 6 6 6 6 fill; @@ -336,8 +338,8 @@ .nitro-currency-icon { background-position: center; background-repeat: no-repeat; - width: 15px; - height: 15px; + width: 16px; + height: 16px; } .nitro-item-count { @@ -354,6 +356,52 @@ color: #fff; } +.nitro-catalog .layout-catalog-grid-item .nitro-item-count, .nitro-catalog .layout-grid-item .nitro-item-count { + border-image-source: url(@/assets/flash/catalogue/item-counter-bg.png); + border-image-slice:6 6 6 6 fill; + border-image-width:6px 6px 6px 6px; + color: #CCCB65; + font-family: Goldfish; + top: 23px; + right: 3px; + font-size: 8.5px; + padding-top: 0px; + padding-bottom: 0px; + padding-left: 6px; + padding-right: 3px; + z-index: 1; +} + +.nitro-catalog .layout-catalog-grid-item .nitro-item-count:before, .nitro-catalog .layout-grid-item .nitro-item-count:before { + content: ' x'; + display: inline; +} + +.badge-wrapper { + overflow: hidden; + + .badges-list { + height: 65% !important; + } + + .badge-container { + background-color: #cacaca; + border-radius: 6px !important; + border: solid 1px #000 !important; + width: 42px; + height: 42px; + padding: 2px; + + &.layout-grid-item.active { + background-color: #cacaca !important; + border: 1px solid #000 !important; + box-shadow: inset 0 0 0 2px white; + box-sizing: border-box; + } + } +} + + .badge-image { position: relative; width: 40px; @@ -361,6 +409,7 @@ background-repeat: no-repeat; background-position: center; + .badge-information { display: none; } @@ -383,22 +432,11 @@ bottom: -15px; z-index: 100; font-size: 9px; + min-height: 80px; } .badge-desc { font-family: Goldfish; - - &:before { - position: absolute; - content: ' '; - width: 0; - height: 0; - border-left: 10px solid $white; - border-bottom: 10px solid transparent; - border-top: 10px solid transparent; - top: 10px; - right: -10px; - } } } @@ -465,7 +503,6 @@ width: 100%; background-color: $black; background-repeat: no-repeat; - background-position: center; overflow: hidden; &.border-0 { @@ -693,4 +730,42 @@ .notification-height { max-height: 85px; } + +.remove-outline { + outline: 0; +} + +.input-error { + background-color: #F18F9B; +} + +.nitro-input-error-popup { + user-select: none; + position: absolute; + width: auto; + height: 21px; + background: white; + text-align: center; + border-radius: 4px; + border: 1px solid black; + margin-left: 20px; + margin-top: -10px; + + &:before { + border: 10px solid white; + content: ''; + border-left-color: transparent; + border-bottom-color: transparent; + border-right-color: transparent; + position: absolute; + bottom: -20px; + left: calc(50% - 15px); + } +} + +.text-stroke { + color: black !important; + text-shadow: -1px -1px 1px #4d4d4d, 1px -1px 1px #4d4d4d, -1px 1px 1px #4d4d4d, 1px 1px 1px #4d4d4d; +} + @import './card/NitroCardView'; diff --git a/src/common/layout/LayoutAvatarImageView.tsx b/src/common/layout/LayoutAvatarImageView.tsx index d127bd993..51ee9a214 100644 --- a/src/common/layout/LayoutAvatarImageView.tsx +++ b/src/common/layout/LayoutAvatarImageView.tsx @@ -49,19 +49,19 @@ export const LayoutAvatarImageView: FC = props => useEffect(() => { const avatarImage = GetAvatarRenderManager().createAvatarImage(figure, AvatarScaleType.LARGE, gender, { - resetFigure: figure => + resetFigure: figure => { if(isDisposed.current) return; setRandomValue(Math.random()); }, - dispose: () => + dispose: () => {}, disposed: false }, null); if(!avatarImage) return; - + let setType = AvatarSetType.FULL; if(headOnly) setType = AvatarSetType.HEAD; @@ -84,6 +84,6 @@ export const LayoutAvatarImageView: FC = props => isDisposed.current = true; } }, []); - + return ; } diff --git a/src/common/layout/LayoutBadgeImageView.tsx b/src/common/layout/LayoutBadgeImageView.tsx index 665f0874d..0a52a8d2f 100644 --- a/src/common/layout/LayoutBadgeImageView.tsx +++ b/src/common/layout/LayoutBadgeImageView.tsx @@ -100,7 +100,7 @@ export const LayoutBadgeImageView: FC = props => return (
{ title }
-
{ description }
+
{ description }
); }; @@ -110,7 +110,7 @@ export const LayoutBadgeImageView: FC = props => { (showInfo && GetConfiguration('badge.descriptions.enabled', true)) && { isGroup ? customTitle : LocalizeBadgeName(badgeCode) } -
{ isGroup ? LocalizeText('group.badgepopup.body') : LocalizeBadgeDescription(badgeCode) }
+
{ isGroup ? LocalizeText('group.badgepopup.body') : LocalizeBadgeDescription(badgeCode) }
} { children } diff --git a/src/common/layout/LayoutCounterTimeView.tsx b/src/common/layout/LayoutCounterTimeView.tsx index 7460b054e..81b1c64f3 100644 --- a/src/common/layout/LayoutCounterTimeView.tsx +++ b/src/common/layout/LayoutCounterTimeView.tsx @@ -27,15 +27,18 @@ export const LayoutCounterTimeView: FC = props => return ( -
{ day != '00' ? day : hour }{ day != '00' ? LocalizeText('countdown_clock_unit_days') : LocalizeText('countdown_clock_unit_hours') }
+
{ day != '00' ? day : hour }
+
{ day != '00' ? LocalizeText('countdown_clock_unit_days') : LocalizeText('countdown_clock_unit_hours') }
- : + : -
{ minutes }{ LocalizeText('countdown_clock_unit_minutes') }
+
{ minutes }
+
{ LocalizeText('countdown_clock_unit_minutes') }
- : + : -
{ seconds }{ LocalizeText('countdown_clock_unit_seconds') }
+
{ seconds }
+
{ LocalizeText('countdown_clock_unit_seconds') }
{ children }
diff --git a/src/common/layout/LayoutInputErrorView.tsx b/src/common/layout/LayoutInputErrorView.tsx new file mode 100644 index 000000000..e7aa128a3 --- /dev/null +++ b/src/common/layout/LayoutInputErrorView.tsx @@ -0,0 +1,18 @@ +import { FC } from 'react'; +import { Flex, Text } from '..'; + +interface LayoutInputErrorViewProps +{ + text: string; +} + +export const LayoutInputErrorView: FC = props => +{ + const { text = null } = props; + + return ( + + { text } + + ); +}; diff --git a/src/common/layout/LayoutMiniCameraView.tsx b/src/common/layout/LayoutMiniCameraView.tsx index 90deb5abe..1b9d21aa6 100644 --- a/src/common/layout/LayoutMiniCameraView.tsx +++ b/src/common/layout/LayoutMiniCameraView.tsx @@ -2,8 +2,8 @@ import { NitroRectangle, NitroRenderTexture } from '@nitrots/nitro-renderer'; import { FC, useRef } from 'react'; import { GetRoomEngine, LocalizeText, PlaySound, SoundNames } from '../../api'; import { Button } from '../Button'; -import { DraggableWindow } from '../draggable-window'; import { Flex } from '../Flex'; +import { DraggableWindow } from '../draggable-window'; interface LayoutMiniCameraViewProps { @@ -35,7 +35,7 @@ export const LayoutMiniCameraView: FC = props => return ( - +
diff --git a/src/common/layout/LayoutRoomThumbnailView.tsx b/src/common/layout/LayoutRoomThumbnailView.tsx index acfdd9a43..42ba0b645 100644 --- a/src/common/layout/LayoutRoomThumbnailView.tsx +++ b/src/common/layout/LayoutRoomThumbnailView.tsx @@ -30,7 +30,7 @@ export const LayoutRoomThumbnailView: FC = props = return ( - { getImageUrl && } + { getImageUrl && } { children } ); diff --git a/src/common/layout/LayoutSearchSavesView.tsx b/src/common/layout/LayoutSearchSavesView.tsx new file mode 100644 index 000000000..42e2eeca2 --- /dev/null +++ b/src/common/layout/LayoutSearchSavesView.tsx @@ -0,0 +1,26 @@ +import { CSSProperties, FC, useMemo } from 'react'; +import { Base, BaseProps } from '../Base'; + +export interface LayoutSearchSavesViewProps extends BaseProps +{ + title: string; + onClick?: () => void; +} + +export const LayoutSearchSavesView: FC = props => +{ + const { title = null, onClick = null, style = {}, } = props; + + const getStyle = useMemo(() => + { + let newStyle: CSSProperties = {}; + + if(Object.keys(style).length) newStyle = { ...newStyle, ...style }; + + return newStyle; + }, [ style ]); + + return ( + + ); +} diff --git a/src/common/layout/UserProfileIconView.tsx b/src/common/layout/UserProfileIconView.tsx index d31fa66b4..b02c772c6 100644 --- a/src/common/layout/UserProfileIconView.tsx +++ b/src/common/layout/UserProfileIconView.tsx @@ -22,7 +22,11 @@ export const UserProfileIconView: FC = props => }, [ classNames ]); return ( - GetUserProfile(userId) } { ... rest }> + + { + event.stopPropagation(); + GetUserProfile(userId) + } } { ... rest }> { children } ); diff --git a/src/common/layout/index.ts b/src/common/layout/index.ts index 3c4238e10..3e4d82266 100644 --- a/src/common/layout/index.ts +++ b/src/common/layout/index.ts @@ -8,6 +8,7 @@ export * from './LayoutFurniImageView'; export * from './LayoutGiftTagView'; export * from './LayoutGridItem'; export * from './LayoutImage'; +export * from './LayoutInputErrorView'; export * from './LayoutItemCountView'; export * from './LayoutLoadingSpinnerView'; export * from './LayoutMiniCameraView'; @@ -18,6 +19,7 @@ export * from './LayoutProgressBar'; export * from './LayoutRarityLevelView'; export * from './LayoutRoomPreviewerView'; export * from './LayoutRoomThumbnailView'; +export * from './LayoutSearchSavesView'; export * from './LayoutTrophyView'; -export * from './limited-edition'; export * from './UserProfileIconView'; +export * from './limited-edition'; diff --git a/src/components/avatar-editor/AvatarEditorView.scss b/src/components/avatar-editor/AvatarEditorView.scss index 2a042f5e4..11263ff82 100644 --- a/src/components/avatar-editor/AvatarEditorView.scss +++ b/src/components/avatar-editor/AvatarEditorView.scss @@ -212,6 +212,56 @@ } } +.saved-outfits-title { + color: #a7a6a2; + font-weight: bold; +} + +.saved-outfit-container { + display: flex; + width: 100% !important; + height: 91.5%; + + .avatar-image { + width: 40px !important; + z-index: 4; + transform: scale(0.5); + } + + .avatar-figure { + margin-top: -46px; + margin-left: -9px; + image-rendering: auto !important; + } + + .nitro-avatar-editor-wardrobe-container { + background-color: #cacaca; + border-radius: 0.3rem; + border: solid 1px #000; + height: 386px; + width: 100%; + display: grid; + grid-template-columns: repeat(2, 1fr); + grid-gap: 10px; + padding: 10px 12px 10px 0; + overflow-y: auto; + } + + .avatar-container { + height: 50px; + border-radius: 0.3rem; + background-color: #a7a6a2; + width: 30px; + } + + .saved-outfit-button { + margin-top: -3px; + background-color: transparent; + border: none; + } +} + + .nitro-avatar-editor-wardrobe-figure-preview { border-image-source: url(@/assets/flash/avatareditor/wardrobe_user_bg.png); border-image-slice: 4 4 4 4 fill; @@ -220,14 +270,6 @@ overflow: hidden; z-index: 1; - .avatar-image { - position: absolute; - bottom: -15px; - margin: 0 auto; - z-index: 4; - - } - .avatar-shadow { position: absolute; left: 0; @@ -249,8 +291,11 @@ } .nitro-avatar-editor { - width: $avatar-editor-width; height: $avatar-editor-height; + min-width: $avatar-editor-width; + min-height: $avatar-editor-height; + max-width: $avatar-editor-width; + max-height: $avatar-editor-height; .category-item { height: 40px; @@ -308,16 +353,21 @@ } } +.nitro-avatar-editor.expanded { + max-width: $avatar-editor-width + 164px; + min-width: $avatar-editor-width + 164px; +} + .choose-clothing { - width: 298px; + width: 320px; } .color-picker-frame { border-image-source: url(@/assets/flash/avatareditor/color_frame.png); border-image-slice: 6 6 6 6 fill; border-image-width: 6px 6px 6px 6px; - width: 13px; - height: 19px; + width: 14px; + height: 21px; border-radius: 4px; &.active { @@ -345,6 +395,40 @@ position: relative; background-color: #1c3d4f !important; + .tab { + background-position-x: 0; + background-position-y: 0; + width: 34px; + height: 22px; + } + + .hd { + background: url(@/assets/flash/wardrobe/hd.png) no-repeat center; + } + + .head { + background: url(@/assets/flash/wardrobe/head.png) no-repeat center; + } + + .torso { + background: url(@/assets/flash/wardrobe/torso.png) no-repeat center; + } + + .legs { + background: url(@/assets/flash/wardrobe/legs.png) no-repeat center; + } + + .tab-wardrobe { + width: 40px; + height: 28px; + background-size: 38px 28px; + background-image: url(@/assets/flash/wardrobe/wardrobe.png); + background-repeat: no-repeat; + background-position: center; + filter: contrast(1.2) brightness(1.05); + } + + .nav-tabs .nav-link { position: relative; border-image-source: url(@/assets/flash/boxes/card/tabs_avatareditor.png); @@ -354,8 +438,13 @@ border-image-repeat: repeat repeat; margin-bottom: -2px; margin-left: -2px; + + &:hover { + border-image-source: url(@/assets/flash/boxes/card/tabs_active.png); + } } + .nav-tabs .nav-link.active { border-image-source: url(@/assets/flash/boxes/card/tabs_active.png); } @@ -382,3 +471,58 @@ border-image-slice: 6 6 6 6 fill; border-image-width: 6px 6px 6px 6px; } + + .avatar-container { + padding: 3px; + } + + .avatar-parts { + border: none !important; + height: 42px; + width: 42px; + background-position: center; + background-repeat: no-repeat; + border-radius: 2rem !important; + overflow: visible !important; + background-color: transparent; + + &:hover { + box-shadow: 0 0 0 3px #dbdad5 !important; + background-color: #cecdc8 !important; + } + + &:active, + &.part-selected { + box-shadow: 0 0 0 3px #c5c3c0 !important; + background-color: #b1b1b1 !important; + } + } + +.avatar-parts-container { + height: 70%; + padding-left: 10px; +} + +.avatar-color-palette-container { + height: 30%; + width: 100%!important; + padding-left: 10px; +} + +.dual-palette { + display: flex !important; + flex-direction: row !important; +} + +.avatar-editor-palette-set-view { + padding-right: 15px !important; + flex-grow: 1; +} + +.clothing-container { + padding-right: 15px !important; +} + +.action-buttons { + gap: 5px; +} diff --git a/src/components/avatar-editor/AvatarEditorView.tsx b/src/components/avatar-editor/AvatarEditorView.tsx index 33e28e4c5..c15bfe180 100644 --- a/src/components/avatar-editor/AvatarEditorView.tsx +++ b/src/components/avatar-editor/AvatarEditorView.tsx @@ -1,319 +1,356 @@ -import { AvatarEditorFigureCategory, FigureSetIdsMessageEvent, GetWardrobeMessageComposer, IAvatarFigureContainer, ILinkEventTracker, UserFigureComposer, UserWardrobePageEvent } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { FaDice, FaTrash, FaUndo } from 'react-icons/fa'; -import { AddEventLinkTracker, AvatarEditorAction, AvatarEditorUtilities, BodyModel, FigureData, generateRandomFigure, GetAvatarRenderManager, GetClubMemberLevel, GetConfiguration, GetSessionDataManager, HeadModel, IAvatarEditorCategoryModel, LegModel, LocalizeText, RemoveLinkEventTracker, SendMessageComposer, TorsoModel } 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(() => GetConfiguration('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/' - }; - - AddEventLinkTracker(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 && - } - - - - - processAction(AvatarEditorAction.ACTION_RANDOMIZE) } /> - - - - - - - - - - - - - - ); -} +import { AvatarEditorFigureCategory, FigureSetIdsMessageEvent, GetWardrobeMessageComposer, IAvatarFigureContainer, ILinkEventTracker, SetClothingChangeDataMessageComposer, UserFigureComposer, UserWardrobePageEvent } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { FaDice, FaTrash, FaUndo } from 'react-icons/fa'; +import { AddEventLinkTracker, AvatarEditorAction, AvatarEditorUtilities, BodyModel, FigureData, GetAvatarRenderManager, GetClubMemberLevel, GetConfiguration, GetLocalStorage, GetSessionDataManager, HeadModel, IAvatarEditorCategoryModel, LegModel, LocalizeText, RemoveLinkEventTracker, SendMessageComposer, SetLocalStorage, TorsoModel, generateRandomFigure } from '../../api'; +import { Button, ButtonGroup, Column, Flex, 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 [ genderFootballGate, setGenderFootballGate ] = useState(null); + const [ objectFootballGate, setObjectFootballGate ] = useState(null); + + const DEFAULT_MALE_FOOTBALL_GATE = (GetLocalStorage('nitro.look.footballgate.M') || 'ch-3109-92-1408.lg-3116-82-1408.sh-3115-1408-1408') as string; + const DEFAULT_FEMALE_FOOTBALL_GATE = (GetLocalStorage('nitro.look.footballgate.F') || 'ch-3112-1408-1408.lg-3116-71-1408.sh-3115-1408-1408') as string; + const maxWardrobeSlots = useMemo(() => GetConfiguration('avatar.wardrobe.max.slots', 10), []); + + const onClose = () => + { + setGenderFootballGate(null); + setObjectFootballGate(null); + setIsVisible(false); + } + + 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(); + + if (!genderFootballGate) + { + categories.set(AvatarEditorFigureCategory.GENERIC, new BodyModel()); + categories.set(AvatarEditorFigureCategory.HEAD, new HeadModel()); + categories.set(AvatarEditorFigureCategory.TORSO, new TorsoModel()); + categories.set(AvatarEditorFigureCategory.LEGS, new LegModel()); + } + else + { + categories.set(AvatarEditorFigureCategory.TORSO, new TorsoModel()); + categories.set(AvatarEditorFigureCategory.LEGS, new LegModel()); + } + + setCategories(categories); + }, [ genderFootballGate ]); + + 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: + !genderFootballGate ? SendMessageComposer(new UserFigureComposer(figureData.gender, figureData.getFigureString())) : SendMessageComposer(new SetClothingChangeDataMessageComposer(objectFootballGate, genderFootballGate, figureData.getFigureString())); + SetLocalStorage(`nitro.look.footballgate.${ genderFootballGate }`, figureData.getFigureString()); + onClose(); + return; + } + }, [ loadAvatarInEditor, figureData, resetCategories, lastFigure, lastGender, figureSetIds, genderFootballGate, objectFootballGate ]) + + 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('/'); + + setGenderFootballGate(parts[2] ? parts[2] : null); + setObjectFootballGate(parts[3] ? Number(parts[3]) : null); + + 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/' + }; + + AddEventLinkTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + useEffect(() => + { + setSavedFigures(new Array(maxWardrobeSlots)); + }, [ maxWardrobeSlots ]); + + useEffect(() => + { + SendMessageComposer(new GetWardrobeMessageComposer()); + }, []); + + useEffect(() => + { + if(!categories) return; + + selectCategory(!genderFootballGate ? AvatarEditorFigureCategory.GENERIC : AvatarEditorFigureCategory.TORSO); + }, [ categories, genderFootballGate, 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; + + if (!genderFootballGate) loadAvatarInEditor(GetSessionDataManager().figure, GetSessionDataManager().gender); + if (genderFootballGate) loadAvatarInEditor(genderFootballGate === FigureData.MALE ? DEFAULT_MALE_FOOTBALL_GATE : DEFAULT_FEMALE_FOOTBALL_GATE, genderFootballGate); + setNeedsReset(false); + }, [ isVisible, isInitalized, needsReset, loadAvatarInEditor, genderFootballGate, DEFAULT_MALE_FOOTBALL_GATE, DEFAULT_FEMALE_FOOTBALL_GATE ]); + + useEffect(() => // This is so when you have the look editor open and you change the mode to Boy or Girl + { + if(!isVisible) return; + + return () => + { + setupFigures(); + setIsWardrobeVisible(false); + setNeedsReset(true); + } + }, [ isVisible, genderFootballGate, setupFigures ]); + + useEffect(() => + { + if(isVisible) return; + + return () => + { + setNeedsReset(true); + } + }, [ isVisible ]); + + if(!isVisible || !figureData) return null; + + const avatarEditorClasses = `nitro-avatar-editor no-resize ${ isWardrobeVisible ? 'expanded' : '' }`; + + return ( + + + + { categories && (categories.size > 0) && Array.from(categories.keys()).map(category => + { + const isActive = (activeCategory && (activeCategory.name === category)); + + return ( + selectCategory(category) }> +
+
+ ); + }) } + { (!genderFootballGate) && + setIsWardrobeVisible(!isWardrobeVisible) }> +
+
+ } +
+ + + + { (activeCategory) && + + } + + + + + + + { (!genderFootballGate) && + + + + + + } + + + + { isWardrobeVisible && + + + + } + + + + +
+ ); +} diff --git a/src/components/avatar-editor/views/AvatarEditorModelView.tsx b/src/components/avatar-editor/views/AvatarEditorModelView.tsx index 237861753..5df65b2c9 100644 --- a/src/components/avatar-editor/views/AvatarEditorModelView.tsx +++ b/src/components/avatar-editor/views/AvatarEditorModelView.tsx @@ -4,6 +4,7 @@ import { Column, Flex, Grid, Text } from '../../../common'; import { AvatarEditorIcon } from './AvatarEditorIcon'; import { AvatarEditorFigureSetView } from './figure-set/AvatarEditorFigureSetView'; import { AvatarEditorPaletteSetView } from './palette-set/AvatarEditorPaletteSetView'; + export interface AvatarEditorModelViewProps { model: IAvatarEditorCategoryModel; @@ -53,8 +54,8 @@ export const AvatarEditorModelView: FC = props => return ( - - + + { model.canSetGender && <> setGender(FigureData.MALE) }> @@ -71,20 +72,22 @@ export const AvatarEditorModelView: FC = props => const category = model.categories.get(name); return ( - selectCategory(name) }> - - +
+ selectCategory(name) }> + + +
); }) }
- + - + { (maxPaletteCount >= 1) && - } + } { (maxPaletteCount === 2) && - } + }
diff --git a/src/components/avatar-editor/views/AvatarEditorWardrobeView.tsx b/src/components/avatar-editor/views/AvatarEditorWardrobeView.tsx index e245bc1b2..dbb76627a 100644 --- a/src/components/avatar-editor/views/AvatarEditorWardrobeView.tsx +++ b/src/components/avatar-editor/views/AvatarEditorWardrobeView.tsx @@ -1,8 +1,8 @@ -import { IAvatarFigureContainer, SaveWardrobeOutfitMessageComposer } from '@nitrots/nitro-renderer'; +import { HabboClubLevelEnum, IAvatarFigureContainer, SaveWardrobeOutfitMessageComposer } from '@nitrots/nitro-renderer'; import { Dispatch, FC, SetStateAction, useCallback, useMemo } from 'react'; -import { FigureData, GetAvatarRenderManager, GetClubMemberLevel, GetConfiguration, LocalizeText, SendMessageComposer } from '../../../api'; -import { AutoGrid, Base, Button, Flex, LayoutAvatarImageView, LayoutCurrencyIcon, LayoutGridItem } from '../../../common'; - +import { MdKeyboardArrowLeft, MdKeyboardArrowRight } from 'react-icons/all'; +import { CreateLinkEvent, FigureData, GetAvatarRenderManager, GetClubMemberLevel, GetConfiguration, GetSessionDataManager, LocalizeText, SendMessageComposer } from '../../../api'; +import { Flex, LayoutAvatarImageView, LayoutCurrencyIcon } from '../../../common'; export interface AvatarEditorWardrobeViewProps { figureData: FigureData; @@ -30,6 +30,8 @@ export const AvatarEditorWardrobeView: FC = props { if(!figureData || (index >= savedFigures.length) || (index < 0)) return; + if (GetSessionDataManager().clubLevel === HabboClubLevelEnum.NO_CLUB) return CreateLinkEvent('habboUI/open/hccenter'); + const newFigures = [ ...savedFigures ]; const figure = figureData.getFigureString(); @@ -41,6 +43,22 @@ export const AvatarEditorWardrobeView: FC = props SendMessageComposer(new SaveWardrobeOutfitMessageComposer((index + 1), figure, gender)); }, [ figureData, savedFigures, setSavedFigures ]); + const getClubLevel = useCallback(() => + { + let highestClubLevel = 0; + + savedFigures.forEach(([ figureContainer, gender ]) => + { + if (figureContainer) + { + const clubLevel = GetAvatarRenderManager().getFigureClubLevel(figureContainer, gender); + highestClubLevel = Math.max(highestClubLevel, clubLevel); + } + }); + + return highestClubLevel; + }, [ savedFigures ]); + const figures = useMemo(() => { if(!savedFigures || !savedFigures.length) return []; @@ -54,26 +72,52 @@ export const AvatarEditorWardrobeView: FC = props if(figureContainer) clubLevel = GetAvatarRenderManager().getFigureClubLevel(figureContainer, gender); items.push( - - { figureContainer && - } - - { !hcDisabled && (clubLevel > 0) && } - - - { figureContainer && - } + + + + { figureContainer && ( + + ) } - +
+ { figureContainer && ( + + ) } +
+
); }); return items; - }, [ savedFigures, hcDisabled, saveFigureAtWardrobeIndex, wearFigureAtIndex ]); + }, [ savedFigures, saveFigureAtWardrobeIndex, wearFigureAtIndex ]); return ( - - { figures } - +
+
+ + { LocalizeText('avatareditor.wardrobe.title') } + + + { !hcDisabled && getClubLevel() > 0 && ( + + ) } + +
+
+
{ figures }
+
+
); + } diff --git a/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetItemView.tsx b/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetItemView.tsx index 2fcec3c7b..e96041378 100644 --- a/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetItemView.tsx +++ b/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetItemView.tsx @@ -25,11 +25,13 @@ export const AvatarEditorFigureSetItemView: FC - { !hcDisabled && partItem.isHC && } - { partItem.isClear && } - { partItem.isSellable && } - { children } - +
+ + { !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 index 3755731c2..538d45b1c 100644 --- a/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetView.tsx +++ b/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetView.tsx @@ -1,44 +1,48 @@ -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) } />) } - - ); -} +import { HabboClubLevelEnum } from '@nitrots/nitro-renderer'; +import { Dispatch, FC, SetStateAction, useCallback, useEffect, useRef } from 'react'; +import { AvatarEditorGridPartItem, CategoryData, CreateLinkEvent, GetSessionDataManager, 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; + + if (item.isHC && GetSessionDataManager().clubLevel === HabboClubLevelEnum.NO_CLUB) return CreateLinkEvent('habboUI/open/hccenter'); + + 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 => + selectPart(item) } />) + } + + ); +} diff --git a/src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetView.tsx b/src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetView.tsx index 76e2d40ee..3fe0ff9de 100644 --- a/src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetView.tsx +++ b/src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetView.tsx @@ -1,5 +1,6 @@ +import { HabboClubLevelEnum } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useRef } from 'react'; -import { AvatarEditorGridColorItem, CategoryData, IAvatarEditorCategoryModel } from '../../../../api'; +import { AvatarEditorGridColorItem, CategoryData, CreateLinkEvent, GetSessionDataManager, IAvatarEditorCategoryModel } from '../../../../api'; import { AutoGrid } from '../../../../common'; import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView'; @@ -21,6 +22,8 @@ export const AvatarEditorPaletteSetView: FC = p const index = paletteSet.indexOf(item); if(index === -1) return; + + if (item.isHC && GetSessionDataManager().clubLevel === HabboClubLevelEnum.NO_CLUB) return CreateLinkEvent('habboUI/open/hccenter'); model.selectColor(category.name, index, paletteIndex); }, [ model, category, paletteSet, paletteIndex ]); @@ -33,9 +36,21 @@ export const AvatarEditorPaletteSetView: FC = p }, [ model, category ]); return ( - - { (paletteSet.length > 0) && paletteSet.map((item, index) => - selectColor(item) } />) } + + { paletteSet.length > 0 && + paletteSet.map((item, index) => ( + selectColor(item) } + /> + )) } ); } diff --git a/src/components/camera/CameraWidgetView.scss b/src/components/camera/CameraWidgetView.scss index 53973c998..2f6cc2ccd 100644 --- a/src/components/camera/CameraWidgetView.scss +++ b/src/components/camera/CameraWidgetView.scss @@ -10,15 +10,32 @@ height: 20px; &:hover { - background-image: url("@/assets/flash/boxes/card/close_hover.png"); + background-image: url("@/assets/flash/boxes/card/close_hover.png"); &:active { - background-image: url("@/assets/flash/boxes/card/close_click.png"); + background-image: url("@/assets/flash/boxes/card/close_click.png"); } } } + .info-camera { + top: 8px; + right: 32px; + cursor: pointer; + background-image: url("@/assets/flash/boxes/card/questionmark.png"); + width: 19px; + height: 20px; + + &:hover { + background-image: url("@/assets/flash/boxes/card/questionmark_hover.png"); + + &:active { + background-image: url("@/assets/flash/boxes/card/questionmark_click.png"); + } + } + } + .camera-area { position: absolute; top: 37px; diff --git a/src/components/camera/views/CameraWidgetCaptureView.tsx b/src/components/camera/views/CameraWidgetCaptureView.tsx index dc18859fc..e6bcd9fbe 100644 --- a/src/components/camera/views/CameraWidgetCaptureView.tsx +++ b/src/components/camera/views/CameraWidgetCaptureView.tsx @@ -1,7 +1,6 @@ import { NitroRectangle, TextureUtils } from '@nitrots/nitro-renderer'; import { FC, useRef } from 'react'; -import { FaTimes } from 'react-icons/fa'; -import { CameraPicture, GetRoomEngine, GetRoomSession, LocalizeText, PlaySound, SoundNames } from '../../../api'; +import { CameraPicture, CreateLinkEvent, GetRoomEngine, GetRoomSession, LocalizeText, PlaySound, SoundNames } from '../../../api'; import { Column, DraggableWindow, Flex } from '../../../common'; import { useCamera, useNotification } from '../../../hooks'; @@ -62,9 +61,8 @@ export const CameraWidgetCaptureView: FC = props = { selectedPicture && }
-
- -
+
CreateLinkEvent('habbopages/camera') }>
+
{ !selectedPicture &&
} { selectedPicture &&
diff --git a/src/components/camera/views/CameraWidgetCheckoutView.tsx b/src/components/camera/views/CameraWidgetCheckoutView.tsx index 7f0b9cc71..32b0d822c 100644 --- a/src/components/camera/views/CameraWidgetCheckoutView.tsx +++ b/src/components/camera/views/CameraWidgetCheckoutView.tsx @@ -1,8 +1,8 @@ -import { CameraPublishStatusMessageEvent, CameraPurchaseOKMessageEvent, CameraStorageUrlMessageEvent, PublishPhotoMessageComposer, PurchasePhotoMessageComposer } from '@nitrots/nitro-renderer'; +import { CameraPublishStatusMessageEvent, CameraPurchaseOKMessageEvent, CameraStorageUrlMessageEvent, NotEnoughBalanceMessageEvent, PublishPhotoMessageComposer, PurchasePhotoMessageComposer } from '@nitrots/nitro-renderer'; import { FC, useEffect, useMemo, useState } from 'react'; import { CreateLinkEvent, GetConfiguration, GetRoomEngine, LocalizeText, SendMessageComposer } from '../../../api'; import { Button, Column, Flex, LayoutCurrencyIcon, LayoutImage, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; -import { useMessageEvent } from '../../../hooks'; +import { useMessageEvent, useNotification } from '../../../hooks'; export interface CameraWidgetCheckoutViewProps { @@ -21,6 +21,7 @@ export const CameraWidgetCheckoutView: FC = props const [ wasPicturePublished, setWasPicturePublished ] = useState(false); const [ isWaiting, setIsWaiting ] = useState(false); const [ publishCooldown, setPublishCooldown ] = useState(0); + const { simpleAlert } = useNotification(); const publishDisabled = useMemo(() => GetConfiguration('camera.publish.disabled', false), []); @@ -34,6 +35,8 @@ export const CameraWidgetCheckoutView: FC = props { const parser = event.getParser(); + if (!parser.ok) simpleAlert(LocalizeText('camera.publish.wait', [ 'minutes' ], [ Math.floor(parser.secondsToWait / 60).toString().replace('-', '') ]), null, null, null, LocalizeText('camera.purchase.pleasewait')); + setPublishUrl(parser.extraDataId); setPublishCooldown(parser.secondsToWait); setWasPicturePublished(parser.ok); @@ -47,6 +50,19 @@ export const CameraWidgetCheckoutView: FC = props setPictureUrl(GetConfiguration('camera.url') + '/' + parser.url); }); + useMessageEvent(NotEnoughBalanceMessageEvent, event => + { + const parser = event.getParser(); + + if (!parser) return null; + + if (parser.notEnoughCredits && !parser.notEnoughActivityPoints) simpleAlert(LocalizeText('catalog.alert.notenough.credits.description'), null, null, null, LocalizeText('catalog.alert.notenough.title')); + + if (!parser.notEnoughCredits && parser.notEnoughActivityPoints) simpleAlert(LocalizeText(`catalog.alert.notenough.activitypoints.description.${ parser.activityPointType }`), null, null, null, LocalizeText(`catalog.alert.notenough.activitypoints.title.${ parser.activityPointType }`)); + + setIsWaiting(false); + }); + const processAction = (type: string, value: string | number = null) => { switch(type) diff --git a/src/components/camera/views/CameraWidgetShowPhotoView.tsx b/src/components/camera/views/CameraWidgetShowPhotoView.tsx index 8614695df..decb7cfd3 100644 --- a/src/components/camera/views/CameraWidgetShowPhotoView.tsx +++ b/src/components/camera/views/CameraWidgetShowPhotoView.tsx @@ -1,7 +1,7 @@ +import { RoomObjectCategory, RoomObjectVariable } from '@nitrots/nitro-renderer'; 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'; +import { GetRoomEngine, GetUserProfile, IPhotoData, LocalizeText } from '../../../api'; +import { Base, Flex, Text } from '../../../common'; export interface CameraWidgetShowPhotoViewProps { @@ -40,6 +40,15 @@ export const CameraWidgetShowPhotoView: FC = pro }); } + const getUserData = (roomId: number, objectId: number, type: string): number | string => + { + const roomObject = GetRoomEngine().getRoomObject(roomId, objectId, RoomObjectCategory.WALL); + + if (!roomObject) return; + + return type == 'username' ? roomObject.model.getValue(RoomObjectVariable.FURNITURE_OWNER_NAME) : roomObject.model.getValue(RoomObjectVariable.FURNITURE_OWNER_ID); + } + useEffect(() => { setImageIndex(currentIndex); @@ -48,24 +57,27 @@ export const CameraWidgetShowPhotoView: FC = pro if(!currentImage) return null; return ( - + - { !currentImage.w && - { LocalizeText('camera.loading') } } + { !currentImage.w && { LocalizeText('camera.loading') } } + + { currentImage.m && currentImage.m.length && { currentImage.m } } + + { new Date(currentImage.t * 1000).toLocaleDateString(undefined, { day: 'numeric', month: 'long', year: 'numeric' }) } - { currentImage.m && currentImage.m.length && - { currentImage.m } } - - { (currentImage.n || '') } - { new Date(currentImage.t * 1000).toLocaleDateString() } + + GetUserProfile(Number(getUserData(currentImage.s, Number(currentImage.u), 'id'))) }>{ getUserData(currentImage.s, Number(currentImage.u), 'username') } { (currentPhotos.length > 1) && - - - GetUserProfile(currentImage.oi) }>{ currentImage.o } - - + <> + + + + + + + } - + ); } diff --git a/src/components/catalog/CatalogView.scss b/src/components/catalog/CatalogView.scss index ffb9369ed..71e01b399 100644 --- a/src/components/catalog/CatalogView.scss +++ b/src/components/catalog/CatalogView.scss @@ -1,7 +1,9 @@ .nitro-catalog { width: $catalog-width; height: $catalog-height; - min-width: 528px; + min-width: $catalog-width; + max-width: $catalog-width; + min-height: $catalog-height - 39px; font[size='16'] { font-size: 20px; @@ -14,12 +16,12 @@ } .avatar-image { - background-position: -2px -34px !important; + background-position: -27px -37px!important; } .catalog-default-image { max-height: 250px; - height: 250px; + height: 243px; } .grid-price-view { @@ -37,9 +39,8 @@ .quantity-input { min-height: 17px; - width: 28px; + width: 34px; padding: 0 4px; - text-align: right; } .grid-bg { @@ -54,6 +55,12 @@ border-image-width: 6px 6px 6px 6px; } + .duckets-bg { + border-image-source: url(@/assets/flash/catalogue/duckets_bg.png); + border-image-slice: 6 6 6 6 fill; + border-image-width: 6px 6px 6px 6px; + } + .credits-default-layout { position: absolute; bottom: 15px; @@ -66,11 +73,23 @@ left: 10px; } + .selectproduct-title { + position: absolute; + font-size: 11px; + color: #62615f; + } + .item-picker { max-height: 150px; min-height: 150px; } + .item-not-picker { + width: calc(100% - 216px); + max-height: 30px; + min-height: 30px; + } + .group-furni-picker { max-height: 110px; min-height: 110px; @@ -78,6 +97,7 @@ .quanity-container { height: 20px; + font-size: 13px; } .catalog-image-column { @@ -109,11 +129,12 @@ } .trophy-text { - height: 150px; + height: 120px; } .nitro-catalog-gift { - width: 325px; + width: 362px; + height: 470px; .gift-preview { width: 80px; @@ -122,6 +143,9 @@ display: flex; justify-content: center; align-items: center; + background-color: #F0F0F0; + border: 1px solid #5D5D5D; + border-radius: 8px; } .gift-color { @@ -269,6 +293,34 @@ } } +.marketplace-bg { + border-image-source: url(@/assets/flash/catalogue/marketplace_bg.png); + border-image-slice: 6 6 6 6 fill; + border-image-width: 6px 6px 6px 6px; +} + +.search-marketplace { + height: 116px; +} + +.font-size-marketplace { + font-size: 13px; +} + +.font-size-marketplace-small { + font-size: 10px; +} + +.layout-grid-item { + + &.layout-marketplace { + background-color: transparent !important; + border: none !important; + border-color: none !important; + cursor: default; + } +} + .nitro-catalog-layout-vip-gifts-grid { .layout-grid-item { height: 55px !important; @@ -277,8 +329,39 @@ } .nitro-catalog-layout-marketplace-post-offer { - width: $marketplace-post-offer-width; - height: $marketplace-post-offer-height; + width: 299px; + + .image-preview { + width: 48px !important; + height: 48px !important; + overflow: hidden !important; + display: flex !important; + justify-content: center !important; + align-items: center !important; + background-color: #F0F0F0 !important; + border: 1px solid #5D5D5D !important; + border-radius: 8px !important; + } + + .textarea-height { + height: 51px; + } +} + +.nitro-catalog-layout-marketplace-confirm { + width: 279px; + + .image-preview { + width: 48px !important; + height: 48px !important; + overflow: hidden !important; + display: flex !important; + justify-content: center !important; + align-items: center !important; + background-color: #F0F0F0 !important; + border: 1px solid #5D5D5D !important; + border-radius: 8px !important; + } } .nitro-catalog-layout-bundle-grid { @@ -309,14 +392,13 @@ .autocomplete-gift-container { background: #fff; - padding: 8px; list-style-type: none; - min-width: 307px; + min-width: 264px; border-radius: 0.2rem; position: absolute; font-size: 0.7875rem; - top: 81px; - left: 8px; + top: 65px; + left: 31px; border: 1px solid #b6c1ce; margin: 0; border-radius: 2px; @@ -329,10 +411,50 @@ .autocomplete-gift-item { width: 100%; box-sizing: border-box; + &:hover { - background-color: #ebf4ff; + background-color: #CCD1DA !important; + } + + &:nth-child(odd) { + background-color: #EEEEEE; + } + + &:nth-child(even) { + background-color: #FFFFFF; } } } +.club-content { + height: 73px; + background-color: #DEDEDE; + border-radius: 12px; + border: 1px solid #959595; + + .content-title { + height: 25px; + background-color: #959595; + border-radius: 6px; + + .margin-image { + margin-right: 27px; + } + + .margin-text { + margin-top: -2px; + } + } +} + +.height-roomads-description { + height: 137px; +} + +.color-trophy { + width: 38px; + height: 30px; + margin-right: 4px; +} + @import './views/targeted-offer/Offer.scss'; diff --git a/src/components/catalog/CatalogView.tsx b/src/components/catalog/CatalogView.tsx index 3cfc6260d..2234ee158 100644 --- a/src/components/catalog/CatalogView.tsx +++ b/src/components/catalog/CatalogView.tsx @@ -10,20 +10,20 @@ import { CatalogHeaderView } from './views/page/common/CatalogHeaderView'; import { GetCatalogLayout } from './views/page/layout/GetCatalogLayout'; import { MarketplacePostOfferView } from './views/page/layout/marketplace/MarketplacePostOfferView'; -export const CatalogView: FC<{}> = props => +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(() => + useEffect(() => { const linkTracker: ILinkEventTracker = { - linkReceived: (url: string) => + linkReceived: (url: string) => { const parts = url.split('/'); - + if(parts.length < 2) return; - - switch(parts[1]) + + switch(parts[1]) { case 'show': setIsVisible(true); @@ -35,27 +35,27 @@ export const CatalogView: FC<{}> = props => setIsVisible(prevValue => !prevValue); return; case 'open': - if(parts.length > 2) + if(parts.length > 2) { - if(parts.length === 4) + if(parts.length === 4) { - switch(parts[2]) + switch(parts[2]) { case 'offerId': openPageByOfferId(parseInt(parts[3])); return; } } - else + else { openPageByName(parts[2]); } } - else + else { setIsVisible(true); } - + return; } }, @@ -73,15 +73,17 @@ export const CatalogView: FC<{}> = props => setIsVisible(false) } /> - { rootNode && (rootNode.children.length > 0) && rootNode.children.map(child => + { rootNode && (rootNode.children.length > 0) && rootNode.children.map((child, index) => { if(!child.isVisible) return null; - + + // Generate a unique key using the index of the map function + const uniqueKey = `${ child.pageId }-${ index }`; + return ( - + { if(searchResult) setSearchResult(null); - activateNode(child); } } > @@ -100,7 +102,7 @@ export const CatalogView: FC<{}> = props => { activeNodes && (activeNodes.length > 0) && } } - + { GetCatalogLayout(currentPage, () => setNavigationHidden(true)) } diff --git a/src/components/catalog/views/gift/CatalogGiftView.tsx b/src/components/catalog/views/gift/CatalogGiftView.tsx index ac1b6bf8c..5b8283b83 100644 --- a/src/components/catalog/views/gift/CatalogGiftView.tsx +++ b/src/components/catalog/views/gift/CatalogGiftView.tsx @@ -1,11 +1,10 @@ import { GiftReceiverNotFoundEvent, PurchaseFromCatalogAsGiftComposer } from '@nitrots/nitro-renderer'; import { ChangeEvent, FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; -import { ColorUtils, GetSessionDataManager, LocalizeText, MessengerFriend, ProductTypeEnum, SendMessageComposer } from '../../../../api'; -import { Base, Button, ButtonGroup, classNames, Column, Flex, FormGroup, LayoutCurrencyIcon, LayoutFurniImageView, LayoutGiftTagView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; +import { ColorUtils, GetConfiguration, GetSessionDataManager, LocalizeText, MessengerFriend, ProductTypeEnum, SendMessageComposer } from '../../../../api'; +import { Base, Button, ButtonGroup, Column, Flex, FormGroup, LayoutCurrencyIcon, LayoutFurniImageView, LayoutGiftTagView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; import { GiftColorButton } from '../../../../common/GiftColorButton'; import { CatalogEvent, CatalogInitGiftEvent, CatalogPurchasedEvent } from '../../../../events'; -import { useCatalog, useFriends, useMessageEvent, useUiEvent } from '../../../../hooks'; +import { useCatalog, useFriends, useMessageEvent, useNotification, useUiEvent } from '../../../../hooks'; export const CatalogGiftView: FC<{}> = props => { @@ -22,13 +21,13 @@ export const CatalogGiftView: FC<{}> = props => 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 { simpleAlert = null } = useNotification(); const onClose = useCallback(() => { @@ -99,6 +98,8 @@ export const CatalogGiftView: FC<{}> = props => setIsAutocompleteVisible(false); } + const alertReceiverNotFound = () => simpleAlert(LocalizeText('catalog.gift_wrapping.receiver_not_found.info'), null, null, null, LocalizeText('catalog.gift_wrapping.receiver_not_found.title')); + const handleAction = useCallback((action: string) => { switch(action) @@ -116,18 +117,14 @@ export const CatalogGiftView: FC<{}> = props => setSelectedRibbonIndex(value => (value === maxRibbonIndex ? 0 : value + 1)); return; case 'buy': - if(!receiverName || (receiverName.length === 0)) - { - setReceiverNotFound(true); - return; - } + if(!receiverName || (receiverName.length === 0)) return alertReceiverNotFound(); 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)); + useMessageEvent(GiftReceiverNotFoundEvent, event => alertReceiverNotFound()); useUiEvent([ CatalogPurchasedEvent.PURCHASE_SUCCESS, @@ -151,11 +148,6 @@ export const CatalogGiftView: FC<{}> = props => } }); - useEffect(() => - { - setReceiverNotFound(false); - }, [ receiverName ]); - const createBoxTypes = useCallback(() => { if (!giftConfiguration) return; @@ -211,26 +203,30 @@ export const CatalogGiftView: FC<{}> = props => const priceText = 'catalog.gift_wrapping_new.' + (isBoxDefault ? 'freeprice' : 'price'); return ( - + - + - { LocalizeText('catalog.gift_wrapping.receiver') } - onTextChanged(e) } /> + + onTextChanged(e) } /> + + { (suggestions.length > 0 && isAutocompleteVisible) && - + { suggestions.map((friend: MessengerFriend) => ( - selectedReceiverName(friend.name) }>{ friend.name } + selectedReceiverName(friend.name) }>{ friend.name } )) } } - { receiverNotFound && - { LocalizeText('catalog.gift_wrapping.receiver_not_found.title') } } setMessage(value) } /> - setShowMyFace(value => !value) } /> - + { GetConfiguration('catalog.gifts.show.my.face') && + <> + setShowMyFace(value => !value) } /> + + + } { selectedColorId && @@ -240,47 +236,47 @@ export const CatalogGiftView: FC<{}> = props => - - - - { LocalizeText(boxName) } + + { LocalizeText(boxName) } - { LocalizeText(priceText, [ 'price' ], [ giftConfiguration.price.toString() ]) } - + { LocalizeText(priceText, [ 'price' ], [ giftConfiguration.price.toString() ]) } + { !isBoxDefault && } - + - - - { LocalizeText(ribbonName) } + { LocalizeText(ribbonName) } - - + + { LocalizeText('catalog.gift_wrapping.pick_color') } - - { colors.map(color => setSelectedColorId(color.id) } />) } + + { colors.map(color => setSelectedColorId(color.id) } />) } - - - diff --git a/src/components/catalog/views/page/common/CatalogGridOfferView.tsx b/src/components/catalog/views/page/common/CatalogGridOfferView.tsx index 6e7419677..e1faad9a8 100644 --- a/src/components/catalog/views/page/common/CatalogGridOfferView.tsx +++ b/src/components/catalog/views/page/common/CatalogGridOfferView.tsx @@ -1,7 +1,7 @@ -import { MouseEventType } from '@nitrots/nitro-renderer'; +import { HabboClubLevelEnum, MouseEventType } from '@nitrots/nitro-renderer'; import { FC, MouseEvent, useMemo, useState } from 'react'; import { IPurchasableOffer, Offer, ProductTypeEnum } from '../../../../../api'; -import { Column, Flex, LayoutAvatarImageView, LayoutGridItemProps } from '../../../../../common'; +import { Base, Column, Flex, LayoutAvatarImageView, LayoutGridItemProps } from '../../../../../common'; import { LayoutCatalogGridItem } from '../../../../../common/layout/LayoutCatalogGridItem'; import { useCatalog, useInventoryFurni } from '../../../../../hooks'; import { CatalogPriceGridDisplayWidgetView } from '../widgets/CatalogPriceGridDisplayWidgetViev'; @@ -55,12 +55,13 @@ export const CatalogGridOfferView: FC = props => return ( + { (offer.clubLevel !== HabboClubLevelEnum.NO_CLUB) && } { (offer.product.productType === ProductTypeEnum.ROBOT) && } - + diff --git a/src/components/catalog/views/page/common/CatalogHeaderView.tsx b/src/components/catalog/views/page/common/CatalogHeaderView.tsx index 5791855ed..29854e5eb 100644 --- a/src/components/catalog/views/page/common/CatalogHeaderView.tsx +++ b/src/components/catalog/views/page/common/CatalogHeaderView.tsx @@ -1,5 +1,5 @@ import { FC } from 'react'; -import { ICatalogNode } from '../../../../../api'; +import { ICatalogNode, LocalizeText } from '../../../../../api'; import { Column, Flex, Text } from '../../../../../common'; import { useCatalog } from '../../../../../hooks'; import { CatalogIconView } from '../../catalog-icon/CatalogIconView'; @@ -12,15 +12,15 @@ export interface CatalogHeaderViewProps export const CatalogHeaderView: FC = props => { const { node = null } = props; - const { currentPage = null, activateNode = null, rootNode = null, getNodeById = null } = useCatalog(); + const { currentPage = null, searchResult = null, rootNode = null, getNodeById = null } = useCatalog(); return ( <> { currentPage && rootNode && } - { currentPage && rootNode && { getNodeById(currentPage.pageId, rootNode).localization } } - + { currentPage && rootNode && { !searchResult ? getNodeById(currentPage.pageId, rootNode).localization : LocalizeText('catalog.search.header') } } + diff --git a/src/components/catalog/views/page/common/CatalogSearchView.tsx b/src/components/catalog/views/page/common/CatalogSearchView.tsx index 03984f2d9..7cba40ea5 100644 --- a/src/components/catalog/views/page/common/CatalogSearchView.tsx +++ b/src/components/catalog/views/page/common/CatalogSearchView.tsx @@ -1,14 +1,13 @@ import { IFurnitureData } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; -import { FaSearch, FaTimes } from 'react-icons/fa'; import { CatalogPage, CatalogType, FilterCatalogNode, FurnitureOffer, GetOfferNodes, GetSessionDataManager, ICatalogNode, ICatalogPage, IPurchasableOffer, LocalizeText, PageLocalization, SearchResult } from '../../../../../api'; -import { Button, Flex } from '../../../../../common'; +import { 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(); + const { currentType = null, rootNode = null, offersToNodes = null, setSearchResult = null, setCurrentPage = null } = useCatalog(); useEffect(() => { @@ -81,14 +80,12 @@ export const CatalogSearchView: FC<{}> = props => return ( - setSearchValue(event.target.value) } /> + setSearchValue(event.target.value) } /> { (!searchValue || !searchValue.length) && } { searchValue && !!searchValue.length && - } + setSearchValue('') } /> } ); } diff --git a/src/components/catalog/views/page/layout/CatalogLayoutBadgeDisplayView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutBadgeDisplayView.tsx index 9b6ec6a8a..b4eaa99af 100644 --- a/src/components/catalog/views/page/layout/CatalogLayoutBadgeDisplayView.tsx +++ b/src/components/catalog/views/page/layout/CatalogLayoutBadgeDisplayView.tsx @@ -1,5 +1,5 @@ import { FC } from 'react'; -import { LocalizeText } from '../../../../../api'; +import { LocalizeText, getTypePrice } from '../../../../../api'; import { Base, Column, Flex, Grid, Text } from '../../../../../common'; import { useCatalog } from '../../../../../hooks'; import { CatalogBadgeSelectorWidgetView } from '../widgets/CatalogBadgeSelectorWidgetView'; @@ -20,33 +20,43 @@ export const CatalogLayoutBadgeDisplayView: FC = props => <> - - - - { LocalizeText('catalog_selectbadge') } - - - - + { !currentOffer && <> { !!page.localization.getImage(1) && } - } + + } { currentOffer && <> + + { currentOffer.localizationName } - { currentOffer.localizationName } - - - - - } + + } + + + + + + + { LocalizeText('catalog_selectproduct') } + + + + + + + + + + + diff --git a/src/components/catalog/views/page/layout/CatalogLayoutDefaultView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutDefaultView.tsx index 6988e4da4..a2396a4a6 100644 --- a/src/components/catalog/views/page/layout/CatalogLayoutDefaultView.tsx +++ b/src/components/catalog/views/page/layout/CatalogLayoutDefaultView.tsx @@ -1,12 +1,12 @@ import { FC } from 'react'; -import { ProductTypeEnum } from '../../../../../api'; -import { Base, Column, Flex, Grid, Text } from '../../../../../common'; +import { getTypePrice } from '../../../../../api'; +import { Base, Column, Flex, 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 { CatalogSelectItemWidgetView } from '../widgets/CatalogSelectItemWidgetView'; import { CatalogSpinnerWidgetView } from '../widgets/CatalogSpinnerWidgetView'; import { CatalogTotalPriceWidget } from '../widgets/CatalogTotalPriceWidget'; import { CatalogViewProductWidgetView } from '../widgets/CatalogViewProductWidgetView'; @@ -28,7 +28,7 @@ export const CatalogLayoutDefaultView: FC = props => <> - + @@ -37,16 +37,18 @@ export const CatalogLayoutDefaultView: FC = props => } - - + + + + + + + + + + + - - - - - - -
); } diff --git a/src/components/catalog/views/page/layout/CatalogLayoutGuildCustomFurniView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutGuildCustomFurniView.tsx index a53ad1cc3..7c02ebe5b 100644 --- a/src/components/catalog/views/page/layout/CatalogLayoutGuildCustomFurniView.tsx +++ b/src/components/catalog/views/page/layout/CatalogLayoutGuildCustomFurniView.tsx @@ -1,5 +1,6 @@ import { FC } from 'react'; -import { Base, Column, Flex, Grid, Text } from '../../../../../common'; +import { getTypePrice } from '../../../../../api'; +import { Base, Column, Flex, Text } from '../../../../../common'; import { useCatalog } from '../../../../../hooks'; import { CatalogGuildBadgeWidgetView } from '../widgets/CatalogGuildBadgeWidgetView'; import { CatalogGuildSelectorWidgetView } from '../widgets/CatalogGuildSelectorWidgetView'; @@ -27,7 +28,7 @@ export const CatalogLayouGuildCustomFurniView: FC = props => - + { currentOffer.localizationName } diff --git a/src/components/catalog/views/page/layout/CatalogLayoutGuildFrontpageView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutGuildFrontpageView.tsx index e3d3c48ec..8b4fc5eae 100644 --- a/src/components/catalog/views/page/layout/CatalogLayoutGuildFrontpageView.tsx +++ b/src/components/catalog/views/page/layout/CatalogLayoutGuildFrontpageView.tsx @@ -16,14 +16,13 @@ export const CatalogLayouGuildFrontpageView: FC = props => - - + ); diff --git a/src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx index 92dd4bff1..2bad14be6 100644 --- a/src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx +++ b/src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx @@ -2,7 +2,7 @@ import { GetRoomAdPurchaseInfoComposer, GetUserEventCatsMessageComposer, Purchas 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 { useCatalog, useMessageEvent, useNavigator, useNotification, useRoomPromote } from '../../../../../hooks'; import { CatalogLayoutProps } from './CatalogLayout.types'; export const CatalogLayoutRoomAdsView: FC = props => @@ -16,6 +16,7 @@ export const CatalogLayoutRoomAdsView: FC = props => const [ categoryId, setCategoryId ] = useState(1); const { categories = null } = useNavigator(); const { setIsVisible = null } = useCatalog(); + const { simpleAlert = null } = useNotification(); const { promoteInformation, isExtended, setIsExtended } = useRoomPromote(); useEffect(() => @@ -30,7 +31,7 @@ export const CatalogLayoutRoomAdsView: FC = props => setIsExtended(false); // This is from hook useRoomPromotte } - }, [ isExtended, eventName, eventDesc, categoryId ]); + }, [ isExtended, eventName, eventDesc, categoryId, roomId, promoteInformation.data.flatId, promoteInformation.data.eventName, promoteInformation.data.eventDescription, promoteInformation.data.categoryId, setIsExtended ]); const resetData = () => { @@ -44,6 +45,10 @@ export const CatalogLayoutRoomAdsView: FC = props => const purchaseAd = () => { + if (!eventName || eventName.length < 5) return simpleAlert(LocalizeText('roomad.alert.name.empty'), null, null, null, LocalizeText('roomad.error.title')); + + if (!roomId || roomId === -1) return simpleAlert(LocalizeText('roomad.no.available.room'), null, null, null, LocalizeText('roomad.error.title')); + const pageId = page.pageId; const offerId = page.offers.length >= 1 ? page.offers[0].offerId : -1; const flatId = roomId; @@ -71,43 +76,36 @@ export const CatalogLayoutRoomAdsView: FC = props => 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') } - + + + + + ) diff --git a/src/components/catalog/views/page/layout/pets/CatalogLayoutPetView.tsx b/src/components/catalog/views/page/layout/pets/CatalogLayoutPetView.tsx index 04d154fc4..1006c6955 100644 --- a/src/components/catalog/views/page/layout/pets/CatalogLayoutPetView.tsx +++ b/src/components/catalog/views/page/layout/pets/CatalogLayoutPetView.tsx @@ -1,8 +1,8 @@ import { ApproveNameMessageComposer, ApproveNameMessageEvent, ColorConverter, GetSellablePetPalettesComposer, PurchaseFromCatalogComposer, SellablePetPaletteData } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { FaFillDrip } from 'react-icons/fa'; -import { DispatchUiEvent, GetPetAvailableColors, GetPetIndexFromLocalization, LocalizeText, SendMessageComposer } from '../../../../../../api'; -import { AutoGrid, Base, Button, Column, Flex, Grid, LayoutGridItem, LayoutPetImageView, Text } from '../../../../../../common'; +import { DispatchUiEvent, GetPetAvailableColors, GetPetIndexFromLocalization, LocalizeText, SendMessageComposer, getTypePrice } from '../../../../../../api'; +import { AutoGrid, Base, Button, Column, Flex, LayoutGridItem, LayoutPetImageView, Text } from '../../../../../../common'; import { CatalogPurchaseFailureEvent } from '../../../../../../events'; import { useCatalog, useMessageEvent } from '../../../../../../hooks'; import { CatalogAddOnBadgeWidgetView } from '../../widgets/CatalogAddOnBadgeWidgetView'; @@ -205,26 +205,26 @@ export const CatalogLayoutPetView: FC = props => <> - + { ((petIndex > -1) && (petIndex <= 7)) && } - - - { !colorsShowing && (sellablePalettes.length > 0) && sellablePalettes.map((palette, index) => - { - return ( - setSelectedPaletteIndex(index) }> - - - ); - }) } - { colorsShowing && (sellableColors.length > 0) && sellableColors.map((colorSet, index) => setSelectedColorIndex(index) } />) } - - + + + { !colorsShowing && (sellablePalettes.length > 0) && sellablePalettes.map((palette, index) => + { + return ( + setSelectedPaletteIndex(index) }> + + + ); + }) } + { colorsShowing && (sellableColors.length > 0) && sellableColors.map((colorSet, index) => setSelectedColorIndex(index) } />) } + + { petBreedName } diff --git a/src/components/catalog/views/page/widgets/CatalogItemGridWidgetView.tsx b/src/components/catalog/views/page/widgets/CatalogItemGridWidgetView.tsx index e4660545c..732837cd9 100644 --- a/src/components/catalog/views/page/widgets/CatalogItemGridWidgetView.tsx +++ b/src/components/catalog/views/page/widgets/CatalogItemGridWidgetView.tsx @@ -44,7 +44,7 @@ export const CatalogItemGridWidgetView: FC = pro } return ( - + { currentPage.offers && (currentPage.offers.length > 0) && currentPage.offers.map((offer, index) => ) } { children } diff --git a/src/components/catalog/views/page/widgets/CatalogPriceDisplayWidgetView.tsx b/src/components/catalog/views/page/widgets/CatalogPriceDisplayWidgetView.tsx index 1c6166108..ed30e6fe7 100644 --- a/src/components/catalog/views/page/widgets/CatalogPriceDisplayWidgetView.tsx +++ b/src/components/catalog/views/page/widgets/CatalogPriceDisplayWidgetView.tsx @@ -1,7 +1,6 @@ import { FC } from 'react'; -import { FaPlus } from 'react-icons/fa'; import { IPurchasableOffer } from '../../../../../api'; -import { Flex, LayoutCurrencyIcon, Text } from '../../../../../common'; +import { Base, Flex, LayoutCurrencyIcon, Text } from '../../../../../common'; import { useCatalog } from '../../../../../hooks'; interface CatalogPriceDisplayWidgetViewProps @@ -23,10 +22,10 @@ export const CatalogPriceDisplayWidgetView: FC 0) && { (offer.priceInCredits * quantity) } - + } { separator && (offer.priceInCredits > 0) && (offer.priceInActivityPoints > 0) && - } + + } { (offer.priceInActivityPoints > 0) && { (offer.priceInActivityPoints * quantity) } diff --git a/src/components/catalog/views/page/widgets/CatalogPriceGridDisplayWidgetViev.tsx b/src/components/catalog/views/page/widgets/CatalogPriceGridDisplayWidgetViev.tsx index 4ba8470b7..583cb5d58 100644 --- a/src/components/catalog/views/page/widgets/CatalogPriceGridDisplayWidgetViev.tsx +++ b/src/components/catalog/views/page/widgets/CatalogPriceGridDisplayWidgetViev.tsx @@ -1,8 +1,6 @@ import { FC } from 'react'; import { IPurchasableOffer } from '../../../../../api'; import { Flex, Text } from '../../../../../common'; -import { useCatalog } from '../../../../../hooks'; -import {FaPlus} from "react-icons/fa"; interface CatalogPriceGridDisplayWidgetViewProps { offer: IPurchasableOffer; @@ -12,8 +10,6 @@ interface CatalogPriceGridDisplayWidgetViewProps export const CatalogPriceGridDisplayWidgetView: FC = props => { const { offer = null, separator = false } = props; - const { purchaseOptions = null } = useCatalog(); - const { quantity = 1 } = purchaseOptions; if(!offer) return null; @@ -21,14 +17,14 @@ export const CatalogPriceGridDisplayWidgetView: FC { (offer.priceInCredits > 0) && - { (offer.priceInCredits * quantity) } + { (offer.priceInCredits) } - } - { separator && (offer.priceInCredits > 0) && (offer.priceInActivityPoints > 0) && - } + + } { (offer.priceInActivityPoints > 0) && - { (offer.priceInActivityPoints * quantity) } + { separator && (offer.priceInCredits > 0) && (offer.priceInActivityPoints > 0) && + } + { (offer.priceInActivityPoints) } } diff --git a/src/components/catalog/views/page/widgets/CatalogPurchaseWidgetView.tsx b/src/components/catalog/views/page/widgets/CatalogPurchaseWidgetView.tsx index 236924743..fc483fd09 100644 --- a/src/components/catalog/views/page/widgets/CatalogPurchaseWidgetView.tsx +++ b/src/components/catalog/views/page/widgets/CatalogPurchaseWidgetView.tsx @@ -1,8 +1,8 @@ import { PurchaseFromCatalogComposer } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { CatalogPurchaseState, CreateLinkEvent, DispatchUiEvent, GetClubMemberLevel, LocalizeText, LocalStorageKeys, Offer, SendMessageComposer } from '../../../../../api'; +import { CatalogPurchaseState, CreateLinkEvent, DispatchUiEvent, GetClubMemberLevel, LocalStorageKeys, LocalizeText, Offer, SendMessageComposer } from '../../../../../api'; import { Button, LayoutLoadingSpinnerView } from '../../../../../common'; -import { CatalogEvent, CatalogInitGiftEvent, CatalogPurchasedEvent, CatalogPurchaseFailureEvent, CatalogPurchaseNotAllowedEvent, CatalogPurchaseSoldOutEvent } from '../../../../../events'; +import { CatalogEvent, CatalogInitGiftEvent, CatalogPurchaseFailureEvent, CatalogPurchaseNotAllowedEvent, CatalogPurchaseSoldOutEvent, CatalogPurchasedEvent } from '../../../../../events'; import { useCatalog, useLocalStorage, usePurse, useUiEvent } from '../../../../../hooks'; interface CatalogPurchaseWidgetViewProps @@ -128,8 +128,6 @@ export const CatalogPurchaseWidgetView: FC = pro const priceCredits = (currentOffer.priceInCredits * purchaseOptions.quantity); const pricePoints = (currentOffer.priceInActivityPoints * purchaseOptions.quantity); - if(GetClubMemberLevel() < currentOffer.clubLevel) return ; - if(isLimitedSoldOut) return
{ LocalizeText('catalog.alert.limited_edition_sold_out.title') } @@ -159,10 +157,10 @@ export const CatalogPurchaseWidgetView: FC = pro return ( <> { (!noGiftOption && !currentOffer.isRentOffer && !currentOffer.product.isUniqueLimitedItem) && - } - + ); } diff --git a/src/components/catalog/views/page/widgets/CatalogSelectItemWidgetView.tsx b/src/components/catalog/views/page/widgets/CatalogSelectItemWidgetView.tsx new file mode 100644 index 000000000..90ee4bd52 --- /dev/null +++ b/src/components/catalog/views/page/widgets/CatalogSelectItemWidgetView.tsx @@ -0,0 +1,17 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../../../api'; +import { Flex, Text } from '../../../../../common'; +import { useCatalog } from '../../../../../hooks'; + +export const CatalogSelectItemWidgetView: FC<{}> = props => +{ + const { currentOffer = null } = useCatalog(); + + if (currentOffer) return null; + + return ( + + { LocalizeText('catalog.purchase.select.info') } + + ); +} diff --git a/src/components/catalog/views/page/widgets/CatalogSpacesWidgetView.tsx b/src/components/catalog/views/page/widgets/CatalogSpacesWidgetView.tsx index 6a53d996d..e7cd2755d 100644 --- a/src/components/catalog/views/page/widgets/CatalogSpacesWidgetView.tsx +++ b/src/components/catalog/views/page/widgets/CatalogSpacesWidgetView.tsx @@ -107,7 +107,7 @@ export const CatalogSpacesWidgetView: FC = props = { SPACES_GROUP_NAMES.map((name, index) => ) } - + { offers && (offers.length > 0) && offers.map((offer, index) => setSelectedOffer(offer) } />) } { children } diff --git a/src/components/catalog/views/page/widgets/CatalogSpinnerWidgetView.tsx b/src/components/catalog/views/page/widgets/CatalogSpinnerWidgetView.tsx index c3d831b09..fe2b8d515 100644 --- a/src/components/catalog/views/page/widgets/CatalogSpinnerWidgetView.tsx +++ b/src/components/catalog/views/page/widgets/CatalogSpinnerWidgetView.tsx @@ -1,5 +1,4 @@ -import { FC } from 'react'; -import { FaCaretLeft, FaCaretRight } from 'react-icons/fa'; +import { ChangeEvent, FC, useState } from 'react'; import { LocalizeText } from '../../../../../api'; import { Flex, Text } from '../../../../../common'; import { useCatalog } from '../../../../../hooks'; @@ -9,37 +8,42 @@ const MAX_VALUE: number = 100; export const CatalogSpinnerWidgetView: FC<{}> = props => { + const [ quantityInput, setQuantityInput ] = useState('1'); const { currentOffer = null, purchaseOptions = null, setPurchaseOptions = null } = useCatalog(); const { quantity = 1 } = purchaseOptions; - const updateQuantity = (value: number) => + const updateQuantity = (value: string | number) => { - if(isNaN(value)) value = 1; + value = Math.max(Number(value), MIN_VALUE); + value = Math.min(Number(value), MAX_VALUE); - value = Math.max(value, MIN_VALUE); - value = Math.min(value, MAX_VALUE); - - if(value === quantity) return; + if(Number(value) === quantity) return; setPurchaseOptions(prevValue => { const newValue = { ...prevValue }; - newValue.quantity = value; + newValue.quantity = !value ? MIN_VALUE : Number(value); return newValue; }); } + const changeQuantity = (event: ChangeEvent) => + { + const value = event.target.value; + + setQuantityInput(Number(value) > 100 ? MAX_VALUE.toString() : value); + updateQuantity(value); + } + if(!currentOffer || !currentOffer.bundlePurchaseAllowed) return null; return ( <> - { LocalizeText('catalog.bundlewidget.spinner.select.amount') } + { LocalizeText('catalog.bundlewidget.quantity') } - updateQuantity(quantity - 1) } /> - updateQuantity(event.target.valueAsNumber) } /> - updateQuantity(quantity + 1) } /> + ); diff --git a/src/components/catalog/views/page/widgets/CatalogTotalPriceWidget.tsx b/src/components/catalog/views/page/widgets/CatalogTotalPriceWidget.tsx index bbe9a18c1..23a43daa1 100644 --- a/src/components/catalog/views/page/widgets/CatalogTotalPriceWidget.tsx +++ b/src/components/catalog/views/page/widgets/CatalogTotalPriceWidget.tsx @@ -1,5 +1,5 @@ import { FC } from 'react'; -import { Flex, ColumnProps } from '../../../../../common'; +import { ColumnProps, Flex } from '../../../../../common'; import { useCatalog } from '../../../../../hooks'; import { CatalogPriceDisplayWidgetView } from './CatalogPriceDisplayWidgetView'; @@ -14,7 +14,7 @@ export const CatalogTotalPriceWidget: FC = pr return ( - + ); } diff --git a/src/components/catalog/views/page/widgets/CatalogViewTrophiesWidgetView.tsx b/src/components/catalog/views/page/widgets/CatalogViewTrophiesWidgetView.tsx new file mode 100644 index 000000000..3fdbb0124 --- /dev/null +++ b/src/components/catalog/views/page/widgets/CatalogViewTrophiesWidgetView.tsx @@ -0,0 +1,24 @@ +import { FC, useEffect } from 'react'; +import { Offer, ProductTypeEnum } from '../../../../../api'; +import { LayoutFurniImageView } from '../../../../../common'; +import { useCatalog } from '../../../../../hooks'; + +export const CatalogViewTrophiesWidgetView: FC<{}> = props => +{ + const { currentOffer = null, roomPreviewer = null, purchaseOptions = null } = useCatalog(); + const { previewStuffData = null } = purchaseOptions; + + useEffect(() => + { + if(!currentOffer || (currentOffer.pricingModel === Offer.PRICING_MODEL_BUNDLE) || !roomPreviewer) return; + + const product = currentOffer.product; + + if(!product) return; + + }, [ currentOffer, previewStuffData, roomPreviewer ]); + + if(!currentOffer) return null; + + return +} diff --git a/src/components/chat-history/ChatHistoryView.scss b/src/components/chat-history/ChatHistoryView.scss index 2a0a9b498..0e58d9d30 100644 --- a/src/components/chat-history/ChatHistoryView.scss +++ b/src/components/chat-history/ChatHistoryView.scss @@ -6,7 +6,7 @@ top: 0; height: 100%; left: 0; - width: 30vw; + width: 400px; border-right: 1px solid #000; -webkit-box-shadow: inset -2px 0px 0px 0px #34322d; box-shadow: inset -2px 0px 0px 0px #34322d; diff --git a/src/components/friends/FriendsView.scss b/src/components/friends/FriendsView.scss index 8aa06a17b..d355ca059 100644 --- a/src/components/friends/FriendsView.scss +++ b/src/components/friends/FriendsView.scss @@ -9,6 +9,7 @@ &.icon-heart { width: 16px; height: 14px; background-position: -5px -67px; + margin-bottom: -3px; } &.icon-new-message { @@ -18,7 +19,8 @@ &.icon-none { width: 16px; height: 14px; - background-position: -31px -67px; + background-position: 7px 0px; + margin-bottom: -3px; } &.icon-profile { @@ -39,6 +41,7 @@ &.icon-smile { width: 16px; height: 14px; background-position: -57px -67px; + margin-bottom: -3px; } &.icon-warning { @@ -105,14 +108,45 @@ border: 0; border-bottom: 1px solid rgba($black, 0.2); } + + .friendlist-bottom { + margin-left: 106px; + overflow: hidden; + } + + .avatar { + width: 29px; + height: 0px; + margin-top: 16px; + margin-left: -15px; + position: relative; + } + + .selected-user { + background-color: #3CB8FF !important; + } +} + +.select-relation { + width: 27px; + border: 1px solid #999; + border-radius: 4px; + padding: 1px; + background-color: #eee; } .nitro-friends-room-invite { - width: $friends-list-width; + width: 211px; + height: 175px; + + .textarea-invite { + height: 69px; + } } .nitro-friends-remove-confirmation { - width: $friends-list-width; + width: 160px; + height: 200px; } .friend-bar { @@ -150,7 +184,7 @@ min-height: 36px; &.friend-bar-item-active { - margin-bottom:26px; + margin-bottom: 26px; } &.find-friends { @@ -181,6 +215,10 @@ } } + .font-size-friend { + font-size: 12px; + } + .friend-bar-text { overflow: hidden; text-overflow: ellipsis; @@ -212,14 +250,21 @@ height: 34px; background-image: url('@/assets/images/toolbar/friend-search.png'); } + + &.friend-bar-item-active { + margin-bottom: 146px; + } } } + + .search-content { + margin-left: -37px; + } } .nitro-friends-messenger { width: $messenger-width; height: $messenger-height; - resize: none; font-size: 10px; @@ -265,9 +310,10 @@ background: transparent; height: 27px; position: absolute; - width: 270px; + width: calc(100% - 16px); bottom: 14px; outline: 0px; + padding-right: 76px; } .thread-message { diff --git a/src/components/friends/views/friends-bar/FriendBarItemView.tsx b/src/components/friends/views/friends-bar/FriendBarItemView.tsx index 84ec6a674..70ab90746 100644 --- a/src/components/friends/views/friends-bar/FriendBarItemView.tsx +++ b/src/components/friends/views/friends-bar/FriendBarItemView.tsx @@ -1,7 +1,7 @@ -import { MouseEventType } from '@nitrots/nitro-renderer'; +import { FindNewFriendsMessageComposer, MouseEventType } from '@nitrots/nitro-renderer'; import { FC, useEffect, useRef, useState } from 'react'; -import { GetUserProfile, LocalizeText, MessengerFriend, OpenMessengerChat } from '../../../../api'; -import { Base, LayoutAvatarImageView, LayoutBadgeImageView } from '../../../../common'; +import { GetUserProfile, LocalizeText, MessengerFriend, OpenMessengerChat, SendMessageComposer } from '../../../../api'; +import { Base, Button, LayoutAvatarImageView, LayoutBadgeImageView } from '../../../../common'; import { useFriends } from '../../../../hooks'; export const FriendBarItemView: FC<{ friend: MessengerFriend }> = props => @@ -33,9 +33,15 @@ export const FriendBarItemView: FC<{ friend: MessengerFriend }> = props => if(!friend) { return ( -
+
setVisible(prevValue => !prevValue) }>
{ LocalizeText('friend.bar.find.title') }
+ { isVisible && +
+
{ LocalizeText('friend.bar.find.text') }
+ +
+ }
); } diff --git a/src/components/friends/views/friends-list/FriendsListRemoveConfirmationView.tsx b/src/components/friends/views/friends-list/FriendsListRemoveConfirmationView.tsx index 1ac383559..e23cdb712 100644 --- a/src/components/friends/views/friends-list/FriendsListRemoveConfirmationView.tsx +++ b/src/components/friends/views/friends-list/FriendsListRemoveConfirmationView.tsx @@ -1,6 +1,6 @@ import { FC } from 'react'; import { LocalizeText } from '../../../../api'; -import { Button, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; +import { Button, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; interface FriendsRemoveConfirmationViewProps { @@ -15,13 +15,15 @@ export const FriendsRemoveConfirmationView: FC - + + -
{ removeFriendsText }
- - - + + { removeFriendsText } + + + +
diff --git a/src/components/friends/views/friends-list/FriendsListRoomInviteView.tsx b/src/components/friends/views/friends-list/FriendsListRoomInviteView.tsx index 048d7016c..9bc3decff 100644 --- a/src/components/friends/views/friends-list/FriendsListRoomInviteView.tsx +++ b/src/components/friends/views/friends-list/FriendsListRoomInviteView.tsx @@ -1,6 +1,6 @@ import { FC, useState } from 'react'; import { LocalizeText } from '../../../../api'; -import { Button, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; +import { Button, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; interface FriendsRoomInviteViewProps { @@ -15,15 +15,17 @@ export const FriendsRoomInviteView: FC = props => const [ roomInviteMessage, setRoomInviteMessage ] = useState(''); return ( - - + + - { LocalizeText('friendlist.invite.summary', [ 'count' ], [ selectedFriendsIds.length.toString() ]) } - - { LocalizeText('friendlist.invite.note') } - - - + + { LocalizeText('friendlist.invite.summary', [ 'count' ], [ selectedFriendsIds.length.toString() ]) } + + { LocalizeText('friendlist.invite.note') } + + + + diff --git a/src/components/friends/views/friends-list/FriendsListSearchView.tsx b/src/components/friends/views/friends-list/FriendsListSearchView.tsx index 4912555f9..6694d616c 100644 --- a/src/components/friends/views/friends-list/FriendsListSearchView.tsx +++ b/src/components/friends/views/friends-list/FriendsListSearchView.tsx @@ -1,17 +1,18 @@ import { HabboSearchComposer, HabboSearchResultData, HabboSearchResultEvent } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; -import { LocalizeText, OpenMessengerChat, SendMessageComposer } from '../../../../api'; +import { FriendListTabs, GetUserProfile, 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 { - + isFromSearchToolbar?: boolean; + setShowHoverText?: (text: string) => void; } export const FriendsSearchView: FC = props => { - const { ...rest } = props; + const { isFromSearchToolbar = false, setShowHoverText = null, ...rest } = props; const [ searchValue, setSearchValue ] = useState(''); const [ friendResults, setFriendResults ] = useState(null); const [ otherResults, setOtherResults ] = useState(null); @@ -40,32 +41,27 @@ export const FriendsSearchView: FC = props => }, [ searchValue ]); return ( - - - - setSearchValue(event.target.value) } /> - - + setShowHoverText(e) } { ...rest }> + + { friendResults && <> { (friendResults.length === 0) && - { LocalizeText('friendlist.search.nofriendsfound') } } + { LocalizeText('friendlist.search.nofriendsfound') } } { (friendResults.length > 0) && - { LocalizeText('friendlist.search.friendscaption', [ 'cnt' ], [ friendResults.length.toString() ]) } -
+ { LocalizeText('friendlist.search.friendscaption', [ 'cnt' ], [ friendResults.length.toString() ]) } { friendResults.map(result => { return ( - - + GetUserProfile(result.avatarId) }> + -
{ result.avatarName }
+ { result.avatarName }
- { result.isAvatarOnline && - OpenMessengerChat(result.avatarId) } title={ LocalizeText('friendlist.tip.im') } /> } + OpenMessengerChat(result.avatarId) } onMouseEnter={ () => setShowHoverText(LocalizeText('friendlist.tip.im')) } onMouseLeave={ () => setShowHoverText('') } />
) @@ -76,23 +72,22 @@ export const FriendsSearchView: FC = props => { otherResults && <> { (otherResults.length === 0) && - { LocalizeText('friendlist.search.noothersfound') } } + { LocalizeText('friendlist.search.noothersfound') } } { (otherResults.length > 0) && - { LocalizeText('friendlist.search.otherscaption', [ 'cnt' ], [ otherResults.length.toString() ]) } -
+ { LocalizeText('friendlist.search.otherscaption', [ 'cnt' ], [ otherResults.length.toString() ]) } { otherResults.map(result => { return ( - - + GetUserProfile(result.avatarId) }> + -
{ result.avatarName }
+ { result.avatarName }
{ canRequestFriend(result.avatarId) && - requestFriend(result.avatarId, result.avatarName) } title={ LocalizeText('friendlist.tip.addfriend') } /> } + requestFriend(result.avatarId, result.avatarName) } onMouseEnter={ () => setShowHoverText(LocalizeText('friendlist.tip.addfriend')) } onMouseLeave={ () => setShowHoverText('') } /> }
) @@ -101,6 +96,18 @@ export const FriendsSearchView: FC = props =>
} }
+ + + + setSearchValue(event.target.value) } onMouseEnter={ () => setShowHoverText(LocalizeText('friendlist.tip.searchstr')) } onMouseLeave={ () => setShowHoverText('') } /> + + + setSearchValue(searchValue) } onMouseEnter={ () => setShowHoverText(LocalizeText('friendlist.tip.search')) } onMouseLeave={ () => setShowHoverText('') }> + { LocalizeText('generic.search') } + + + +
); diff --git a/src/components/friends/views/friends-list/FriendsListView.tsx b/src/components/friends/views/friends-list/FriendsListView.tsx index c396a7b78..82b5d682c 100644 --- a/src/components/friends/views/friends-list/FriendsListView.tsx +++ b/src/components/friends/views/friends-list/FriendsListView.tsx @@ -1,14 +1,14 @@ import { ILinkEventTracker, RemoveFriendComposer, SendRoomInviteComposer } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { AddEventLinkTracker, LocalizeText, MessengerFriend, RemoveLinkEventTracker, SendMessageComposer } from '../../../../api'; -import { Flex, NitroCardAccordionSetView, NitroCardAccordionView, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; +import { AddEventLinkTracker, FriendListTabs, LocalizeText, MessengerFriend, RemoveLinkEventTracker, SendMessageComposer } from '../../../../api'; +import { Column, Flex, NitroCardAccordionSetView, NitroCardAccordionView, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; import { NitroCardAccordionSetInnerView } from '../../../../common/card/accordion/NitroCardAccordionSetInnerView'; import { useFriends } from '../../../../hooks'; -import { FriendsListGroupView } from './friends-list-group/FriendsListGroupView'; -import { FriendsListRequestView } from './friends-list-request/FriendsListRequestView'; 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 => { @@ -16,6 +16,8 @@ export const FriendsListView: FC<{}> = props => const [ selectedFriendsIds, setSelectedFriendsIds ] = useState([]); const [ showRoomInvite, setShowRoomInvite ] = useState(false); const [ showRemoveFriendsConfirmation, setShowRemoveFriendsConfirmation ] = useState(false); + const [ showHoverText, setShowHoverText ] = useState(null); + const [ isFromSearchToolbar, setIsFromSearchToolbar ] = useState(false); const { onlineFriends = [], offlineFriends = [], requests = [], requestFriend = null } = useFriends(); const removeFriendsText = useMemo(() => @@ -97,16 +99,24 @@ export const FriendsListView: FC<{}> = props => { case 'show': setIsVisible(true); + setIsFromSearchToolbar(false); return; case 'hide': setIsVisible(false); + setIsFromSearchToolbar(false); return; case 'toggle': setIsVisible(prevValue => !prevValue); + setIsFromSearchToolbar(false); + return; + case 'search': + setIsVisible(true); + setIsFromSearchToolbar(true); return; case 'request': if(parts.length < 4) return; - + + setIsFromSearchToolbar(false); requestFriend(parseInt(parts[2]), parts[3]); } }, @@ -123,34 +133,33 @@ export const FriendsListView: FC<{}> = props => return ( <> - setIsVisible(false) } /> + setIsVisible(false) } /> - - - - - - - - { selectedFriendsIds && selectedFriendsIds.length === 0 && - -
-
-
- } - { selectedFriendsIds && selectedFriendsIds.length > 0 && - -
setShowRoomInvite(true) } /> -
-
setShowRemoveFriendsConfirmation(true) } /> - } + setShowHoverText(e) }> + + + + setShowHoverText(e) } /> + + + setShowHoverText(e) } /> + + + + +
setShowRoomInvite(true) } onMouseEnter={ selectedFriendsIds && selectedFriendsIds.length === 0 ? null : () => setShowHoverText(LocalizeText('friendlist.tip.invite')) } onMouseLeave={ selectedFriendsIds && selectedFriendsIds.length === 0 ? null : () => setShowHoverText('') } /> +
setShowHoverText(LocalizeText('friendlist.tip.home')) } onMouseLeave={ selectedFriendsIds && selectedFriendsIds.length === 0 ? null : () => setShowHoverText('') } /> +
setShowRemoveFriendsConfirmation(true) } onMouseEnter={ selectedFriendsIds && selectedFriendsIds.length === 0 ? null : () => setShowHoverText(LocalizeText('friendlist.tip.remove')) } onMouseLeave={ selectedFriendsIds && selectedFriendsIds.length === 0 ? null : () => setShowHoverText('') } /> + + + - - + 0) ? true : false } setShowHoverText={ (e) => setShowHoverText(e) } /> + setShowHoverText(e) } /> -
+
{ showHoverText }
{ showRoomInvite && setShowRoomInvite(false) } sendRoomInvite={ sendRoomInvite } /> } 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 index 9fe77d494..12058cd8d 100644 --- a/src/components/friends/views/friends-list/friends-list-group/FriendsListGroupItemView.tsx +++ b/src/components/friends/views/friends-list/friends-list-group/FriendsListGroupItemView.tsx @@ -1,11 +1,19 @@ import { FC, MouseEvent, useState } from 'react'; import { LocalizeText, MessengerFriend, OpenMessengerChat } from '../../../../../api'; -import { Base, Flex, NitroCardAccordionItemView, UserProfileIconView } from '../../../../../common'; +import { Base, Column, Flex, LayoutAvatarImageView, NitroCardAccordionItemView, Text, UserProfileIconView } from '../../../../../common'; import { useFriends } from '../../../../../hooks'; -export const FriendsListGroupItemView: FC<{ friend: MessengerFriend, selected: boolean, selectFriend: (userId: number) => void }> = props => +interface FriendsListGroupItemViewProps { - const { friend = null, selected = false, selectFriend = null } = props; + friend: MessengerFriend; + selected: boolean; + selectFriend: (userId: number) => void; + setShowHoverText?: (text: string) => void; +} + +export const FriendsListGroupItemView: FC = props => +{ + const { friend = null, selected = false, selectFriend = null, setShowHoverText = null } = props; const [ isRelationshipOpen, setIsRelationshipOpen ] = useState(false); const { followFriend = null, updateRelationship = null } = useFriends(); @@ -41,44 +49,49 @@ export const FriendsListGroupItemView: FC<{ friend: MessengerFriend, selected: b const getCurrentRelationshipName = () => { - if(!friend) return 'none'; + if(!friend) return null; switch(friend.relationshipStatus) { case MessengerFriend.RELATIONSHIP_HEART: return 'heart'; case MessengerFriend.RELATIONSHIP_SMILE: return 'smile'; case MessengerFriend.RELATIONSHIP_BOBBA: return 'bobba'; - default: return 'none'; + default: return null; } } if(!friend) return null; return ( - selectFriend(friend.id) }> - - event.stopPropagation() }> + selectFriend(friend.id) }> + + { (friend.id > 0 && friend.online) && + + + + } + event.stopPropagation() } onMouseEnter={ () => setShowHoverText(LocalizeText('infostand.profile.link.tooltip')) } onMouseLeave={ () => setShowHoverText('') }> -
{ friend.name }
+ { friend.name }
- { !isRelationshipOpen && - <> - { friend.followingAllowed && - } - { friend.online && - } - { (friend.id > 0) && - } - } + setShowHoverText(LocalizeText('infostand.link.relationship')) } onMouseLeave={ () => setShowHoverText('') }> + { (friend.id > 0) && } + { (friend.id > 0) && } + + { friend.followingAllowed && setShowHoverText(LocalizeText('friendlist.tip.follow')) } onMouseLeave={ () => setShowHoverText('') } /> } + setShowHoverText(LocalizeText('friendlist.tip.im')) } onMouseLeave={ () => setShowHoverText('') } /> { isRelationshipOpen && <> - clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_HEART) } /> - clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_SMILE) } /> - clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_BOBBA) } /> - clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_NONE) } /> - } + + clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_NONE) } /> + clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_HEART) } /> + clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_SMILE) } /> + clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_BOBBA) } /> + + + }
); 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 index c593003a0..ade41e3f9 100644 --- a/src/components/friends/views/friends-list/friends-list-group/FriendsListGroupView.tsx +++ b/src/components/friends/views/friends-list/friends-list-group/FriendsListGroupView.tsx @@ -7,17 +7,18 @@ interface FriendsListGroupViewProps list: MessengerFriend[]; selectedFriendsIds: number[]; selectFriend: (userId: number) => void; + setShowHoverText?: (text: string) => void; } export const FriendsListGroupView: FC = props => { - const { list = null, selectedFriendsIds = null, selectFriend = null } = props; + const { list = null, selectedFriendsIds = null, selectFriend = null, setShowHoverText = null } = props; if(!list || !list.length) return null; return ( <> - { list.map((item, index) => = 0) } selectFriend={ selectFriend } />) } + { list.map((item, index) => = 0) } selectFriend={ selectFriend } setShowHoverText={ (e) => setShowHoverText(e) } />) } ); } 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 index de5d3a3b0..5b495e3b1 100644 --- a/src/components/friends/views/friends-list/friends-list-request/FriendsListRequestItemView.tsx +++ b/src/components/friends/views/friends-list/friends-list-request/FriendsListRequestItemView.tsx @@ -1,24 +1,30 @@ import { FC } from 'react'; -import { MessengerRequest } from '../../../../../api'; +import { LocalizeText, MessengerRequest } from '../../../../../api'; import { Base, Flex, NitroCardAccordionItemView, UserProfileIconView } from '../../../../../common'; import { useFriends } from '../../../../../hooks'; -export const FriendsListRequestItemView: FC<{ request: MessengerRequest }> = props => +interface FriendsListRequestItemViewProps { - const { request = null } = props; + request: MessengerRequest; + setShowHoverText?: (text: string) => void; +} + +export const FriendsListRequestItemView: FC = props => +{ + const { request = null, setShowHoverText = null } = props; const { requestResponse = null } = useFriends(); if(!request) return null; return ( - + setShowHoverText(LocalizeText('infostand.profile.link.tooltip')) } onMouseLeave={ () => setShowHoverText('') }>
{ request.name }
- - requestResponse(request.id, true) } /> - requestResponse(request.id, false) } /> + + requestResponse(request.id, true) } onMouseEnter={ () => setShowHoverText(LocalizeText('friendbar.request.accept')) } onMouseLeave={ () => setShowHoverText('') } /> + requestResponse(request.id, false) } onMouseEnter={ () => setShowHoverText(LocalizeText('friendbar.request.decline')) } onMouseLeave={ () => setShowHoverText('') } />
); 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 index 5f6e99184..5921e563a 100644 --- a/src/components/friends/views/friends-list/friends-list-request/FriendsListRequestView.tsx +++ b/src/components/friends/views/friends-list/friends-list-request/FriendsListRequestView.tsx @@ -1,27 +1,41 @@ import { FC } from 'react'; -import { LocalizeText } from '../../../../../api'; -import { Button, Column, Flex, NitroCardAccordionSetView, NitroCardAccordionSetViewProps } from '../../../../../common'; +import { FriendListTabs, LocalizeText } from '../../../../../api'; +import { Base, Column, Flex, NitroCardAccordionSetView, NitroCardAccordionSetViewProps, Text } from '../../../../../common'; import { useFriends } from '../../../../../hooks'; import { FriendsListRequestItemView } from './FriendsListRequestItemView'; -export const FriendsListRequestView: FC = props => +interface FriendsListRequestViewProps extends NitroCardAccordionSetViewProps { - const { children = null, ...rest } = props; + setShowHoverText?: (text: string) => void; +} + +export const FriendsListRequestView: FC = props => +{ + const { setShowHoverText = null, children = null, ...rest } = props; const { requests = [], requestResponse = null } = useFriends(); if(!requests.length) return null; return ( - - - - { requests.map((request, index) => ) } + setShowHoverText(e) } { ...rest }> + + + { requests.map((request, index) => setShowHoverText(e) } />) } + + + + + requestResponse(-1, true) } onMouseEnter={ () => setShowHoverText(LocalizeText('friendlist.requests.acceptall')) } onMouseLeave={ () => setShowHoverText('') }> + + { LocalizeText('friendlist.requests.acceptall') } + + requestResponse(-1, false) } onMouseEnter={ () => setShowHoverText(LocalizeText('friendlist.requests.dismissall')) } onMouseLeave={ () => setShowHoverText('') }> + + { LocalizeText('friendlist.requests.dismissall') } + + + - - - { children } diff --git a/src/components/friends/views/messenger/FriendsMessengerView.tsx b/src/components/friends/views/messenger/FriendsMessengerView.tsx index ff6e8f73f..5eee7a4cc 100644 --- a/src/components/friends/views/messenger/FriendsMessengerView.tsx +++ b/src/components/friends/views/messenger/FriendsMessengerView.tsx @@ -1,9 +1,9 @@ -import { FollowFriendMessageComposer, ILinkEventTracker } from '@nitrots/nitro-renderer'; +import { FollowFriendFailedEvent, FollowFriendMessageComposer, ILinkEventTracker } from '@nitrots/nitro-renderer'; import { FC, KeyboardEvent, useEffect, useRef, useState } from 'react'; -import { AddEventLinkTracker, GetSessionDataManager, GetUserProfile, LocalizeText, RemoveLinkEventTracker, ReportType, SendMessageComposer } from '../../../../api'; +import { AddEventLinkTracker, GetSessionDataManager, GetUserProfile, LocalizeText, MessengerFollowFriendFailedType, RemoveLinkEventTracker, ReportType, SendMessageComposer } from '../../../../api'; import { ButtonGroup, Column, Flex, LayoutAvatarImageView, LayoutBadgeImageView, LayoutItemCountView, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; import { LayoutMessengerGrid } from '../../../../common/layout/LayoutMessengerGrid'; -import { useHelp, useMessenger } from '../../../../hooks'; +import { useHelp, useMessageEvent, useMessenger, useNotification } from '../../../../hooks'; import { FriendsMessengerThreadView } from './messenger-thread/FriendsMessengerThreadView'; export const FriendsMessengerView: FC<{}> = props => @@ -12,6 +12,7 @@ export const FriendsMessengerView: FC<{}> = props => const [ lastThreadId, setLastThreadId ] = useState(-1); const [ messageText, setMessageText ] = useState(''); const { visibleThreads = [], activeThread = null, getMessageThread = null, sendMessage = null, setActiveThreadId = null, closeThread = null } = useMessenger(); + const { simpleAlert = null } = useNotification(); const { report = null } = useHelp(); const messagesBox = useRef(); @@ -34,6 +35,29 @@ export const FriendsMessengerView: FC<{}> = props => send(); } + useMessageEvent(FollowFriendFailedEvent, event => + { + const parser = event.getParser(); + + if (!parser) return null; + + switch(parser.errorCode) + { + case MessengerFollowFriendFailedType.NOT_IN_FRIEND_LIST: + simpleAlert(LocalizeText('friendlist.followerror.notfriend'), null, null, null, LocalizeText('friendlist.alert.title'), null); + break; + case MessengerFollowFriendFailedType.FRIEND_OFFLINE: + simpleAlert(LocalizeText('friendlist.followerror.offline'), null, null, null, LocalizeText('friendlist.alert.title'), null); + break; + case MessengerFollowFriendFailedType.FRIEND_NOT_IN_ROOM: + simpleAlert(LocalizeText('friendlist.followerror.hotelview'), null, null, null, LocalizeText('friendlist.alert.title'), null); + break; + case MessengerFollowFriendFailedType.FRIEND_BLOCKED_STALKING: + simpleAlert(LocalizeText('friendlist.followerror.prevented'), null, null, null, LocalizeText('friendlist.alert.title'), null); + break; + } + }); + useEffect(() => { const linkTracker: ILinkEventTracker = { diff --git a/src/components/friends/views/messenger/messenger-thread/FriendsMessengerThreadGroup.tsx b/src/components/friends/views/messenger/messenger-thread/FriendsMessengerThreadGroup.tsx index 10796fc86..6752d4ebd 100644 --- a/src/components/friends/views/messenger/messenger-thread/FriendsMessengerThreadGroup.tsx +++ b/src/components/friends/views/messenger/messenger-thread/FriendsMessengerThreadGroup.tsx @@ -32,10 +32,15 @@ export const FriendsMessengerThreadGroup: FC<{ thread: MessengerThread, group: M { (chat.type === MessengerThreadChat.SECURITY_NOTIFICATION) && - + { chat.message } } + { (!thread.participant.online) && + + + { LocalizeText('messenger.notification.persisted_messages') } + } { (chat.type === MessengerThreadChat.ROOM_INVITE) && diff --git a/src/components/groups/views/GroupCreatorView.tsx b/src/components/groups/views/GroupCreatorView.tsx index 7f75b0535..bef485ae3 100644 --- a/src/components/groups/views/GroupCreatorView.tsx +++ b/src/components/groups/views/GroupCreatorView.tsx @@ -1,8 +1,8 @@ -import { GroupBuyComposer, GroupBuyDataComposer, GroupBuyDataEvent } from '@nitrots/nitro-renderer'; +import { GroupBuyComposer, GroupBuyDataComposer, GroupBuyDataEvent, GuildEditFailedMessageEvent, HabboClubLevelEnum } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; -import { HasHabboClub, IGroupData, LocalizeText, SendMessageComposer } from '../../../api'; +import { CreateLinkEvent, GetSessionDataManager, HasHabboClub, IGroupData, LocalizeText, SendMessageComposer } from '../../../api'; import { Base, Button, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; -import { useMessageEvent } from '../../../hooks'; +import { useMessageEvent, useNotification } from '../../../hooks'; import { GroupTabBadgeView } from './tabs/GroupTabBadgeView'; import { GroupTabColorsView } from './tabs/GroupTabColorsView'; import { GroupTabCreatorConfirmationView } from './tabs/GroupTabCreatorConfirmationView'; @@ -23,6 +23,7 @@ export const GroupCreatorView: FC = props => const [ groupData, setGroupData ] = useState(null); const [ availableRooms, setAvailableRooms ] = useState<{ id: number, name: string }[]>(null); const [ purchaseCost, setPurchaseCost ] = useState(0); + const { simpleAlert = null } = useNotification(); const onCloseClose = () => { @@ -97,6 +98,17 @@ export const GroupCreatorView: FC = props => setPurchaseCost(parser.groupCost); }); + useMessageEvent(GuildEditFailedMessageEvent, event => + { + const parser = event.getParser(); + + if (!parser) return null; + + GetSessionDataManager().clubLevel === HabboClubLevelEnum.NO_CLUB + ? CreateLinkEvent('habboUI/open/hccenter') + : simpleAlert(LocalizeText(`group.edit.fail.${ parser.reason }`), null, null, null, LocalizeText('group.edit.fail.title')); + }); + useEffect(() => { setCurrentTab(1); diff --git a/src/components/groups/views/GroupMembersView.tsx b/src/components/groups/views/GroupMembersView.tsx index 317bbcc3a..d6022880f 100644 --- a/src/components/groups/views/GroupMembersView.tsx +++ b/src/components/groups/views/GroupMembersView.tsx @@ -1,7 +1,7 @@ -import { GroupAdminGiveComposer, GroupAdminTakeComposer, GroupConfirmMemberRemoveEvent, GroupConfirmRemoveMemberComposer, GroupMemberParser, GroupMembersComposer, GroupMembersEvent, GroupMembershipAcceptComposer, GroupMembershipDeclineComposer, GroupMembersParser, GroupRank, GroupRemoveMemberComposer, ILinkEventTracker } from '@nitrots/nitro-renderer'; +import { GroupAdminGiveComposer, GroupAdminTakeComposer, GroupConfirmMemberRemoveEvent, GroupConfirmRemoveMemberComposer, GroupMemberParser, GroupMembersComposer, GroupMembersEvent, GroupMembershipAcceptComposer, GroupMembershipDeclineComposer, GroupMembersParser, GroupRank, GroupRemoveMemberComposer, GuildMemberMgmtFailedMessageEvent, HabboGroupJoinFailedMessageEvent, HabboGroupJoinFailedMessageParser, ILinkEventTracker } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useState } from 'react'; import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; -import { AddEventLinkTracker, GetSessionDataManager, GetUserProfile, LocalizeText, RemoveLinkEventTracker, SendMessageComposer } from '../../../api'; +import { AddEventLinkTracker, CreateLinkEvent, GetSessionDataManager, GetUserProfile, LocalizeText, RemoveLinkEventTracker, SendMessageComposer } from '../../../api'; import { Base, Button, Column, Flex, Grid, LayoutAvatarImageView, LayoutBadgeImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; import { useMessageEvent, useNotification } from '../../../hooks'; @@ -14,7 +14,7 @@ export const GroupMembersView: FC<{}> = props => const [ totalPages, setTotalPages ] = useState(0); const [ searchQuery, setSearchQuery ] = useState(''); const [ removingMemberName, setRemovingMemberName ] = useState(null); - const { showConfirm = null } = useNotification(); + const { showConfirm = null, simpleAlert = null } = useNotification(); const getRankDescription = (member: GroupMemberParser) => { @@ -96,6 +96,28 @@ export const GroupMembersView: FC<{}> = props => setRemovingMemberName(null); }); + useMessageEvent(GuildMemberMgmtFailedMessageEvent, event => + { + const parser = event.getParser(); + + if (!parser) return null; + + simpleAlert(LocalizeText(`group.membermgmt.fail.${ parser.reason }`), null, null, null, LocalizeText('group.membermgmt.fail.title')); + + refreshMembers(); + }); + + useMessageEvent(HabboGroupJoinFailedMessageEvent, event => + { + const parser = event.getParser(); + + if (!parser) return null; + + parser.reason == HabboGroupJoinFailedMessageParser.INSUFFICIENT_SUBSCRIPTION_LEVEL + ? CreateLinkEvent('habboUI/open/hccenter') + : simpleAlert(LocalizeText(`group.joinfail.${ parser.reason }`), null, null, null, LocalizeText('group.joinfail.title')); + }); + useEffect(() => { const linkTracker: ILinkEventTracker = { diff --git a/src/components/groups/views/GroupRoomInformationView.tsx b/src/components/groups/views/GroupRoomInformationView.tsx index dceceea3e..96a489dad 100644 --- a/src/components/groups/views/GroupRoomInformationView.tsx +++ b/src/components/groups/views/GroupRoomInformationView.tsx @@ -1,6 +1,5 @@ import { DesktopViewEvent, GetGuestRoomResultEvent, 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, GetSessionDataManager, GroupMembershipType, GroupType, LocalizeText, SendMessageComposer, TryJoinGroup } from '../../../api'; import { Base, Button, Column, Flex, LayoutBadgeImageView, Text } from '../../../common'; import { useMessageEvent, useNotification } from '../../../hooks'; @@ -108,9 +107,14 @@ export const GroupRoomInformationView: FC<{}> = props => setIsOpen(value => !value) }> - { LocalizeText('group.homeroominfo.title') } - { isOpen && } - { !isOpen && } + + + + { LocalizeText('group.homeroominfo.title') } + + { isOpen && } + { !isOpen && } + { isOpen && <> diff --git a/src/components/hc-center/HcCenterView.tsx b/src/components/hc-center/HcCenterView.tsx index 3a74ab0ba..6684edec2 100644 --- a/src/components/hc-center/HcCenterView.tsx +++ b/src/components/hc-center/HcCenterView.tsx @@ -113,7 +113,7 @@ export const HcCenterView: FC<{}> = props => if(!isVisible) return null; const popover = ( - +
{ LocalizeText('hccenter.breakdown.title') }
{ LocalizeText('hccenter.breakdown.creditsspent', [ 'credits' ], [ kickbackData?.totalCreditsSpent.toString() ]) }
@@ -121,7 +121,7 @@ export const HcCenterView: FC<{}> = props =>
{ 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/' + GetConfiguration('hc.center')['payday.habbopage']) }> +
CreateLinkEvent('habbopages/' + GetConfiguration('hc.center')['payday.habbopage']) }> { LocalizeText('hccenter.special.infolink') }
@@ -146,17 +146,17 @@ export const HcCenterView: FC<{}> = props => - - { LocalizeText('hccenter.status.' + clubStatus) } + + { LocalizeText('hccenter.status.' + clubStatus) } { GetConfiguration('hc.center')['payday.info'] && - + { LocalizeText('hccenter.special.title') } { LocalizeText('hccenter.special.info') } - CreateLinkEvent('habbopages/' + GetConfiguration('hc.center')['payday.habbopage']) }>{ LocalizeText('hccenter.special.infolink') } + CreateLinkEvent('habbopages/' + GetConfiguration('hc.center')['payday.habbopage']) }>{ LocalizeText('hccenter.special.infolink') } @@ -170,10 +170,12 @@ export const HcCenterView: FC<{}> = props => { LocalizeText('hccenter.special.amount.title') } { getHcPaydayAmount() } - - - { LocalizeText('hccenter.breakdown.infolink') } - + + popover } className="pt-4"> + + { LocalizeText('hccenter.breakdown.infolink') } + + } diff --git a/src/components/hotel-view/HotelView.scss b/src/components/hotel-view/HotelView.scss index f7a2dcd69..39d451b2e 100644 --- a/src/components/hotel-view/HotelView.scss +++ b/src/components/hotel-view/HotelView.scss @@ -2,7 +2,7 @@ display: block; position: fixed; width: 100%; - height: calc(100% - 55px); + height: calc(100% - 51px); background: rgba($black, 1); color:#fff; @@ -19,6 +19,7 @@ width: 100%; background-position: left; background-repeat: repeat-x; + background-size: contain; } .drape { diff --git a/src/components/hotel-view/views/widgets/hall-of-fame-item/HallOfFameItemView.tsx b/src/components/hotel-view/views/widgets/hall-of-fame-item/HallOfFameItemView.tsx index b4237f516..54e889fc6 100644 --- a/src/components/hotel-view/views/widgets/hall-of-fame-item/HallOfFameItemView.tsx +++ b/src/components/hotel-view/views/widgets/hall-of-fame-item/HallOfFameItemView.tsx @@ -1,25 +1,25 @@ import { HallOfFameEntryData } from '@nitrots/nitro-renderer'; import { FC } from 'react'; -import { LocalizeFormattedNumber, LocalizeText } from '../../../../../api'; -import { LayoutAvatarImageView, UserProfileIconView } from '../../../../../common'; +import { GetUserProfile, LocalizeFormattedNumber, LocalizeText } from '../../../../../api'; +import { Base, LayoutAvatarImageView } from '../../../../../common'; export interface HallOfFameItemViewProps { data: HallOfFameEntryData; - level: number; + goalCode: string; } export const HallOfFameItemView: FC = props => { - const { data = null, level = 0 } = props; + const { data = null, goalCode = null } = props; return (
-
-
- { level }. { data.userName } -
-
{ LocalizeText('landing.view.competition.hof.points', [ 'points' ], [ LocalizeFormattedNumber(data.currentScore).toString() ]) }
+
+ + GetUserProfile(data.userId) }>{ data.userName } +
{ LocalizeText('landing.view.competition.hof.points', [ 'points' ], [ LocalizeFormattedNumber(data.currentScore).toString() ]) } points
+
{ LocalizeText(`landing.view.competition.hof.${ goalCode }.rankdesc.leader`) }
diff --git a/src/components/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.scss b/src/components/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.scss index 29a5ef7f1..05b072b47 100644 --- a/src/components/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.scss +++ b/src/components/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.scss @@ -1,7 +1,4 @@ .hall-of-fame { - background-color: rgba($black,.3); - border-radius: $border-radius; - justify-content: center; .hof-user-container { display:inline-flex; @@ -17,10 +14,10 @@ .hof-tooltip { position: absolute; display: none; - width: 125px; + width: 136px; max-width: 125px; padding: 2px; - background-color: $gable-green; + background-color: $white; border: 2px solid rgba($white, 0.5); border-radius: $border-radius; font-size: $font-size-sm; @@ -29,20 +26,6 @@ left: -15px; bottom: calc(100% - 10px); - .hof-header { - display: flex; - align-items: center; - justify-content: center; - gap: 5px; - background-color: $william; - color: $white; - min-width: 117px; - height: 25px; - max-height: 25px; - font-size: 16px; - margin-bottom: 2px; - } - &:after { content: ''; position: absolute; @@ -53,7 +36,7 @@ height: 10px; width: 10px; transform: rotate(45deg); - border-color: transparent rgba($white, 0.5) rgba($white, 0.5) transparent; + border-color: $white; border-style: solid; border-width: 5px; } diff --git a/src/components/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.tsx b/src/components/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.tsx index 582ce0414..9dfb57e22 100644 --- a/src/components/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.tsx +++ b/src/components/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.tsx @@ -26,10 +26,10 @@ export const HallOfFameWidgetView: FC = props => if(!data) return null; return ( -
+
{ data.hof && (data.hof.length > 0) && data.hof.map((entry, index) => { - return ; + return ; } ) }
diff --git a/src/components/inventory/InventoryView.scss b/src/components/inventory/InventoryView.scss index a27cad0cf..5c8c73b09 100644 --- a/src/components/inventory/InventoryView.scss +++ b/src/components/inventory/InventoryView.scss @@ -1,7 +1,9 @@ .nitro-inventory { - width: $inventory-width; height: $inventory-height; - min-width: 326px; + min-height: $inventory-height; + width: $inventory-width; + min-width: $inventory-width; + max-width: $inventory-width; color: #000; input { @@ -11,10 +13,25 @@ &.trading { width: 535px; - min-height: 505px; + min-height: $inventory-height + 290px !important; + max-width: $inventory-width + 15px !important; + min-width: $inventory-width + 15px !important; .trading-inventory { - height: 200px; + max-height: 240px; + } + + .nitro-item-count { + top: 0px; + right: -2px; + background-color: white; + padding: 0px; + padding-left: 3px; + padding-right: 3px; + color: #306A83; + border: 1px solid #2F6982; + font-weight: normal !important; + font-family: Goldfish; } } @@ -32,8 +49,87 @@ } .trade-bg { - border-image-source: url(@/assets/flash/inventory/trading_bg.png); + border-image-source: url('@/assets/flash/inventory/trading_bg.png'); border-image-slice: 15 15 15 15 fill; border-image-width: 15px 15px 15px 15px; + z-index: 1; } + + .credits-align { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 230px; + } + + .quantity-input { + width: 49px; + } + + .lock-design-left { + margin-left: 21px; + margin-right: 33px; + } + + .lock-design-right { + margin-left: 21px; + } + + .divisor { + height: 180px; + border-left: 2px solid #B4B4B4; + margin-top: 15px; + } + + .inventory-items { + + border-image-source: url(@/assets/flash/inventory/item.png) !important; + border-image-slice: 6 6 6 6 fill !important; + border-image-width: 6px 6px 6px 6px !important; + + &.active { + border-image-source: url(@/assets/flash/inventory/selected_item.png) !important; + border-image-slice: 6 6 6 6 fill !important; + border-image-width: 6px 6px 6px 6px !important; + } + + &.unseen { + background-color: #9BCA64; + } + + .nitro-item-count { + top: 0px; + right: -2px; + background-color: white; + padding: 0px; + padding-left: 3px; + padding-right: 3px; + color: #306A83; + border: 1px solid #2F6982; + font-weight: normal !important; + font-family: Goldfish; + } + } +} + +.calc-wrapper { + width: 96%; + height: calc(100% - 100px); +} + +.size-list-badges { + width: 335px; +} + +.size-badges { + width: 145px; +} + +.nitro-inventory-category-filter { + background-color: #C9C9C9; + height: 25px; +} + +.text-shadow-around-text { + text-shadow: -1px 0 white, 0 1px white, 1px 0 #ececec, 0 -1px white; } diff --git a/src/components/inventory/InventoryView.tsx b/src/components/inventory/InventoryView.tsx index 0df5e4a30..28da493c7 100644 --- a/src/components/inventory/InventoryView.tsx +++ b/src/components/inventory/InventoryView.tsx @@ -1,29 +1,28 @@ import { BadgePointLimitsEvent, ILinkEventTracker, IRoomSession, RoomEngineObjectEvent, RoomEngineObjectPlacedEvent, RoomPreviewer, RoomSessionEvent } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; -import { AddEventLinkTracker, GetLocalization, GetRoomEngine, isObjectMoverRequested, LocalizeText, RemoveLinkEventTracker, setObjectMoverRequested, UnseenItemCategory } from '../../api'; +import { AddEventLinkTracker, GetLocalization, GetRoomEngine, GroupItem, LocalizeText, RemoveLinkEventTracker, isObjectMoverRequested, setObjectMoverRequested } from '../../api'; import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common'; -import { useInventoryTrade, useInventoryUnseenTracker, useMessageEvent, useRoomEngineEvent, useRoomSessionManagerEvent } from '../../hooks'; +import { useInventoryBadges, useInventoryFurni, useInventoryTrade, useInventoryUnseenTracker, useMessageEvent, useRoomEngineEvent, useRoomSessionManagerEvent } from '../../hooks'; +import { TABS, TAB_BADGES, TAB_BOTS, TAB_FURNITURE, TAB_PETS, UNSEEN_CATEGORIES } from './constants'; +import { InventoryCategoryFilterView } from './views/InventoryCategoryFilterView'; import { InventoryBadgeView } from './views/badge/InventoryBadgeView'; import { InventoryBotView } from './views/bot/InventoryBotView'; import { InventoryFurnitureView } from './views/furniture/InventoryFurnitureView'; import { InventoryTradeView } from './views/furniture/InventoryTradeView'; import { InventoryPetView } from './views/pet/InventoryPetView'; -const TAB_FURNITURE: string = 'inventory.furni'; -const TAB_BOTS: string = 'inventory.bots'; -const TAB_PETS: string = 'inventory.furni.tab.pets'; -const TAB_BADGES: string = 'inventory.badges'; -const TABS = [ TAB_FURNITURE, TAB_BOTS, TAB_PETS, TAB_BADGES ]; -const UNSEEN_CATEGORIES = [ UnseenItemCategory.FURNI, UnseenItemCategory.BOT, UnseenItemCategory.PET, UnseenItemCategory.BADGE ]; - export const InventoryView: FC<{}> = props => { const [ isVisible, setIsVisible ] = useState(false); const [ currentTab, setCurrentTab ] = useState(TABS[0]); const [ roomSession, setRoomSession ] = useState(null); const [ roomPreviewer, setRoomPreviewer ] = useState(null); + const [ filteredGroupItems, setFilteredGroupItems ] = useState([]); + const [ filteredBadgeCodes, setFilteredBadgeCodes ] = useState([]); const { isTrading = false, stopTrading = null } = useInventoryTrade(); - const { getCount = null, resetCategory = null } = useInventoryUnseenTracker(); + const { getCount = null } = useInventoryUnseenTracker(); + const { groupItems = [] } = useInventoryFurni(); + const { badgeCodes = [] } = useInventoryBadges(); const onClose = () => { @@ -73,7 +72,7 @@ export const InventoryView: FC<{}> = props => const parts = url.split('/'); if(parts.length < 2) return; - + switch(parts[1]) { case 'show': @@ -118,35 +117,32 @@ export const InventoryView: FC<{}> = props => if(!isVisible) return null; return ( - + - { !isTrading && - <> - - { TABS.map((name, index) => - { - return ( - setCurrentTab(name) } count={ getCount(UNSEEN_CATEGORIES[index]) }> - { LocalizeText(name) } - - ); - }) } - - - { (currentTab === TAB_FURNITURE ) && - } - { (currentTab === TAB_BOTS ) && - } - { (currentTab === TAB_PETS ) && - } - { (currentTab === TAB_BADGES ) && - } - - } - { isTrading && - - - } + <> + + { TABS.map((name, index) => + { + return ( + setCurrentTab(name) } count={ getCount(UNSEEN_CATEGORIES[index]) }> + { LocalizeText(name) } + + ); + }) } + + + { (currentTab !== TAB_PETS && currentTab !== TAB_BOTS) && } + { (currentTab === TAB_FURNITURE ) && + } + { (currentTab === TAB_PETS ) && + } + { (currentTab === TAB_BADGES ) && + } + { (currentTab === TAB_BOTS ) && + } + { isTrading && setCurrentTab(e) } cancelTrade={ onClose } /> } + + ); } diff --git a/src/components/inventory/constants/InventoryFilterType.ts b/src/components/inventory/constants/InventoryFilterType.ts new file mode 100644 index 000000000..7efdaffb3 --- /dev/null +++ b/src/components/inventory/constants/InventoryFilterType.ts @@ -0,0 +1,9 @@ +export class InventoryFilterType +{ + public static EVERYTHING: string = 'inventory.filter.option.everything'; + public static FLOOR: string = 'inventory.furni.tab.floor'; + public static WALL: string = 'inventory.furni.tab.wall'; + public static ANYWHERE: string = 'inventory.placement.option.anywhere'; + public static IN_ROOM: string = 'inventory.placement.option.inroom'; + public static IN_INVENTORY: string = 'inventory.placement.option.notinroom'; +} diff --git a/src/components/inventory/constants/InventoryTabConstants.ts b/src/components/inventory/constants/InventoryTabConstants.ts new file mode 100644 index 000000000..859209787 --- /dev/null +++ b/src/components/inventory/constants/InventoryTabConstants.ts @@ -0,0 +1,9 @@ +import { UnseenItemCategory } from '../../../api/inventory/UnseenItemCategory'; + +export const TAB_FURNITURE: string = 'inventory.furni'; +export const TAB_BOTS: string = 'inventory.bots'; +export const TAB_PETS: string = 'inventory.furni.tab.pets'; +export const TAB_BADGES: string = 'inventory.badges'; +export const TABS = [ TAB_FURNITURE, TAB_PETS, TAB_BADGES, TAB_BOTS ]; +export const UNSEEN_CATEGORIES = [ UnseenItemCategory.FURNI, UnseenItemCategory.PET, UnseenItemCategory.BADGE, UnseenItemCategory.BOT ]; +export const MAX_ITEMS_TO_TRADE: number = 9; diff --git a/src/components/inventory/constants/index.ts b/src/components/inventory/constants/index.ts new file mode 100644 index 000000000..22efe5bc3 --- /dev/null +++ b/src/components/inventory/constants/index.ts @@ -0,0 +1,2 @@ +export * from './InventoryFilterType'; +export * from './InventoryTabConstants'; diff --git a/src/components/inventory/views/InventoryCategoryEmptyView.tsx b/src/components/inventory/views/InventoryCategoryEmptyView.tsx index f250364e3..501a2a2e1 100644 --- a/src/components/inventory/views/InventoryCategoryEmptyView.tsx +++ b/src/components/inventory/views/InventoryCategoryEmptyView.tsx @@ -1,15 +1,17 @@ import { FC } from 'react'; -import { Column, Grid, GridProps, Text } from '../../../common'; +import { CreateLinkEvent, LocalizeText } from '../../../api'; +import { Button, Column, Flex, Grid, GridProps, Text } from '../../../common'; export interface InventoryCategoryEmptyViewProps extends GridProps { title: string; desc: string; + isTrading?: boolean; } export const InventoryCategoryEmptyView: FC = props => { - const { title = '', desc = '', children = null, ...rest } = props; + const { title = '', desc = '', isTrading = false, children = null, ...rest } = props; return ( @@ -17,8 +19,13 @@ export const InventoryCategoryEmptyView: FC = p
- { title } - { desc } + { title } + { desc } + { !isTrading && + + + + } { children } diff --git a/src/components/inventory/views/InventoryCategoryFilterView.tsx b/src/components/inventory/views/InventoryCategoryFilterView.tsx new file mode 100644 index 000000000..6f9552de9 --- /dev/null +++ b/src/components/inventory/views/InventoryCategoryFilterView.tsx @@ -0,0 +1,107 @@ +import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react'; +import { GroupItem, LocalizeBadgeName, LocalizeText } from '../../../api'; +import { Flex } from '../../../common'; +import { InventoryFilterType, TAB_BADGES, TAB_FURNITURE } from '../constants'; + +export interface InventoryCategoryFilterViewProps +{ + currentTab: string; + groupItems: GroupItem[]; + badgeCodes: string[]; + setGroupItems: Dispatch>; + setBadgeCodes: Dispatch>; +} + +export const InventoryCategoryFilterView: FC = props => +{ + const { currentTab = null, groupItems = [], badgeCodes = [], setGroupItems = null, setBadgeCodes = null } = props; + const [ filterType, setFilterType ] = useState(InventoryFilterType.EVERYTHING); + const [ filterPlace, setFilterPlace ] = useState(InventoryFilterType.IN_INVENTORY); + const [ searchValue, setSearchValue ] = useState(''); + + useEffect(() => + { + if (currentTab !== TAB_BADGES) return; + + let filteredBadgeCodes = [ ...badgeCodes ]; + + const filteredBadges = badgeCodes.filter( badge => badge.startsWith('ACH_') ); + + const numberMap = {}; + + filteredBadges.forEach(badge => + { + const name = badge.split(/[\d]+/)[0]; + const number = Number(badge.replace(name, '')); + + if (numberMap[name] === undefined || number > numberMap[name]) + { + numberMap[name] = number; + } + }); + + const allBadges = Object.keys(numberMap).map( name => `${ name }${ numberMap[name] }` ).concat( badgeCodes.filter( badge => !badge.startsWith('ACH_') ) ); + + filteredBadgeCodes = allBadges.filter(badgeCode => + { + return LocalizeBadgeName(badgeCode).toLocaleLowerCase().includes(searchValue?.toLocaleLowerCase().replace(' ', '')); + }); + + setBadgeCodes(filteredBadgeCodes); + + }, [ badgeCodes, currentTab, searchValue, setBadgeCodes ]); + + useEffect(() => + { + if (currentTab !== TAB_FURNITURE) return; + + let filteredGroupItems = [ ...groupItems ]; + + const comparison = searchValue.toLocaleLowerCase(); + + if (filterType === InventoryFilterType.EVERYTHING) return setGroupItems(groupItems.filter( item => item.name.toLocaleLowerCase().includes(comparison) )); + + filteredGroupItems = groupItems.filter(item => + { + const isWallFilter = (filterType === InventoryFilterType.WALL) ? item.isWallItem : false; + const isFloorFilter = (filterType === InventoryFilterType.FLOOR) ? !item.isWallItem : false; + const isSearchFilter = (item.name.toLocaleLowerCase().includes(comparison)) ? true : false; + + return comparison && comparison.length ? (isSearchFilter && (isWallFilter || isFloorFilter)) : isWallFilter || isFloorFilter; + }); + + setGroupItems(filteredGroupItems); + }, [ groupItems, setGroupItems, searchValue, filterType, currentTab ]); + + useEffect(() => + { + setFilterType(InventoryFilterType.EVERYTHING); + setFilterPlace(InventoryFilterType.IN_INVENTORY); + setSearchValue(''); + }, [ currentTab ]); + + return ( + + + + setSearchValue(event.target.value) } /> + + { (searchValue && !!searchValue.length) && setSearchValue('') } /> } + + { (currentTab !== TAB_BADGES) && + <> + + + + + + + + } + + ); +} diff --git a/src/components/inventory/views/badge/InventoryBadgeItemView.tsx b/src/components/inventory/views/badge/InventoryBadgeItemView.tsx index bb6c54aff..198c13d5c 100644 --- a/src/components/inventory/views/badge/InventoryBadgeItemView.tsx +++ b/src/components/inventory/views/badge/InventoryBadgeItemView.tsx @@ -11,7 +11,7 @@ export const InventoryBadgeItemView: FC const unseen = isUnseen(UnseenItemCategory.BADGE, getBadgeId(badgeCode)); return ( - setSelectedBadgeCode(badgeCode) } onDoubleClick={ event => toggleBadge(selectedBadgeCode) } { ...rest }> + setSelectedBadgeCode(badgeCode) } onDoubleClick={ event => toggleBadge(selectedBadgeCode) } { ...rest }> { children } diff --git a/src/components/inventory/views/badge/InventoryBadgeView.tsx b/src/components/inventory/views/badge/InventoryBadgeView.tsx index 96da219b4..0ffc88593 100644 --- a/src/components/inventory/views/badge/InventoryBadgeView.tsx +++ b/src/components/inventory/views/badge/InventoryBadgeView.tsx @@ -1,14 +1,21 @@ import { FC, useEffect, useState } from 'react'; import { LocalizeBadgeName, LocalizeText, UnseenItemCategory } from '../../../../api'; import { AutoGrid, Button, Column, Flex, Grid, LayoutBadgeImageView, Text } from '../../../../common'; -import { useInventoryBadges, useInventoryUnseenTracker } from '../../../../hooks'; +import { useAchievements, useInventoryBadges, useInventoryUnseenTracker } from '../../../../hooks'; import { InventoryBadgeItemView } from './InventoryBadgeItemView'; -export const InventoryBadgeView: FC<{}> = props => +interface InventoryBadgeViewProps { + filteredBadgeCodes: string[]; +} + +export const InventoryBadgeView: FC = props => +{ + const { filteredBadgeCodes = [] } = props; const [ isVisible, setIsVisible ] = useState(false); - const { badgeCodes = [], activeBadgeCodes = [], selectedBadgeCode = null, isWearingBadge = null, canWearBadges = null, toggleBadge = null, getBadgeId = null, activate = null, deactivate = null } = useInventoryBadges(); + const { activeBadgeCodes = [], selectedBadgeCode = null, isWearingBadge = null, canWearBadges = null, toggleBadge = null, getBadgeId = null, activate = null, deactivate = null } = useInventoryBadges(); const { isUnseen = null, removeUnseen = null } = useInventoryUnseenTracker(); + const { achievementScore = 0 } = useAchievements(); useEffect(() => { @@ -34,33 +41,41 @@ export const InventoryBadgeView: FC<{}> = props => }, []); return ( - - - - { badgeCodes && (badgeCodes.length > 0) && badgeCodes.map((badgeCode, index) => - { - if(isWearingBadge(badgeCode)) return null; +
+ + + + { filteredBadgeCodes && filteredBadgeCodes.length > 0 && filteredBadgeCodes.map((badgeCode, index) => + { + if (isWearingBadge(badgeCode)) return null; - return - }) } - - - - - { LocalizeText('inventory.badges.activebadges') } - - { activeBadgeCodes && (activeBadgeCodes.length > 0) && activeBadgeCodes.map((badgeCode, index) => ) } + return ; + }) } + + + { LocalizeText('inventory.badges.activebadges') } + + { activeBadgeCodes && (activeBadgeCodes.length > 0) && activeBadgeCodes.map((badgeCode, index) => ) } + + + + + { !!selectedBadgeCode && - - + + - { LocalizeBadgeName(selectedBadgeCode) } + { LocalizeBadgeName(selectedBadgeCode) } - - } + + + } +
+ { LocalizeText('achievements.categories.score', [ 'score' ], [ achievementScore.toString() ]) } +
- +
); } diff --git a/src/components/inventory/views/bot/InventoryBotView.tsx b/src/components/inventory/views/bot/InventoryBotView.tsx index 8ea90d32e..861dbcf64 100644 --- a/src/components/inventory/views/bot/InventoryBotView.tsx +++ b/src/components/inventory/views/bot/InventoryBotView.tsx @@ -1,6 +1,6 @@ import { IRoomSession, RoomObjectVariable, RoomPreviewer } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; -import { attemptBotPlacement, GetRoomEngine, LocalizeText, UnseenItemCategory } from '../../../../api'; +import { GetRoomEngine, LocalizeText, UnseenItemCategory, attemptBotPlacement } from '../../../../api'; import { AutoGrid, Button, Column, Grid, LayoutRoomPreviewerView, Text } from '../../../../common'; import { useInventoryBots, useInventoryUnseenTracker } from '../../../../hooks'; import { InventoryCategoryEmptyView } from '../InventoryCategoryEmptyView'; @@ -10,11 +10,12 @@ interface InventoryBotViewProps { roomSession: IRoomSession; roomPreviewer: RoomPreviewer; + isTrading: boolean; } export const InventoryBotView: FC = props => { - const { roomSession = null, roomPreviewer = null } = props; + const { roomSession = null, roomPreviewer = null, isTrading = false } = props; const [ isVisible, setIsVisible ] = useState(false); const { botItems = [], selectedBot = null, activate = null, deactivate = null } = useInventoryBots(); const { isUnseen = null, removeUnseen = null } = useInventoryUnseenTracker(); @@ -64,7 +65,7 @@ export const InventoryBotView: FC = props => return () => setIsVisible(false); }, []); - if(!botItems || !botItems.length) return ; + if(!botItems || !botItems.length) return ; return ( diff --git a/src/components/inventory/views/furniture/InventoryFurnitureItemView.tsx b/src/components/inventory/views/furniture/InventoryFurnitureItemView.tsx index f5e848147..5106e7518 100644 --- a/src/components/inventory/views/furniture/InventoryFurnitureItemView.tsx +++ b/src/components/inventory/views/furniture/InventoryFurnitureItemView.tsx @@ -1,12 +1,12 @@ import { MouseEventType } from '@nitrots/nitro-renderer'; import { FC, MouseEvent, useState } from 'react'; -import { attemptItemPlacement, GroupItem } from '../../../../api'; +import { GroupItem, attemptItemPlacement } from '../../../../api'; import { LayoutGridItem } from '../../../../common'; import { useInventoryFurni } from '../../../../hooks'; -export const InventoryFurnitureItemView: FC<{ groupItem: GroupItem }> = props => +export const InventoryFurnitureItemView: FC<{ groupItem: GroupItem, isTrading: boolean, attemptItemOffer: (count: number) => void, setGroupItem: (item: GroupItem) => void }> = props => { - const { groupItem = null, ...rest } = props; + const { groupItem = null, isTrading = null, attemptItemOffer = null, setGroupItem = null, ...rest } = props; const [ isMouseDown, setMouseDown ] = useState(false); const { selectedItem = null, setSelectedItem = null } = useInventoryFurni(); @@ -24,15 +24,16 @@ export const InventoryFurnitureItemView: FC<{ groupItem: GroupItem }> = props => case MouseEventType.ROLL_OUT: if(!isMouseDown || !(groupItem === selectedItem)) return; - attemptItemPlacement(groupItem); + if (!isTrading) attemptItemPlacement(groupItem); return; case 'dblclick': - attemptItemPlacement(groupItem); + if (!isTrading) attemptItemPlacement(groupItem); + if (isTrading) (setGroupItem(groupItem), attemptItemOffer(1)) return; } } const count = groupItem.getUnlockedCount(); - return ; + return (count && setGroupItem(groupItem)) } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent } onDoubleClick={ onMouseEvent } { ...rest } />; } diff --git a/src/components/inventory/views/furniture/InventoryFurnitureView.tsx b/src/components/inventory/views/furniture/InventoryFurnitureView.tsx index c563eecff..ad1ccc216 100644 --- a/src/components/inventory/views/furniture/InventoryFurnitureView.tsx +++ b/src/components/inventory/views/furniture/InventoryFurnitureView.tsx @@ -1,17 +1,19 @@ -import { IRoomSession, RoomObjectVariable, RoomPreviewer, Vector3d } from '@nitrots/nitro-renderer'; +import { IObjectData, IRoomSession, RoomObjectVariable, RoomPreviewer, TradingListAddItemComposer, TradingListAddItemsComposer, Vector3d } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; -import { attemptItemPlacement, DispatchUiEvent, FurniCategory, GetRoomEngine, GetSessionDataManager, GroupItem, LocalizeText, UnseenItemCategory } from '../../../../api'; -import { AutoGrid, Button, Column, Grid, LayoutLimitedEditionCompactPlateView, LayoutRarityLevelView, LayoutRoomPreviewerView, Text } from '../../../../common'; +import { DispatchUiEvent, FurniCategory, GetRoomEngine, GetSessionDataManager, GroupItem, IFurnitureItem, LocalizeText, NotificationAlertType, SendMessageComposer, UnseenItemCategory, attemptItemPlacement, getGuildFurniType } from '../../../../api'; +import { AutoGrid, Base, Button, Column, Flex, Grid, LayoutLimitedEditionCompactPlateView, LayoutRarityLevelView, LayoutRoomPreviewerView, Text } from '../../../../common'; import { CatalogPostMarketplaceOfferEvent } from '../../../../events'; -import { useInventoryFurni, useInventoryUnseenTracker } from '../../../../hooks'; +import { useInventoryFurni, useInventoryTrade, useInventoryUnseenTracker, useNotification } from '../../../../hooks'; +import { MAX_ITEMS_TO_TRADE } from '../../constants'; import { InventoryCategoryEmptyView } from '../InventoryCategoryEmptyView'; import { InventoryFurnitureItemView } from './InventoryFurnitureItemView'; -import { InventoryFurnitureSearchView } from './InventoryFurnitureSearchView'; interface InventoryFurnitureViewProps { roomSession: IRoomSession; roomPreviewer: RoomPreviewer; + isTrading: boolean; + filteredGroupItems: GroupItem[]; } const attemptPlaceMarketplaceOffer = (groupItem: GroupItem) => @@ -27,11 +29,120 @@ const attemptPlaceMarketplaceOffer = (groupItem: GroupItem) => export const InventoryFurnitureView: FC = props => { - const { roomSession = null, roomPreviewer = null } = props; + const { roomSession = null, roomPreviewer = null, isTrading = null, filteredGroupItems = [] } = props; const [ isVisible, setIsVisible ] = useState(false); - const [ filteredGroupItems, setFilteredGroupItems ] = useState([]); + const [ groupItem, setGroupItem ] = useState(null); + const [ quantity, setQuantity ] = useState(1); const { groupItems = [], selectedItem = null, activate = null, deactivate = null } = useInventoryFurni(); + const { ownUser = null } = useInventoryTrade(); const { resetItems = null } = useInventoryUnseenTracker(); + const { simpleAlert = null } = useNotification(); + + const updateQuantity = (value: number, totalItemCount: number) => + { + if(isNaN(Number(value)) || Number(value) < 0 || !value) value = 1; + + value = Math.max(Number(value), 1); + value = Math.min(Number(value), totalItemCount); + + if(value === quantity) return; + + setQuantity(value); + } + + const changeCount = (totalItemCount: number) => + { + updateQuantity(quantity, totalItemCount); + attemptItemOffer(quantity); + } + + const canTradeItem = (isWallItem: boolean, spriteId: number, category: number, groupable: boolean, stuffData: IObjectData) => + { + if(!ownUser || ownUser.accepts || !ownUser.userItems) return false; + + if(ownUser.userItems.length < MAX_ITEMS_TO_TRADE) return true; + + if(!groupable) return false; + + let type = spriteId.toString(); + + if(category === FurniCategory.POSTER) + { + type = ((type + 'poster') + stuffData.getLegacyString()); + } + else + { + if(category === FurniCategory.GUILD_FURNI) + { + type = getGuildFurniType(spriteId, stuffData); + } + else + { + type = (((isWallItem) ? 'I' : 'S') + type); + } + } + + return !!ownUser.userItems.getValue(type); + } + + const attemptItemOffer = (count: number) => + { + if(!groupItem) return; + + const tradeItems = groupItem.getTradeItems(count); + + if(!tradeItems || !tradeItems.length) return; + + let coreItem: IFurnitureItem = null; + const itemIds: number[] = []; + + for(const item of tradeItems) + { + itemIds.push(item.id); + + if(!coreItem) coreItem = item; + } + + const ownItemCount = ownUser.userItems.length; + + if((ownItemCount + itemIds.length) <= 1500) + { + if(!coreItem.isGroupable && (itemIds.length)) + { + SendMessageComposer(new TradingListAddItemComposer(itemIds.pop())); + } + else + { + const tradeIds: number[] = []; + + for(const itemId of itemIds) + { + if(canTradeItem(coreItem.isWallItem, coreItem.type, coreItem.category, coreItem.isGroupable, coreItem.stuffData)) + { + tradeIds.push(itemId); + } + } + + if(tradeIds.length) + { + if(tradeIds.length === 1) + { + SendMessageComposer(new TradingListAddItemComposer(tradeIds.pop())); + } + else + { + SendMessageComposer(new TradingListAddItemsComposer(...tradeIds)); + } + } + } + } + else + { + simpleAlert(LocalizeText('trading.items.too_many_items.desc'), NotificationAlertType.DEFAULT, null, null, LocalizeText('trading.items.too_many_items.title')); + } + + setGroupItem(selectedItem); + } useEffect(() => { @@ -81,6 +192,8 @@ export const InventoryFurnitureView: FC = props => roomPreviewer.addFurnitureIntoRoom(selectedItem.type, new Vector3d(90), selectedItem.stuffData, (furnitureItem.extra.toString())); } } + + setGroupItem(selectedItem); }, [ roomPreviewer, selectedItem ]); useEffect(() => @@ -108,19 +221,38 @@ export const InventoryFurnitureView: FC = props => return () => setIsVisible(false); }, []); - if(!groupItems || !groupItems.length) return ; + useEffect(() => + { + setQuantity(1); + }, [ filteredGroupItems ]); + + if(!groupItems || !groupItems.length) return ; + + const totalItems = !isTrading ? selectedItem.items.length : selectedItem.getUnlockedCount(); + return ( - - - - - { filteredGroupItems && (filteredGroupItems.length > 0) && filteredGroupItems.map((item, index) => ) } + + + + { filteredGroupItems && (filteredGroupItems.length > 0) && filteredGroupItems.map((item, index) => attemptItemOffer(e) } setGroupItem={ (e) => setGroupItem(e) } />) } - + + { (selectedItem && (selectedItem.items[0].isTradable || !selectedItem.items[0].isTradable)) && + + 0) ? 'icon-tradeable' : 'icon-not-tradeable' }` } title={ LocalizeText((selectedItem.items[0].isTradable && totalItems > 0) ? 'inventory.furni.preview.tradeable_amount' : 'inventory.furni.preview.not_tradeable') } /> + { (selectedItem.items[0].isTradable && totalItems > 0) && { totalItems } } + + } + { (selectedItem && (selectedItem.items[0].recyclable || !selectedItem.items[0].recyclable)) && + + 0) ? 'icon-recyclable' : 'icon-not-recyclable' }` } title={ LocalizeText((selectedItem.items[0].recyclable && totalItems > 0) ? 'inventory.furni.preview.recyclable_amount' : 'inventory.furni.preview.not_recyclable') } /> + { (selectedItem.items[0].recyclable && totalItems > 0) && { totalItems } } + + } { selectedItem && selectedItem.stuffData.isUnique && } { (selectedItem && selectedItem.stuffData.rarityLevel > -1) && @@ -128,16 +260,27 @@ export const InventoryFurnitureView: FC = props => { selectedItem && - { selectedItem.name } - - { !!roomSession && - } - { (selectedItem && selectedItem.isSellable) && - } + + { selectedItem.name } + { (selectedItem.description) && { selectedItem.description } } + { (!isTrading) && + <> + { !!roomSession && + } + { (selectedItem && selectedItem.isSellable) && + } + + } + { (isTrading) && + + setQuantity(event.target.valueAsNumber) } /> + + + } } diff --git a/src/components/inventory/views/furniture/InventoryTradeView.tsx b/src/components/inventory/views/furniture/InventoryTradeView.tsx index 27382cfdd..c5df70dd5 100644 --- a/src/components/inventory/views/furniture/InventoryTradeView.tsx +++ b/src/components/inventory/views/furniture/InventoryTradeView.tsx @@ -1,151 +1,42 @@ -import { IObjectData, TradingListAddItemComposer, TradingListAddItemsComposer } from '@nitrots/nitro-renderer'; +import { AdvancedMap } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; -import { FaChevronLeft, FaChevronRight, FaLock, FaUnlock } from 'react-icons/fa'; -import { FurniCategory, getGuildFurniType, GroupItem, IFurnitureItem, LocalizeText, NotificationAlertType, SendMessageComposer, TradeState } from '../../../../api'; +import { GroupItem, LocalizeText, TradeState } from '../../../../api'; import { AutoGrid, Base, Button, Column, Flex, Grid, LayoutGridItem, Text } from '../../../../common'; -import { useInventoryTrade, useNotification } from '../../../../hooks'; -import { InventoryFurnitureSearchView } from './InventoryFurnitureSearchView'; +import { useInventoryTrade } from '../../../../hooks'; +import { MAX_ITEMS_TO_TRADE, TABS, TAB_FURNITURE } from '../../constants'; interface InventoryTradeViewProps { + currentTab: string; + setCurrentTab: (value: string) => void; cancelTrade: () => void; } -const MAX_ITEMS_TO_TRADE: number = 9; - export const InventoryTradeView: FC = props => { - const { cancelTrade = null } = props; - const [ groupItem, setGroupItem ] = useState(null); + const { currentTab = null, setCurrentTab = null, cancelTrade = null } = props; const [ ownGroupItem, setOwnGroupItem ] = useState(null); const [ otherGroupItem, setOtherGroupItem ] = useState(null); - const [ filteredGroupItems, setFilteredGroupItems ] = useState(null); const [ countdownTick, setCountdownTick ] = useState(3); - const [ quantity, setQuantity ] = useState(1); - const { ownUser = null, otherUser = null, groupItems = [], tradeState = TradeState.TRADING_STATE_READY, progressTrade = null, removeItem = null, setTradeState = null } = useInventoryTrade(); - const { simpleAlert = null } = useNotification(); - - const canTradeItem = (isWallItem: boolean, spriteId: number, category: number, groupable: boolean, stuffData: IObjectData) => - { - if(!ownUser || ownUser.accepts || !ownUser.userItems) return false; - - if(ownUser.userItems.length < MAX_ITEMS_TO_TRADE) return true; - - if(!groupable) return false; - - let type = spriteId.toString(); - - if(category === FurniCategory.POSTER) - { - type = ((type + 'poster') + stuffData.getLegacyString()); - } - else - { - if(category === FurniCategory.GUILD_FURNI) - { - type = getGuildFurniType(spriteId, stuffData); - } - else - { - type = (((isWallItem) ? 'I' : 'S') + type); - } - } - - return !!ownUser.userItems.getValue(type); - } - - const attemptItemOffer = (count: number) => - { - if(!groupItem) return; - - const tradeItems = groupItem.getTradeItems(count); - - if(!tradeItems || !tradeItems.length) return; - - let coreItem: IFurnitureItem = null; - const itemIds: number[] = []; - - for(const item of tradeItems) - { - itemIds.push(item.id); - - if(!coreItem) coreItem = item; - } - - const ownItemCount = ownUser.userItems.length; - - if((ownItemCount + itemIds.length) <= 1500) - { - if(!coreItem.isGroupable && (itemIds.length)) - { - SendMessageComposer(new TradingListAddItemComposer(itemIds.pop())); - } - else - { - const tradeIds: number[] = []; - - for(const itemId of itemIds) - { - if(canTradeItem(coreItem.isWallItem, coreItem.type, coreItem.category, coreItem.isGroupable, coreItem.stuffData)) - { - tradeIds.push(itemId); - } - } - - if(tradeIds.length) - { - if(tradeIds.length === 1) - { - SendMessageComposer(new TradingListAddItemComposer(tradeIds.pop())); - } - else - { - SendMessageComposer(new TradingListAddItemsComposer(...tradeIds)); - } - } - } - } - else - { - simpleAlert(LocalizeText('trading.items.too_many_items.desc'), NotificationAlertType.DEFAULT, null, null, LocalizeText('trading.items.too_many_items.title')); - } - } + const { ownUser = null, otherUser = null, tradeState = TradeState.TRADING_STATE_READY, progressTrade = null, removeItem = null, setTradeState = null } = useInventoryTrade(); const getLockIcon = (accepts: boolean) => { if(accepts) { - return + return } else { - return + return } } - const updateQuantity = (value: number, totalItemCount: number) => - { - if(isNaN(Number(value)) || Number(value) < 0 || !value) value = 1; - - value = Math.max(Number(value), 1); - value = Math.min(Number(value), totalItemCount); - - if(value === quantity) return; - - setQuantity(value); - } - - const changeCount = (totalItemCount: number) => + const getTotalCredits = (items: AdvancedMap): number => { - updateQuantity(quantity, totalItemCount); - attemptItemOffer(quantity); + return items.getValues().map( item => Number(item.iconUrl.split('/')[item.iconUrl.split('/').length - 1]?.split('_')[1]) * item.items.length ).reduce((acc, cur) => acc + (isNaN(cur) ? 0 : cur), 0); } - useEffect(() => - { - setQuantity(1); - }, [ groupItem ]); - useEffect(() => { if(tradeState !== TradeState.TRADING_STATE_COUNTDOWN) return; @@ -178,102 +69,93 @@ export const InventoryTradeView: FC = props => return ( - - - - - - { filteredGroupItems && (filteredGroupItems.length > 0) && filteredGroupItems.map((item, index) => - { - const count = item.getUnlockedCount(); - - return ( - (count && setGroupItem(item)) } onDoubleClick={ event => attemptItemOffer(1) }> - { ((count > 0) && (groupItem === item)) && - - } - - ); - }) } - - - - - setQuantity(event.target.valueAsNumber) } /> + + { currentTab === TAB_FURNITURE && + <> + { LocalizeText('inventory.trading.info.add') } + + + + { (ownUser.accepts) && } + { LocalizeText('inventory.trading.you') } { LocalizeText('inventory.trading.areoffering') } + + + { Array.from(Array(MAX_ITEMS_TO_TRADE), (e, i) => + { + const item = (ownUser.userItems.getWithIndex(i) || null); + + if(!item) return ; + + return ( + setOwnGroupItem(item) } onDoubleClick={ event => removeItem(item) }> + { (ownGroupItem === item) && + removeItem(item) } /> } + + ); + }) } + + + { LocalizeText('inventory.trading.info.itemcount', [ 'value' ], [ ownUser.itemCount.toString() ]) } + { LocalizeText('inventory.trading.info.creditvalue.own', [ 'value' ], [ getTotalCredits(ownUser.userItems).toString() ]) } + - - + + { getLockIcon(ownUser.accepts) } + + + + + { (otherUser.accepts) && } + { otherUser.userName } { LocalizeText('inventory.trading.isoffering') } + + + { Array.from(Array(MAX_ITEMS_TO_TRADE), (e, i) => + { + const item = (otherUser.userItems.getWithIndex(i) || null); + + if(!item) return ; + + return setOtherGroupItem(item) } />; + }) } + + + { LocalizeText('inventory.trading.info.itemcount', [ 'value' ], [ otherUser.itemCount.toString() ]) } + { LocalizeText('inventory.trading.info.creditvalue', [ 'value' ], [ getTotalCredits(otherUser.userItems).toString() ]) } + + + { getLockIcon(otherUser.accepts) } + - - { groupItem ? groupItem.name : LocalizeText('catalog_selectproduct') } - - - - - - - - - - { LocalizeText('inventory.trading.you') } { LocalizeText('inventory.trading.areoffering') }: - - - { Array.from(Array(MAX_ITEMS_TO_TRADE), (e, i) => - { - const item = (ownUser.userItems.getWithIndex(i) || null); - - if(!item) return ; - - return ( - setOwnGroupItem(item) } onDoubleClick={ event => removeItem(item) }> - { (ownGroupItem === item) && - } - - ); - }) } - - - { ownGroupItem ? ownGroupItem.name : LocalizeText('catalog_selectproduct') } - - - { getLockIcon(ownUser.accepts) } - - - { otherUser.userName } { LocalizeText('inventory.trading.isoffering') }: + + } + { currentTab !== TAB_FURNITURE && + <> + + + { LocalizeText('inventory.trading.minimized.trade_in_progress') } - - { Array.from(Array(MAX_ITEMS_TO_TRADE), (e, i) => - { - const item = (otherUser.userItems.getWithIndex(i) || null); - - if(!item) return ; - - return setOtherGroupItem(item) } />; - }) } - - - { otherGroupItem ? otherGroupItem.name : LocalizeText('catalog_selectproduct') } - - - { getLockIcon(otherUser.accepts) } - + + } + { (currentTab === TAB_FURNITURE) && + <> + { (tradeState === TradeState.TRADING_STATE_READY) && + } + { (tradeState === TradeState.TRADING_STATE_RUNNING) && + } + { (tradeState === TradeState.TRADING_STATE_COUNTDOWN) && + } + { (tradeState === TradeState.TRADING_STATE_CONFIRMING) && + } + { (tradeState === TradeState.TRADING_STATE_CONFIRMED) && + } + + } + { (currentTab !== TAB_FURNITURE) && + + } - { (tradeState === TradeState.TRADING_STATE_READY) && - } - { (tradeState === TradeState.TRADING_STATE_RUNNING) && - } - { (tradeState === TradeState.TRADING_STATE_COUNTDOWN) && - } - { (tradeState === TradeState.TRADING_STATE_CONFIRMING) && - } - { (tradeState === TradeState.TRADING_STATE_CONFIRMED) && - } diff --git a/src/components/inventory/views/pet/InventoryPetView.tsx b/src/components/inventory/views/pet/InventoryPetView.tsx index 0855c98bf..138d41cc5 100644 --- a/src/components/inventory/views/pet/InventoryPetView.tsx +++ b/src/components/inventory/views/pet/InventoryPetView.tsx @@ -1,6 +1,6 @@ import { IRoomSession, RoomObjectVariable, RoomPreviewer } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; -import { attemptPetPlacement, GetRoomEngine, LocalizeText, UnseenItemCategory } from '../../../../api'; +import { GetRoomEngine, LocalizeText, UnseenItemCategory, attemptPetPlacement } from '../../../../api'; import { AutoGrid, Button, Column, Grid, LayoutRoomPreviewerView, Text } from '../../../../common'; import { useInventoryPets, useInventoryUnseenTracker } from '../../../../hooks'; import { InventoryCategoryEmptyView } from '../InventoryCategoryEmptyView'; @@ -10,11 +10,12 @@ interface InventoryPetViewProps { roomSession: IRoomSession; roomPreviewer: RoomPreviewer; + isTrading: boolean; } export const InventoryPetView: FC = props => { - const { roomSession = null, roomPreviewer = null } = props; + const { roomSession = null, roomPreviewer = null, isTrading = false } = props; const [ isVisible, setIsVisible ] = useState(false); const { petItems = null, selectedPet = null, activate = null, deactivate = null } = useInventoryPets(); const { isUnseen = null, removeUnseen = null } = useInventoryUnseenTracker(); @@ -63,7 +64,7 @@ export const InventoryPetView: FC = props => return () => setIsVisible(false); }, []); - if(!petItems || !petItems.length) return ; + if(!petItems || !petItems.length) return ; return ( diff --git a/src/components/mod-tools/ModToolsView.tsx b/src/components/mod-tools/ModToolsView.tsx index 3304e6cf5..2272a59ba 100644 --- a/src/components/mod-tools/ModToolsView.tsx +++ b/src/components/mod-tools/ModToolsView.tsx @@ -1,7 +1,7 @@ import { ILinkEventTracker, RoomEngineEvent, RoomId, RoomObjectCategory, RoomObjectType } from '@nitrots/nitro-renderer'; import { FC, useEffect, useRef, useState } from 'react'; import { AddEventLinkTracker, CreateLinkEvent, GetRoomSession, ISelectedUser, RemoveLinkEventTracker } from '../../api'; -import { Base, Button, DraggableWindowPosition, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../common'; +import { Base, Button, DraggableWindowPosition, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common'; import { useModTools, useObjectSelectedEvent, useRoomEngineEvent } from '../../hooks'; import { ModToolsChatlogView } from './views/room/ModToolsChatlogView'; import { ModToolsRoomView } from './views/room/ModToolsRoomView'; @@ -120,20 +120,24 @@ export const ModToolsView: FC<{}> = props => return ( <> { isVisible && - - setIsVisible(false) } /> + + setIsVisible(false) } /> - - - - } diff --git a/src/components/navigator/NavigatorView.scss b/src/components/navigator/NavigatorView.scss index 5ab079ad9..0e862146d 100644 --- a/src/components/navigator/NavigatorView.scss +++ b/src/components/navigator/NavigatorView.scss @@ -4,6 +4,11 @@ max-width: $navigator-width; min-height: $navigator-min-height; + &.expanded { + max-width: $navigator-width + 153px; + min-width: $navigator-width + 153px; + } + .nav-form { width: 275px; } @@ -47,6 +52,20 @@ .nitro-room-info { width: $room-info-width; + resize: none; +} + +.text-tag { + font-size: 10px; + background-color: #CEE9FA; +} + +.text-tag:hover { + background-color: #449FCF; +} + +.text-embed { + font-size: 10px; } .nitro-room-link { @@ -67,7 +86,7 @@ .badge-empty { - background-image: url(@/assets/flash/navigator/badge_empty.png); + background-image: url('@/assets/flash/navigator/badge_empty.png'); background-repeat: no-repeat; width: 40px; height: 19px; @@ -75,7 +94,7 @@ } .badge-success { - background-image: url(@/assets/flash/navigator/badge_success.png); + background-image: url('@/assets/flash/navigator/badge_success.png'); background-repeat: no-repeat; width: 40px; height: 19px; @@ -83,7 +102,7 @@ } .badge-danger { - background-image: url(@/assets/flash/navigator/badge_danger.png); + background-image: url('@/assets/flash/navigator/badge_danger.png'); background-repeat: no-repeat; width: 40px; height: 19px; @@ -91,7 +110,7 @@ } .badge-warning { - background-image: url(@/assets/flash/navigator/badge_warning.png); + background-image: url('@/assets/flash/navigator/badge_warning.png'); background-repeat: no-repeat; width: 40px; height: 19px; @@ -109,34 +128,34 @@ } .minus-icon { - background-image: url(@/assets/flash/navigator/minus.png); + background-image: url('@/assets/flash/navigator/minus.png'); background-repeat: no-repeat; width: 18px; height: 18px; } .plus-icon { - background-image: url(@/assets/flash/navigator/plus.png); + background-image: url('@/assets/flash/navigator/plus.png'); background-repeat: no-repeat; width: 18px; height: 18px; } .nav-avatar-icon { - background-image: url(@/assets/flash/navigator/avatar_icon.png); + background-image: url('@/assets/flash/navigator/avatar_icon.png'); background-repeat: no-repeat; height: 8px; width: 7px; } .nav-thumbnail { - border-image-source: url(@/assets/flash/navigator/thumbnail_bg.png); + border-image-source: url('@/assets/flash/navigator/thumbnail_bg.png'); border-image-slice: 7 7 7 7 fill; border-image-width: 7px 7px 7px 7px; } .nav-create-room { - background-image: url(@/assets/flash/navigator/create_room.png); + background-image: url('@/assets/flash/navigator/create_room.png'); background-repeat: no-repeat; height: 60px; width: 189px; @@ -145,14 +164,23 @@ } .nav-random-room { - background-image: url(@/assets/flash/navigator/random_room.png); + background-image: url('@/assets/flash/navigator/random_room.png'); background-repeat: no-repeat; height: 60px; width: 189px; float: right; margin-left: 15px; cursor: pointer; +} +.nav-promote-room { + background-image: url('@/assets/flash/navigator/promote_room.png'); + background-repeat: no-repeat; + height: 60px; + width: 189px; + float: right; + margin-left: 15px; + cursor: pointer; } .nav-bottom { @@ -222,7 +250,7 @@ image-rendering: pixelated; .room-info-bg { - border-image-source: url(@/assets/flash/navigator/white_bg.png); + border-image-source: url('@/assets/flash/navigator/white_bg.png'); border-image-slice: 6 6 6 6 fill; border-image-width: 6px 6px 6px 6px; } @@ -233,3 +261,31 @@ right: 10px; top: 11px; } + +.nitro-navigator-search-saves-result { + background-color: #fff; + width: 141px; + min-height: 494px; + max-height: 350px; + border-radius: 10px; + + .bg-orange { + background-color: #FAA700; + } +} + +.nitro-navigator-search-delete { + cursor: pointer; + background-image: url("@/assets/flash/navigator/saves-search/delete_search.png"); + width: 18px; + height: 18px; + + &:hover { + background-image: url("@/assets/flash/navigator/saves-search/delete_search_hover.png"); + + &:active { + background-image: url("@/assets/flash/navigator/saves-search/delete_search_click.png"); + } + + } +} diff --git a/src/components/navigator/NavigatorView.tsx b/src/components/navigator/NavigatorView.tsx index ab335c4a4..3f1b97b3d 100644 --- a/src/components/navigator/NavigatorView.tsx +++ b/src/components/navigator/NavigatorView.tsx @@ -1,8 +1,7 @@ -import { ConvertGlobalRoomIdMessageComposer, HabboWebTools, ILinkEventTracker, LegacyExternalInterface, NavigatorInitComposer, NavigatorSearchComposer, RoomSessionEvent } from '@nitrots/nitro-renderer'; +import { ConvertGlobalRoomIdMessageComposer, FindNewFriendsMessageComposer, HabboWebTools, ILinkEventTracker, LegacyExternalInterface, NavigatorInitComposer, NavigatorSearchComposer, RoomSessionEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useRef, useState } from 'react'; -import { FaPlus } from 'react-icons/fa'; -import { AddEventLinkTracker, LocalizeText, RemoveLinkEventTracker, SendMessageComposer, TryVisitRoom } from '../../api'; -import { Base, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text } from '../../common'; +import { AddEventLinkTracker, CreateLinkEvent, LocalizeText, RemoveLinkEventTracker, SendMessageComposer, TryVisitRoom } from '../../api'; +import { Base, Column, Flex, LayoutSearchSavesView, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text } from '../../common'; import { useNavigator, useRoomSessionManagerEvent } from '../../hooks'; import { NavigatorDoorStateView } from './views/NavigatorDoorStateView'; import { NavigatorRoomCreatorView } from './views/NavigatorRoomCreatorView'; @@ -10,6 +9,7 @@ import { NavigatorRoomInfoView } from './views/NavigatorRoomInfoView'; import { NavigatorRoomLinkView } from './views/NavigatorRoomLinkView'; import { NavigatorRoomSettingsView } from './views/room-settings/NavigatorRoomSettingsView'; import { NavigatorSearchResultView } from './views/search/NavigatorSearchResultView'; +import { NavigatorSearchSavesResultView } from './views/search/NavigatorSearchSavesResultView'; import { NavigatorSearchView } from './views/search/NavigatorSearchView'; export const NavigatorView: FC<{}> = props => @@ -19,10 +19,11 @@ export const NavigatorView: FC<{}> = props => const [ isCreatorOpen, setCreatorOpen ] = useState(false); const [ isRoomInfoOpen, setRoomInfoOpen ] = useState(false); const [ isRoomLinkOpen, setRoomLinkOpen ] = useState(false); + const [ isOpenSavesSearchs, setIsOpenSavesSearchs ] = 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 { searchResult = null, topLevelContext = null, topLevelContexts = null, navigatorData = null, navigatorSearches = null } = useNavigator(); const pendingSearch = useRef<{ value: string, code: string }>(null); const elementRef = useRef(); @@ -197,9 +198,12 @@ export const NavigatorView: FC<{}> = props => return ( <> { isVisible && - - setIsVisible(false) } /> + + CreateLinkEvent('habbopages/navigator') } onCloseClick={ event => setIsVisible(false) } /> + + setIsOpenSavesSearchs(prevValue => !prevValue) } /> + { topLevelContexts && (topLevelContexts.length > 0) && topLevelContexts.map((context, index) => { return ( @@ -210,28 +214,45 @@ export const NavigatorView: FC<{}> = props => }) } - { isLoading && - } - <> - - - { (searchResult && searchResult.results.map((result, index) => )) } - - - - - setCreatorOpen(value => !value) }> - - { LocalizeText('navigator.createroom.create') } - - - - - { LocalizeText('navigator.random.room') } - + { isLoading && } + + + { isOpenSavesSearchs && + + + + } + + + + { (searchResult && searchResult.results.map((result, index) => )) } + + + + setCreatorOpen(value => !value) }> + + { LocalizeText('navigator.createroom.create') } + + + { (searchResult?.code !== 'myworld_view' && searchResult?.code !== 'roomads_view') && + SendMessageComposer(new FindNewFriendsMessageComposer()) }> + + { LocalizeText('navigator.random.room') } + + + } + { (searchResult?.code === 'myworld_view' || searchResult?.code === 'roomads_view') && + CreateLinkEvent('catalog/open/room_event') }> + + { LocalizeText('navigator.promote.room') } + + + } + + - + } { isCreatorOpen && } diff --git a/src/components/navigator/views/NavigatorRoomCreatorView.tsx b/src/components/navigator/views/NavigatorRoomCreatorView.tsx index 2a9f915c1..ee9f06905 100644 --- a/src/components/navigator/views/NavigatorRoomCreatorView.tsx +++ b/src/components/navigator/views/NavigatorRoomCreatorView.tsx @@ -2,7 +2,7 @@ import { CreateFlatMessageComposer, HabboClubLevelEnum } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; import { CreateLinkEvent, GetClubMemberLevel, GetConfiguration, IRoomModel, LocalizeText, SendMessageComposer } from '../../../api'; -import { AutoGrid, Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; +import { AutoGrid, Base, Button, Column, Flex, Grid, LayoutInputErrorView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; import { RoomCreatorGridItem } from '../../../common/layout/RoomCreatorGridItem'; import { useNavigator } from '../../../hooks'; @@ -24,15 +24,19 @@ export const NavigatorRoomCreatorView: FC<{}> = props => const selectModel = (model: IRoomModel, index: number) => { - if(!model || (model.clubLevel > GetClubMemberLevel())) return; + if(!model) return; + + if (GetClubMemberLevel() < model.clubLevel) return CreateLinkEvent('habboUI/open/hccenter'); setSelectedModelName(roomModels[index].name); - }; + } const createRoom = () => { + if (!name || (name.length < 3)) return; + SendMessageComposer(new CreateFlatMessageComposer(name, description, 'model_' + selectedModelName, Number(category), Number(visitorsCount), tradesSetting)); - }; + } useEffect(() => { @@ -64,7 +68,7 @@ export const NavigatorRoomCreatorView: FC<{}> = props => }, []); return ( - + CreateLinkEvent('navigator/close-creator') } /> @@ -72,7 +76,8 @@ export const NavigatorRoomCreatorView: FC<{}> = props => { LocalizeText('navigator.createroom.roomnameinfo') } - setName(event.target.value) } placeholder={ LocalizeText('navigator.createroom.roomnameinfo') } /> + setName(event.target.value) } placeholder={ LocalizeText('navigator.createroom.roomnameinfo') } /> + { (!name || (name.length < 3)) && } { LocalizeText('navigator.createroom.roomdescinfo') } @@ -105,21 +110,21 @@ export const NavigatorRoomCreatorView: FC<{}> = props => - + - + { roomModels.map((model, index )=> { - return ( selectModel(model, index) } itemActive={ (selectedModelName === model.name) } overflow="unset" gap={ 0 } className="py-3" disabled={ (GetClubMemberLevel() < model.clubLevel) }> + return ( selectModel(model, index) } itemActive={ (selectedModelName === model.name) } overflow="unset" gap={ 0 } className="py-3"> - { model.tileSize } { LocalizeText('navigator.createroom.tilesize') } - { !hcDisabled && model.clubLevel > HabboClubLevelEnum.NO_CLUB && } + { model.tileSize } { LocalizeText('navigator.createroom.tilesize') } + { !hcDisabled && model.clubLevel > HabboClubLevelEnum.NO_CLUB && } { selectedModelName && } ); }) diff --git a/src/components/navigator/views/NavigatorRoomInfoView.tsx b/src/components/navigator/views/NavigatorRoomInfoView.tsx index 9f965c2bd..0af22a636 100644 --- a/src/components/navigator/views/NavigatorRoomInfoView.tsx +++ b/src/components/navigator/views/NavigatorRoomInfoView.tsx @@ -1,8 +1,7 @@ -import { GetCustomRoomFilterMessageComposer, NavigatorSearchComposer, RoomMuteComposer, RoomSettingsComposer, SecurityLevel, ToggleStaffPickMessageComposer, UpdateHomeRoomMessageComposer } from '@nitrots/nitro-renderer'; +import { AddFavouriteRoomMessageComposer, DeleteFavouriteRoomMessageComposer, GetCustomRoomFilterMessageComposer, NavigatorSearchComposer, RoomMuteComposer, RoomSettingsComposer, SecurityLevel, ToggleStaffPickMessageComposer, UpdateHomeRoomMessageComposer } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; -import { FaLink } from 'react-icons/fa'; -import { CreateLinkEvent, DispatchUiEvent, GetGroupInformation, GetSessionDataManager, LocalizeText, ReportType, SendMessageComposer } from '../../../api'; -import { Base, Button, classNames, Column, Flex, LayoutBadgeImageView, LayoutRoomThumbnailView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text, UserProfileIconView } from '../../../common'; +import { CreateLinkEvent, DispatchUiEvent, GetConfiguration, GetGroupInformation, GetSessionDataManager, GetUserProfile, LocalizeText, ReportType, SendMessageComposer } from '../../../api'; +import { Base, Button, Column, Flex, LayoutBadgeImageView, LayoutRoomThumbnailView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text, UserProfileIconView, classNames } from '../../../common'; import { RoomWidgetThumbnailEvent } from '../../../events'; import { useHelp, useNavigator } from '../../../hooks'; @@ -17,6 +16,7 @@ export const NavigatorRoomInfoView: FC = props => const { onCloseClick = null } = props; const [ isRoomPicked, setIsRoomPicked ] = useState(false); const [ isRoomMuted, setIsRoomMuted ] = useState(false); + const [ isOpenLink, setIsOpenLink ] = useState(false); const { report = null } = useHelp(); const { navigatorData = null } = useNavigator(); @@ -54,15 +54,14 @@ export const NavigatorRoomInfoView: FC = props => return; case 'open_room_thumbnail_camera': DispatchUiEvent(new RoomWidgetThumbnailEvent(RoomWidgetThumbnailEvent.TOGGLE_THUMBNAIL)); + onCloseClick(); return; case 'open_group_info': GetGroupInformation(navigatorData.enteredGuestRoom.habboGroupId); return; - case 'toggle_room_link': - CreateLinkEvent('navigator/toggle-room-link'); - return; case 'open_room_settings': SendMessageComposer(new RoomSettingsComposer(navigatorData.enteredGuestRoom.roomId)); + onCloseClick(); return; case 'toggle_pick': setIsRoomPicked(value => !value); @@ -74,12 +73,21 @@ export const NavigatorRoomInfoView: FC = props => return; case 'room_filter': SendMessageComposer(new GetCustomRoomFilterMessageComposer(navigatorData.enteredGuestRoom.roomId)); + onCloseClick(); return; case 'open_floorplan_editor': CreateLinkEvent('floor-editor/toggle'); + onCloseClick(); return; case 'report_room': report(ReportType.ROOM, { roomId: navigatorData.enteredGuestRoom.roomId, roomName: navigatorData.enteredGuestRoom.roomName }); + onCloseClick(); + return; + case 'set_favourite_room': + SendMessageComposer(new AddFavouriteRoomMessageComposer(navigatorData.enteredGuestRoom.roomId)); + return; + case 'set_unfavourite_room': + SendMessageComposer(new DeleteFavouriteRoomMessageComposer(navigatorData.enteredGuestRoom.roomId)); return; case 'close': onCloseClick(); @@ -109,40 +117,45 @@ export const NavigatorRoomInfoView: FC = props => - - processAction('set_home_room') } className={ classNames('flex-shrink-0 icon icon-house-small cursor-pointer', ((navigatorData.homeRoomId !== navigatorData.enteredGuestRoom.roomId) && 'gray')) } /> + { navigatorData.enteredGuestRoom.roomName } + + processAction('set_home_room') } className={ classNames('flex-shrink-0 icon icon-house-small', ((navigatorData.homeRoomId !== navigatorData.enteredGuestRoom.roomId) && 'cursor-pointer'), ((navigatorData.homeRoomId !== navigatorData.enteredGuestRoom.roomId) && 'gray')) } /> + { /* + { (navigatorData.enteredGuestRoom.ownerId !== GetSessionDataManager().userId) && + processAction('set_favourite_room') } className={ classNames('flex-shrink-0 icon icon-favourite-room-active cursor-pointer') } /> + processAction('set_unfavourite_room') } className={ classNames('flex-shrink-0 icon icon-favourite-room-inactive cursor-pointer') } /> + } + */ } + { navigatorData.enteredGuestRoom.showOwner && - + GetUserProfile(navigatorData.enteredGuestRoom.ownerId) }> { LocalizeText('navigator.roomownercaption') } { navigatorData.enteredGuestRoom.ownerName } - } + + } { LocalizeText('navigator.roomrating') } { navigatorData.currentRoomRating } { (navigatorData.enteredGuestRoom.tags.length > 0) && - + { navigatorData.enteredGuestRoom.tags.map(tag => { - return processAction('navigator_search_tag', tag) }>#{ tag } + return processAction('navigator_search_tag', tag) }>#{ tag } }) } - } - - - { hasPermission('settings') && - processAction('open_room_settings') } /> } - CreateLinkEvent('navigator/toggle-room-link') } /> + + } - { navigatorData.enteredGuestRoom.description } + { navigatorData.enteredGuestRoom.description } - - { hasPermission('settings') && processAction('open_room_thumbnail_camera') } /> } + + { hasPermission('settings') && processAction('open_room_thumbnail_camera') } /> } { (navigatorData.enteredGuestRoom.habboGroupId > 0) && @@ -153,37 +166,52 @@ export const NavigatorRoomInfoView: FC = props => } - processAction('toggle_room_link') }> + setIsOpenLink(prevValue => !prevValue) }> { LocalizeText('navigator.embed.caption') } + { (isOpenLink) && + + { LocalizeText('navigator.embed.info') } + ('url.prefix', '')) } /> + + } { hasPermission('staff_pick') && - } - + + } { hasPermission('settings') && - <> - + + + + } + + + + { hasPermission('settings') && - - - } + } - } + + } diff --git a/src/components/navigator/views/NavigatorRoomLinkView.tsx b/src/components/navigator/views/NavigatorRoomLinkView.tsx index 7f0b9f789..de3e28b43 100644 --- a/src/components/navigator/views/NavigatorRoomLinkView.tsx +++ b/src/components/navigator/views/NavigatorRoomLinkView.tsx @@ -16,7 +16,7 @@ export const NavigatorRoomLinkView: FC = props => if(!navigatorData.enteredGuestRoom) return null; return ( - + @@ -25,7 +25,7 @@ export const NavigatorRoomLinkView: FC = props => { LocalizeText('navigator.embed.headline') } { LocalizeText('navigator.embed.info') } { LocalizeText('navigator.embed.direct.info') } - + ('url.prefix', '')) } /> diff --git a/src/components/navigator/views/room-settings/NavigatorRoomSettingsView.tsx b/src/components/navigator/views/room-settings/NavigatorRoomSettingsView.tsx index bdda17798..874fd4ba7 100644 --- a/src/components/navigator/views/room-settings/NavigatorRoomSettingsView.tsx +++ b/src/components/navigator/views/room-settings/NavigatorRoomSettingsView.tsx @@ -1,6 +1,6 @@ import { RoomBannedUsersComposer, RoomDataParser, RoomSettingsDataEvent, SaveRoomSettingsComposer } from '@nitrots/nitro-renderer'; import { FC, useState } from 'react'; -import { IRoomData, LocalizeText, SendMessageComposer } from '../../../../api'; +import { CreateLinkEvent, IRoomData, LocalizeText, SendMessageComposer } from '../../../../api'; import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../common'; import { useMessageEvent } from '../../../../hooks'; import { NavigatorRoomSettingsAccessTabView } from './NavigatorRoomSettingsAccessTabView'; @@ -182,7 +182,7 @@ export const NavigatorRoomSettingsView: FC<{}> = props => return ( - + (currentTab === TABS[3]) ? CreateLinkEvent('habbopages/chat/options') : null }onCloseClick={ onClose } /> { TABS.map(tab => { diff --git a/src/components/navigator/views/search/NavigatorSearchResultItemInfoView.tsx b/src/components/navigator/views/search/NavigatorSearchResultItemInfoView.tsx index 1bc60fb9c..6818d0825 100644 --- a/src/components/navigator/views/search/NavigatorSearchResultItemInfoView.tsx +++ b/src/components/navigator/views/search/NavigatorSearchResultItemInfoView.tsx @@ -1,5 +1,5 @@ import { RoomDataParser } from '@nitrots/nitro-renderer'; -import { FC, useRef, useState } from 'react'; +import { FC, useEffect, useRef, useState } from 'react'; import { Overlay, Popover } from 'react-bootstrap'; import { FaUser } from 'react-icons/fa'; import { LocalizeText } from '../../../../api'; @@ -15,6 +15,37 @@ export const NavigatorSearchResultItemInfoView: FC(); + const [ showProfile, setShowProfile ] = useState(false); + + useEffect(() => + { + if (showProfile) + { + setIsVisible(false); + } + }, [ showProfile ]); + + useEffect(() => + { + // add when mounted + document.addEventListener('mousedown', handleClick); + // return function to be called when unmounted + return () => + { + document.removeEventListener('mousedown', handleClick); + }; + }, []); + + const handleClick = e => + { + if (elementRef.current.contains(e.target)) + { + // inside click + return; + } + // outside click + setIsVisible(false); + }; const getUserCounterColor = () => { @@ -40,8 +71,12 @@ export const NavigatorSearchResultItemInfoView: FC - setIsVisible(true) } onMouseLeave={ event => setIsVisible(false) } /> - + + { + isVisible || showProfile ? setIsVisible(false) : setIsVisible(true); + event.stopPropagation(); + } } /> + @@ -60,7 +95,10 @@ export const NavigatorSearchResultItemInfoView: FC - + + { + setShowProfile(true); + } } /> { roomData.ownerName } diff --git a/src/components/navigator/views/search/NavigatorSearchResultView.tsx b/src/components/navigator/views/search/NavigatorSearchResultView.tsx index f3de4637f..3ad8b4867 100644 --- a/src/components/navigator/views/search/NavigatorSearchResultView.tsx +++ b/src/components/navigator/views/search/NavigatorSearchResultView.tsx @@ -1,8 +1,8 @@ -import { NavigatorSearchComposer, NavigatorSearchResultList } from '@nitrots/nitro-renderer'; +import { NavigatorSearchComposer, NavigatorSearchResultList, NavigatorSearchSaveComposer } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; import { FaBars, FaMinus, FaPlus, FaTh, FaWindowMaximize, FaWindowRestore } from 'react-icons/fa'; import { LocalizeText, NavigatorSearchResultViewDisplayMode, SendMessageComposer } from '../../../../api'; -import { AutoGrid, AutoGridProps, Column, Flex, Grid, Text } from '../../../../common'; +import { AutoGrid, AutoGridProps, Column, Flex, Grid, LayoutSearchSavesView } from '../../../../common'; import { useNavigator } from '../../../../hooks'; import { NavigatorSearchResultItemView } from './NavigatorSearchResultItemView'; @@ -70,6 +70,7 @@ export const NavigatorSearchResultView: FC = pro { (displayMode >= NavigatorSearchResultViewDisplayMode.THUMBNAILS) && } { (searchResult.action > 0) && (searchResult.action === 1) && } { (searchResult.action > 0) && (searchResult.action !== 1) && } + { (topLevelContext.code !== 'official_view') && SendMessageComposer(new NavigatorSearchSaveComposer(getResultTitle(), searchResult.data)) } /> } { isExtended && <> @@ -83,35 +84,5 @@ export const NavigatorSearchResultView: FC = pro } - //
- //
- //
- // - //
{ LocalizeText(getResultTitle()) }
- // = NavigatorSearchResultViewDisplayMode.THUMBNAILS })}> - //
- // { isExtended && - //
= NavigatorSearchResultViewDisplayMode.THUMBNAILS) }) }> - // { searchResult.rooms.length > 0 && searchResult.rooms.map((room, index) => - // { - // return - // }) } - //
} - //
- //
- //
- //
- // - //
{ LocalizeText(getListCode()) }
- // = NavigatorResultListViewDisplayMode.THUMBNAILS })} onClick={ toggleDisplayMode }> - //
- //
= NavigatorResultListViewDisplayMode.THUMBNAILS }) }> - // { isExtended && resultList && resultList.rooms.map((room, index) => - // { - // return - // }) - // } - //
- //
); } diff --git a/src/components/navigator/views/search/NavigatorSearchSavesResultItemView.tsx b/src/components/navigator/views/search/NavigatorSearchSavesResultItemView.tsx new file mode 100644 index 000000000..6ed4b0cfa --- /dev/null +++ b/src/components/navigator/views/search/NavigatorSearchSavesResultItemView.tsx @@ -0,0 +1,47 @@ +import { NavigatorDeleteSavedSearchComposer, NavigatorSavedSearch, NavigatorSearchComposer } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { LocalizeText, SendMessageComposer } from '../../../../api'; +import { Base, Flex, Text } from '../../../../common'; + +export interface NavigatorSearchSavesResultItemViewProps +{ + search: NavigatorSavedSearch +} + +export const NavigatorSearchSavesResultItemView: FC = props => +{ + const { search = null } = props; + const [ isHoverText, setIsHoverText ] = useState(false); + const [ currentIndex, setCurrentIndex ] = useState(0); + + const onHover = (searchId: number) => + { + setCurrentIndex(searchId); + setIsHoverText(true); + } + + const onLeave = () => + { + setCurrentIndex(0); + setIsHoverText(false); + } + + const getResultTitle = () => + { + let name = search.code; + + if(!name || !name.length || LocalizeText('navigator.searchcode.title.' + name) == ('navigator.searchcode.title.' + name)) return search.code; + + if(name.startsWith('${')) return name.slice(2, (name.length - 1)); + + return ('navigator.searchcode.title.' + name); + } + + return ( + onHover(search.id) } onMouseLeave={ () => onLeave() }> + { (isHoverText && currentIndex === search.id) && + SendMessageComposer(new NavigatorDeleteSavedSearchComposer(search.id)) } /> } + SendMessageComposer(new NavigatorSearchComposer(search.code.split('.').reverse()[0], search.filter)) }>{ LocalizeText(getResultTitle()) } + + ); +} diff --git a/src/components/navigator/views/search/NavigatorSearchSavesResultView.tsx b/src/components/navigator/views/search/NavigatorSearchSavesResultView.tsx new file mode 100644 index 000000000..6d2eff178 --- /dev/null +++ b/src/components/navigator/views/search/NavigatorSearchSavesResultView.tsx @@ -0,0 +1,30 @@ +import { NavigatorSavedSearch } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { FaBolt } from 'react-icons/fa'; +import { LocalizeText } from '../../../../api'; +import { Column, Flex, Text } from '../../../../common'; +import { NavigatorSearchSavesResultItemView } from './NavigatorSearchSavesResultItemView'; + +export interface NavigatorSearchSavesResultViewProps +{ + searchs: NavigatorSavedSearch[] +} + +export const NavigatorSearchSavesResultView: FC = props => +{ + const { searchs = [] } = props; + + return ( + + + + { LocalizeText('navigator.quick.links.title') } + + + { (searchs && searchs.length > 0) && + searchs.map((search: NavigatorSavedSearch) => ) + } + + + ); +} diff --git a/src/components/navigator/views/search/NavigatorSearchView.tsx b/src/components/navigator/views/search/NavigatorSearchView.tsx index 51cb1bda4..844e8714d 100644 --- a/src/components/navigator/views/search/NavigatorSearchView.tsx +++ b/src/components/navigator/views/search/NavigatorSearchView.tsx @@ -1,7 +1,6 @@ import { FC, KeyboardEvent, useEffect, useState } from 'react'; -import { FaSearch } from 'react-icons/fa'; import { INavigatorSearchFilter, LocalizeText, SearchFilterOptions } from '../../../../api'; -import { Button, Flex } from '../../../../common'; +import { Flex } from '../../../../common'; import { useNavigator } from '../../../../hooks'; export interface NavigatorSearchViewProps @@ -64,7 +63,7 @@ export const NavigatorSearchView: FC = props => }, [ searchResult ]); return ( - + ('motto.max.length', 38) } value={ motto } onChange={ event => setMotto(event.target.value) } onBlur={ onMottoBlur } onKeyDown={ onMottoKeyDown } autoFocus={ true } /> } + ('motto.max.length', 38) } value={ motto } onChange={ event => setMotto(event.target.value) } onKeyDown={ onMottoKeyDown } autoFocus={ true } /> } }
- - { LocalizeText('infostand.text.achievement_score') + ' ' + avatarInfo.achievementScore } - + { LocalizeText('infostand.text.achievement_score') } + { avatarInfo.achievementScore } { (avatarInfo.carryItem > 0) && <>
@@ -201,7 +197,7 @@ export const InfoStandWidgetUserView: FC = props = }
- + { GetConfiguration('user.tags.enabled') && diff --git a/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetAvatarView.tsx b/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetAvatarView.tsx index efb75f505..76e09d7e2 100644 --- a/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetAvatarView.tsx +++ b/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetAvatarView.tsx @@ -1,6 +1,5 @@ import { RoomControllerLevel, RoomObjectCategory, RoomObjectVariable, RoomUnitGiveHandItemComposer, SetRelationshipStatusComposer, TradingOpenComposer } from '@nitrots/nitro-renderer'; import { FC, useEffect, useMemo, useState } from 'react'; -import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; import { AvatarInfoUser, CreateLinkEvent, DispatchUiEvent, GetOwnRoomObject, GetSessionDataManager, GetUserProfile, LocalizeText, MessengerFriend, ReportType, RoomWidgetUpdateChatInputContentEvent, SendMessageComposer } from '../../../../../api'; import { Base, Flex } from '../../../../../common'; import { useFriends, useHelp, useRoom, useSessionInfo } from '../../../../../hooks'; @@ -203,7 +202,7 @@ export const AvatarInfoWidgetAvatarView: FC = p return ( - GetUserProfile(avatarInfo.webID) }> + GetUserProfile(avatarInfo.webID) }> { avatarInfo.name } { (mode === MODE_NORMAL) && @@ -225,7 +224,6 @@ export const AvatarInfoWidgetAvatarView: FC = p { !canRequestFriend(avatarInfo.webID) && processAction('relationship') }> { LocalizeText('infostand.link.relationship') } - } { !avatarInfo.isIgnored && processAction('ignore') }> @@ -240,12 +238,10 @@ export const AvatarInfoWidgetAvatarView: FC = p { moderateMenuHasContent && processAction('moderate') }> - { LocalizeText('infostand.link.moderate') } } { avatarInfo.isAmbassador && processAction('ambassador') }> - { LocalizeText('infostand.link.ambassador') } } { canGiveHandItem && processAction('pass_hand_item') }> @@ -258,11 +254,9 @@ export const AvatarInfoWidgetAvatarView: FC = p { LocalizeText('infostand.button.kick') } processAction('mute') }> - { LocalizeText('infostand.button.mute') } processAction('ban') }> - { LocalizeText('infostand.button.ban') } { isShowGiveRights && @@ -274,7 +268,6 @@ export const AvatarInfoWidgetAvatarView: FC = p { LocalizeText('infostand.button.removerights') } } processAction('back') }> - { LocalizeText('generic.back') } } @@ -290,7 +283,6 @@ export const AvatarInfoWidgetAvatarView: FC = p { LocalizeText('infostand.button.perm_ban') } processAction('back_moderate') }> - { LocalizeText('generic.back') } } @@ -306,7 +298,6 @@ export const AvatarInfoWidgetAvatarView: FC = p { LocalizeText('infostand.button.mute_10min') } processAction('back_moderate') }> - { LocalizeText('generic.back') } } @@ -320,10 +311,8 @@ export const AvatarInfoWidgetAvatarView: FC = p processAction('ambassador_mute') }> { LocalizeText('infostand.button.mute') } - processAction('back') }> - { LocalizeText('generic.back') } } @@ -342,7 +331,6 @@ export const AvatarInfoWidgetAvatarView: FC = p { LocalizeText('infostand.button.mute_18hour') } processAction('back_ambassador') }> - { LocalizeText('generic.back') } } @@ -363,7 +351,6 @@ export const AvatarInfoWidgetAvatarView: FC = p { LocalizeText('avatar.widget.clear_relationship') } processAction('back') }> - { LocalizeText('generic.back') } } diff --git a/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetDecorateView.tsx b/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetDecorateView.tsx index 5ad5a6800..273503c29 100644 --- a/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetDecorateView.tsx +++ b/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetDecorateView.tsx @@ -1,6 +1,7 @@ import { RoomObjectCategory } from '@nitrots/nitro-renderer'; import { Dispatch, FC, SetStateAction } from 'react'; -import { LocalizeText } from '../../../../../api'; +import { GetUserProfile, LocalizeText } from '../../../../../api'; +import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView'; import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView'; import { ContextMenuListView } from '../../context-menu/ContextMenuListView'; import { ContextMenuView } from '../../context-menu/ContextMenuView'; @@ -18,7 +19,10 @@ export const AvatarInfoWidgetDecorateView: FC const { userId = -1, userName = '', roomIndex = -1, setIsDecorating = null } = props; return ( - + + GetUserProfile(userId) }> + { userName } + setIsDecorating(false) }> { LocalizeText('widget.avatar.stop_decorating') } diff --git a/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetFurniView.tsx b/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetFurniView.tsx index 4dcf4b585..61521d9bf 100644 --- a/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetFurniView.tsx +++ b/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetFurniView.tsx @@ -42,7 +42,7 @@ export const AvatarInfoWidgetFurniView: FC = pro } return ( - + { avatarInfo.name } diff --git a/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetOwnAvatarView.tsx b/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetOwnAvatarView.tsx index cf24b8203..1da463a94 100644 --- a/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetOwnAvatarView.tsx +++ b/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetOwnAvatarView.tsx @@ -1,7 +1,6 @@ -import { AvatarAction, AvatarExpressionEnum, RoomControllerLevel, RoomObjectCategory, RoomUnitDropHandItemComposer } from '@nitrots/nitro-renderer'; +import { AvatarAction, AvatarExpressionEnum, HabboClubLevelEnum, RoomControllerLevel, RoomObjectCategory, RoomUnitDropHandItemComposer } from '@nitrots/nitro-renderer'; import { Dispatch, FC, SetStateAction, useState } from 'react'; -import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; -import { AvatarInfoUser, CreateLinkEvent, DispatchUiEvent, GetCanStandUp, GetCanUseExpression, GetOwnPosture, GetUserProfile, HasHabboClub, HasHabboVip, IsRidingHorse, LocalizeText, PostureTypeEnum, SendMessageComposer } from '../../../../../api'; +import { AvatarInfoUser, CreateLinkEvent, DispatchUiEvent, GetCanStandUp, GetCanUseExpression, GetOwnPosture, GetSessionDataManager, GetUserProfile, HasHabboClub, IsRidingHorse, LocalizeText, PostureTypeEnum, SendMessageComposer } from '../../../../../api'; import { Column, Flex, LayoutCurrencyIcon } from '../../../../../common'; import { HelpNameChangeEvent } from '../../../../../events'; import { useRoom } from '../../../../../hooks'; @@ -68,9 +67,11 @@ export const AvatarInfoWidgetOwnAvatarView: FC - GetUserProfile(avatarInfo.webID) }> + GetUserProfile(avatarInfo.webID) }> { avatarInfo.name } { !hide && ( @@ -132,21 +133,19 @@ export const AvatarInfoWidgetOwnAvatarView: FC processAction('decorate') }> { LocalizeText('widget.avatar.decorate') } } - processAction('change_looks') }> + processAction('change_looks') }> { LocalizeText('widget.memenu.myclothes') } { (HasHabboClub() && !isRidingHorse) && processAction('dance_menu') }> { LocalizeText('widget.memenu.dance') } - } { (!isDancing && !HasHabboClub() && !isRidingHorse) && processAction('dance') }> { LocalizeText('widget.memenu.dance') } - } { (isDancing && !HasHabboClub() && !isRidingHorse) && @@ -156,13 +155,11 @@ export const AvatarInfoWidgetOwnAvatarView: FC processAction('expressions') }> { LocalizeText('infostand.link.expressions') } - processAction('signs') }> { LocalizeText('infostand.show.signs') } - { (avatarInfo.carryItem > 0) && @@ -190,7 +187,6 @@ export const AvatarInfoWidgetOwnAvatarView: FC processAction('back') }> - { LocalizeText('generic.back') } @@ -210,13 +206,13 @@ export const AvatarInfoWidgetOwnAvatarView: FC } { GetCanUseExpression() && - processAction('laugh') }> - { !HasHabboVip() && } + processAction('laugh') }> + { GetSessionDataManager().clubLevel === HabboClubLevelEnum.NO_CLUB && } { LocalizeText('widget.memenu.laugh') } } { GetCanUseExpression() && - processAction('blow') }> - { !HasHabboVip() && } + processAction('blow') }> + { GetSessionDataManager().clubLevel === HabboClubLevelEnum.NO_CLUB && } { LocalizeText('widget.memenu.blow') } } processAction('idle') }> @@ -224,7 +220,6 @@ export const AvatarInfoWidgetOwnAvatarView: FC processAction('back') }> - { LocalizeText('generic.back') } @@ -299,7 +294,6 @@ export const AvatarInfoWidgetOwnAvatarView: FC processAction('back') }> - { LocalizeText('generic.back') } diff --git a/src/components/room/widgets/chat-input/ChatInputView.scss b/src/components/room/widgets/chat-input/ChatInputView.scss index ed322f1eb..ddc5a5dc6 100644 --- a/src/components/room/widgets/chat-input/ChatInputView.scss +++ b/src/components/room/widgets/chat-input/ChatInputView.scss @@ -2,9 +2,8 @@ display: flex; align-items: center; position: absolute; + width: 40vw; bottom:65px; - left: 30vw; - right: 30vw; height: 38px; border-image-source: url(@/assets/flash/room/chatinput.png); border-image-slice:8 8 8 8 fill; @@ -12,27 +11,28 @@ background: #e4e4e4; border-radius: 9px; padding-right: 10px; - overflow: hidden; z-index: 31; @include media-breakpoint-down(sm) { display: flex; position: absolute; bottom: 70px; - left: calc(100% / 3); - width: 200px; + left: 30px; + width: 85vw; } .input-sizer { - display: inline-grid; - vertical-align: top; - height: 100%; + align-content: center; + display: flex; padding-right: 10px; + .chat-input { + width: 100%; + } + &::after, input, textarea { - width: auto; min-width: 1em; grid-area: 1 / 2; margin: 0; @@ -99,3 +99,20 @@ border-image-width:9px 9px 9px 9px; margin-bottom: -13px; } + +.info-habbopages { + cursor: pointer; + background-image: url("@/assets/flash/boxes/card/questionmark.png"); + width: 19px; + height: 20px; + right: -18px; + position: absolute; + + &:hover { + background-image: url("@/assets/flash/boxes/card/questionmark_hover.png"); + + &:active { + background-image: url("@/assets/flash/boxes/card/questionmark_click.png"); + } + } +} diff --git a/src/components/room/widgets/chat-input/ChatInputView.tsx b/src/components/room/widgets/chat-input/ChatInputView.tsx index 1f50236b8..ff1eafc13 100644 --- a/src/components/room/widgets/chat-input/ChatInputView.tsx +++ b/src/components/room/widgets/chat-input/ChatInputView.tsx @@ -1,8 +1,8 @@ import { HabboClubLevelEnum, RoomControllerLevel } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; -import { ChatMessageTypeEnum, GetClubMemberLevel, GetConfiguration, GetSessionDataManager, LocalizeText, RoomWidgetUpdateChatInputContentEvent } from '../../../../api'; -import { Text } from '../../../../common'; +import { ChatMessageTypeEnum, CreateLinkEvent, GetClubMemberLevel, GetConfiguration, GetSessionDataManager, LocalizeText, RoomWidgetUpdateChatInputContentEvent } from '../../../../api'; +import { Base, Text } from '../../../../common'; import { useChatInputWidget, useRoom, useSessionInfo, useUiEvent } from '../../../../hooks'; import { ChatInputStyleSelectorView } from './ChatInputStyleSelectorView'; @@ -11,6 +11,7 @@ export const ChatInputView: FC<{}> = props => const [ chatValue, setChatValue ] = useState(''); const { chatStyleId = 0, updateChatStyleId = null } = useSessionInfo(); const { selectedUsername = '', floodBlocked = false, floodBlockedSeconds = 0, setIsTyping = null, setIsIdle = null, sendChat = null } = useChatInputWidget(); + const [ showInfoHabboPages, setShowInfohabboPages ] = useState(false); const { roomSession = null } = useRoom(); const inputRef = useRef(); @@ -235,14 +236,18 @@ export const ChatInputView: FC<{}> = props => return ( createPortal( -
+
setShowInfohabboPages(true) } onMouseLeave={ () => setTimeout(() => setShowInfohabboPages(false), 100) }> -
+
{ !floodBlocked && updateChatInput(event.target.value) } onMouseDown={ event => setInputFocus() } /> } { floodBlocked && { LocalizeText('chat.input.alert.flood', [ 'time' ], [ floodBlockedSeconds.toString() ]) } }
-
, document.getElementById('toolbar-chat-input-container')) + { (showInfoHabboPages) && + CreateLinkEvent('habbopages/chat/chatting') }> + } +
, + document.getElementById('toolbar-chat-input-container')) ); } diff --git a/src/components/room/widgets/furniture/FurnitureExternalImageView.tsx b/src/components/room/widgets/furniture/FurnitureExternalImageView.tsx index 3abaad7a6..ed28dc048 100644 --- a/src/components/room/widgets/furniture/FurnitureExternalImageView.tsx +++ b/src/components/room/widgets/furniture/FurnitureExternalImageView.tsx @@ -1,6 +1,6 @@ import { FC } from 'react'; import { GetSessionDataManager, ReportType } from '../../../../api'; -import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; +import { Base, DraggableWindow, DraggableWindowPosition, Flex } from '../../../../common'; import { useFurnitureExternalImageWidget, useHelp } from '../../../../hooks'; import { CameraWidgetShowPhotoView } from '../../../camera/views/CameraWidgetShowPhotoView'; @@ -12,11 +12,14 @@ export const FurnitureExternalImageView: FC<{}> = props => if((objectId === -1) || (currentPhotoIndex === -1)) return null; return ( - - report(ReportType.PHOTO, { extraData: currentPhotos[currentPhotoIndex].w, roomId: currentPhotos[currentPhotoIndex].s, reportedUserId: GetSessionDataManager().userId, roomObjectId: Number(currentPhotos[currentPhotoIndex].u) }) } onCloseClick={ onClose } /> - + + + + report(ReportType.PHOTO, { extraData: currentPhotos[currentPhotoIndex].w, roomId: currentPhotos[currentPhotoIndex].s, reportedUserId: GetSessionDataManager().userId, roomObjectId: Number(currentPhotos[currentPhotoIndex].u) }) } /> + + - - + + ); } diff --git a/src/components/room/widgets/furniture/FurnitureFootballGateView.tsx b/src/components/room/widgets/furniture/FurnitureFootballGateView.tsx new file mode 100644 index 000000000..cdfbc858f --- /dev/null +++ b/src/components/room/widgets/furniture/FurnitureFootballGateView.tsx @@ -0,0 +1,36 @@ +import { FC } from 'react'; +import { CreateLinkEvent, FigureData, LocalizeText } from '../../../../api'; +import { Button, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; +import { useFurnitureFootballGateWidget } from '../../../../hooks'; + +export const FurnitureFootballGateView: FC<{}> = props => +{ + const { objectId, setObjectId, onClose } = useFurnitureFootballGateWidget(); + + const onGender = (gender: string) => + { + CreateLinkEvent(`avatar-editor/show/${ gender }/${ objectId }`); + setObjectId(-1); + } + + if(objectId === -1) return null; + + return ( + + + + + { LocalizeText('widget.furni.clothingchange.gender.info') } + + + + + + + + ); +} diff --git a/src/components/room/widgets/furniture/FurnitureHighScoreView.tsx b/src/components/room/widgets/furniture/FurnitureHighScoreView.tsx index e8b3fa975..34459ed8e 100644 --- a/src/components/room/widgets/furniture/FurnitureHighScoreView.tsx +++ b/src/components/room/widgets/furniture/FurnitureHighScoreView.tsx @@ -19,39 +19,45 @@ export const FurnitureHighScoreView: FC<{}> = props => { return ( - - - { LocalizeText('high.score.display.caption', [ 'scoretype', 'cleartype' ], [ LocalizeText(`high.score.display.scoretype.${ getScoreType(stuffData.scoreType) }`), LocalizeText(`high.score.display.cleartype.${ getClearType(stuffData.clearType) }`) ]) } - - - - - - { LocalizeText('high.score.display.users.header') } - - - { LocalizeText('high.score.display.score.header') } - +
+ + + + { LocalizeText('high.score.display.caption', [ 'scoretype', 'cleartype' ], [ LocalizeText(`high.score.display.scoretype.${ getScoreType(stuffData.scoreType) }`), LocalizeText(`high.score.display.cleartype.${ getClearType(stuffData.clearType) }`) ]) } -
-
- - { stuffData.entries.map((entry, index) => - { - return ( - - - { entry.users.join(', ') } - - - { entry.score } - - - ); - }) } - - - + + + + + + { LocalizeText('high.score.display.users.header') } + + + { LocalizeText('high.score.display.score.header') } + + + + + { stuffData.entries.map((entry, index) => + { + return ( + + + { entry.users.join(', ') } + + + { entry.score } + + + ); + }) } + + + { LocalizeText('high.score.display.congratulations.footer') } + + + +
); }) } diff --git a/src/components/room/widgets/furniture/FurnitureRentableSpaceView.tsx b/src/components/room/widgets/furniture/FurnitureRentableSpaceView.tsx new file mode 100644 index 000000000..e5877916f --- /dev/null +++ b/src/components/room/widgets/furniture/FurnitureRentableSpaceView.tsx @@ -0,0 +1,43 @@ +import { FriendlyTime } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { LocalizeText } from '../../../../api'; +import { Button, Column, Flex, LayoutCurrencyIcon, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; +import { useFurnitureRentableSpaceWidget } from '../../../../hooks'; + +export const FurnitureRentableSpaceView: FC<{}> = props => +{ + const { renter, isRoomOwner, onRent, onCancelRent, onClose } = useFurnitureRentableSpaceWidget(); + + if(!renter) return null; + + return ( + + + + + { (!renter.rented) && + <> + { LocalizeText('rentablespace.widget.instructions') } + + { renter.price + ' x' }  +   + { LocalizeText('catalog.purchase_confirmation.rent') } + + + } + { (renter.rented) && + <> + { LocalizeText('rentablespace.widget.rented_to_label') } + { renter.renterName } + { LocalizeText('rentablespace.widget.expires_label') } + { FriendlyTime.shortFormat(renter.timeRemaining) } + { (isRoomOwner) && + } + + + } + + + + ); +} \ No newline at end of file diff --git a/src/components/room/widgets/furniture/FurnitureWidgets.scss b/src/components/room/widgets/furniture/FurnitureWidgets.scss index 65b6559fc..02caf9f57 100644 --- a/src/components/room/widgets/furniture/FurnitureWidgets.scss +++ b/src/components/room/widgets/furniture/FurnitureWidgets.scss @@ -1,526 +1,632 @@ -.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; - } - } -} +.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 { + + border-top: 71px solid rgba(0, 0, 0, 0.5); + border-bottom: 71px solid rgba(0, 0, 0, 0.5); + border-right: 84px solid rgba(0, 0, 0, 0.5); + border-left: 84px solid rgba(0, 0, 0, 0.5); + border-radius: 20px; + + .nitro-card-header-close { + cursor: pointer; + background-image: url("@/assets/flash/boxes/card/close.png"); + width: 19px; + height: 20px; + + &:hover { + background-image: url("@/assets/flash/boxes/card/close_hover.png"); + + &:active { + background-image: url("@/assets/flash/boxes/card/close_click.png"); + } + + } + } + + .nitro-camera-report { + cursor: pointer; + background-image: url("@/assets/flash/camera/report.png"); + width: 19px; + height: 20px; + + &:hover { + background-image: url("@/assets/flash/camera/report_hover.png"); + + &:active { + background-image: url("@/assets/flash/camera/report_click.png"); + } + + } + } + + .picture-preview { + width: 320px; + height: 320px; + } + + .center-buttons { + top: 45%; + } + + .nitro-camera-button-left { + cursor: pointer; + background-image: url("@/assets/flash/camera/camera_button_left.png"); + width: 64px; + height: 64px; + } + + .nitro-camera-button-right { + cursor: pointer; + background-image: url("@/assets/flash/camera/camera_button_right.png"); + width: 64px; + height: 64px; + } +} + +.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: 280px; + max-width: 280px; + height: 320px; + background-color: #bab8b4; + border-radius: 0.3rem; + border: solid 1px #000; + + .header { + border-image-source: none !important; + background-color: #40403e; + border-radius: 0.3rem 0.3rem 0 0; + } + + .align-right { + text-align: right; + } + + .section-border { + border: solid 1px #8c8a88; + } + + .high-score-content { + height: 100%; + background-color: #000; + border-radius: 0.3rem; + } + + .score-color { + color: #bab8b4; + } + + .score-footer { + color: #9a9896; + font-weight: bold; + } +} + +.high-score-wired { + .nitro-context-menu:not(.name-only)::after { + border-bottom: solid 1px #000; + border-right: solid 1px #000; + content: ""; + width: 11px; + height: 9px; + bottom: -5px; + left: 0; + right: 0; + margin: auto; + background-image: none !important; + background-color: #bab8b4; + transform: rotate(42deg); + } +} + +.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; + } + } +} + +.nitro-football-gate +{ + width: 300px; + + .football-gate-content + { + color: black; + + .size-buttons + { + width: 100px; + } + } +} diff --git a/src/components/room/widgets/furniture/FurnitureWidgetsView.tsx b/src/components/room/widgets/furniture/FurnitureWidgetsView.tsx index d0e4066d6..73e0e6593 100644 --- a/src/components/room/widgets/furniture/FurnitureWidgetsView.tsx +++ b/src/components/room/widgets/furniture/FurnitureWidgetsView.tsx @@ -1,48 +1,52 @@ -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 ( - - - - - - - - - - - - - - - - - - - - - - ); -} +import { FC } from 'react'; +import { Base } from '../../../../common'; +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 { FurnitureFootballGateView } from './FurnitureFootballGateView'; +import { FurnitureFriendFurniView } from './FurnitureFriendFurniView'; +import { FurnitureGiftOpeningView } from './FurnitureGiftOpeningView'; +import { FurnitureHighScoreView } from './FurnitureHighScoreView'; +import { FurnitureInternalLinkView } from './FurnitureInternalLinkView'; +import { FurnitureMannequinView } from './FurnitureMannequinView'; +import { FurnitureRentableSpaceView } from './FurnitureRentableSpaceView'; +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 { FurnitureContextMenuView } from './context-menu/FurnitureContextMenuView'; +import { FurniturePlaylistEditorWidgetView } from './playlist-editor/FurniturePlaylistEditorWidgetView'; + +export const FurnitureWidgetsView: FC<{}> = props => +{ + return ( + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/components/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx b/src/components/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx index 40e9579cf..43b4ac2cf 100644 --- a/src/components/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx +++ b/src/components/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx @@ -52,7 +52,7 @@ export const FurnitureContextMenuView: FC<{}> = props => } { (objectId >= 0) && mode && - + { (mode === ContextMenuEnum.FRIEND_FURNITURE) && <> diff --git a/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.scss b/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.scss index 254821ced..d6b9e0408 100644 --- a/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.scss +++ b/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.scss @@ -49,4 +49,21 @@ background-position: center; background-repeat: no-repeat; } + + .button { + background-image: url('@/assets/flash/messenger/button.png'); + background-repeat: no-repeat; + height: 20px; + } + + .text-link { + color: #05A3D8; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + &:hover { + color: #79b1d3; + } + } } diff --git a/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.tsx b/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.tsx index 83ae06cfe..54bb4ced6 100644 --- a/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.tsx +++ b/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.tsx @@ -1,7 +1,6 @@ import { MysteryBoxKeysUpdateEvent } from '@nitrots/nitro-renderer'; import { FC, useState } from 'react'; -import { FaChevronDown, FaChevronUp } from 'react-icons/fa'; -import { ColorUtils, LocalizeText } from '../../../../api'; +import { ColorUtils, CreateLinkEvent, LocalizeText } from '../../../../api'; import { Base, Column, Flex, LayoutGridItem, Text } from '../../../../common'; import { useSessionDataManagerEvent } from '../../../../hooks'; @@ -38,28 +37,31 @@ export const MysteryBoxExtensionView: FC<{}> = props => if(keyColor === '' && boxColor === '') return null; return ( - + - setIsOpen(value => !value) }> - { LocalizeText('mysterybox.tracker.title') } - { isOpen && } - { !isOpen && } + setIsOpen(value => !value) }> + { LocalizeText('mysterybox.tracker.title') } + { isOpen && <> - { LocalizeText('mysterybox.tracker.description') } - - -
-
-
- - -
-
-
- - + + { LocalizeText('mysterybox.tracker.description') } + + +
+
+
+ + +
+
+
+ + + + { LocalizeText('mysterybox.tracker.link') } + } diff --git a/src/components/room/widgets/room-promotes/RoomPromotesWidgetView.tsx b/src/components/room/widgets/room-promotes/RoomPromotesWidgetView.tsx index bfd66fa17..d95fbc483 100644 --- a/src/components/room/widgets/room-promotes/RoomPromotesWidgetView.tsx +++ b/src/components/room/widgets/room-promotes/RoomPromotesWidgetView.tsx @@ -1,9 +1,8 @@ import { DesktopViewEvent } from '@nitrots/nitro-renderer'; import { FC, useState } from 'react'; -import { FaChevronDown, FaChevronUp } from 'react-icons/fa'; -import { GetSessionDataManager } from '../../../../api'; +import { CreateLinkEvent, GetSessionDataManager, LocalizeText } from '../../../../api'; import { Base, Column, Flex, Text } from '../../../../common'; -import { useMessageEvent, useRoomPromote } from '../../../../hooks'; +import { useMessageEvent, useNavigator, useRoomPromote } from '../../../../hooks'; import { RoomPromoteEditWidgetView, RoomPromoteMyOwnEventWidgetView, RoomPromoteOtherEventWidgetView } from './views'; export const RoomPromotesWidgetView: FC<{}> = props => @@ -11,6 +10,7 @@ export const RoomPromotesWidgetView: FC<{}> = props => const [ isEditingPromote, setIsEditingPromote ] = useState(false); const [ isOpen, setIsOpen ] = useState(true); const { promoteInformation, setPromoteInformation } = useRoomPromote(); + const { navigatorData } = useNavigator(); useMessageEvent(DesktopViewEvent, event => { @@ -21,13 +21,33 @@ export const RoomPromotesWidgetView: FC<{}> = props => return ( <> + { (promoteInformation?.data.adId === -1 && navigatorData?.enteredGuestRoom?.ownerId === GetSessionDataManager().userId) && + + + CreateLinkEvent('catalog/open/room_event') }> + + + + { LocalizeText('roomad.get.event') } + + + + + + + } { promoteInformation.data.adId !== -1 && - + - setIsOpen(value => !value) }> - { promoteInformation.data.eventName } - { isOpen && } - { !isOpen && } + setIsOpen(value => !value) }> + + + + { promoteInformation.data.eventName } + + { isOpen && } + { !isOpen && } + { (isOpen && GetSessionDataManager().userId !== promoteInformation.data.ownerAvatarId) && = pro const updatePromote = () => { + if (!newEventName) return; + SendMessageComposer(new EditEventMessageComposer(eventId, newEventName, newEventDescription)); - setIsEditingPromote(false); } return ( - + setIsEditingPromote(false) } /> - + { LocalizeText('navigator.eventsettings.name') } - setNewEventName(event.target.value) } /> + setNewEventName(event.target.value) } onBlur={ updatePromote } /> + { (newEventName.length < 3) && } - + { 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 index 169cc9b2d..4a9acc565 100644 --- a/src/components/room/widgets/room-promotes/views/RoomPromoteMyOwnEventWidgetView.tsx +++ b/src/components/room/widgets/room-promotes/views/RoomPromoteMyOwnEventWidgetView.tsx @@ -1,6 +1,6 @@ import { FC } from 'react'; import { CreateLinkEvent, LocalizeText } from '../../../../../api'; -import { Button, Flex, Grid, Text } from '../../../../../common'; +import { Flex, Text } from '../../../../../common'; import { useRoomPromote } from '../../../../../hooks'; interface RoomPromoteMyOwnEventWidgetViewProps @@ -22,14 +22,13 @@ export const RoomPromoteMyOwnEventWidgetView: FC - - { eventDescription } + + ') } } /> + + + setIsEditingPromote(true) }>{ LocalizeText('navigator.roominfo.editevent') } + extendPromote() }>{ LocalizeText('roomad.extend.event') } -

- - - - ); }; diff --git a/src/components/room/widgets/room-promotes/views/RoomPromoteOtherEventWidgetView.tsx b/src/components/room/widgets/room-promotes/views/RoomPromoteOtherEventWidgetView.tsx index 5adb51761..30d066c81 100644 --- a/src/components/room/widgets/room-promotes/views/RoomPromoteOtherEventWidgetView.tsx +++ b/src/components/room/widgets/room-promotes/views/RoomPromoteOtherEventWidgetView.tsx @@ -1,6 +1,6 @@ import { FC } from 'react'; import { LocalizeText } from '../../../../../api'; -import { Base, Column, Flex, Text } from '../../../../../common'; +import { Flex, Text } from '../../../../../common'; interface RoomPromoteOtherEventWidgetViewProps { @@ -13,18 +13,14 @@ export const RoomPromoteOtherEventWidgetView: FC - - { eventDescription } + + ') } } /> + + + + { LocalizeText('navigator.eventinprogress') } + -

- - - - { LocalizeText('navigator.eventinprogress') } - -   - - ); }; diff --git a/src/components/room/widgets/room-tools/RoomToolsWidgetView.tsx b/src/components/room/widgets/room-tools/RoomToolsWidgetView.tsx index 6907c7219..0ab4b4d6e 100644 --- a/src/components/room/widgets/room-tools/RoomToolsWidgetView.tsx +++ b/src/components/room/widgets/room-tools/RoomToolsWidgetView.tsx @@ -1,6 +1,6 @@ import { GetGuestRoomResultEvent, NavigatorSearchComposer, RateFlatMessageComposer, RoomDataParser } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; -import { CreateLinkEvent, GetRoomEngine, LocalizeText, SendMessageComposer, SetLocalStorage, TryVisitRoom } from '../../../../api'; +import { CreateLinkEvent, GetLocalStorage, GetRoomEngine, LocalizeText, SendMessageComposer, SetLocalStorage, TryVisitRoom } from '../../../../api'; import { Base, Column, Flex, Text, TransitionAnimation, TransitionAnimationTypes, classNames } from '../../../../common'; import { useMessageEvent, useNavigator, useRoom } from '../../../../hooks'; @@ -64,8 +64,8 @@ export const RoomToolsWidgetView: FC<{}> = props => const onChangeRoomHistory = (roomId: number, roomName: string) => { - let newStorage = JSON.parse(window.localStorage.getItem('nitro.room.history')); - + let newStorage = GetLocalStorage('nitro.room.history') as { roomId: number, roomName: string }[]; + if (newStorage && newStorage.filter( (room: RoomDataParser) => room.roomId === roomId ).length > 0) return; if (newStorage && newStorage.length >= 10) newStorage.shift(); @@ -93,7 +93,7 @@ export const RoomToolsWidgetView: FC<{}> = props => { const handleTabClose = () => { - if (JSON.parse(window.localStorage.getItem('nitro.room.history'))) window.localStorage.removeItem('nitro.room.history'); + if (GetLocalStorage('nitro.room.history')) window.localStorage.removeItem('nitro.room.history'); }; window.addEventListener('beforeunload', handleTabClose); @@ -115,7 +115,7 @@ export const RoomToolsWidgetView: FC<{}> = props => useEffect(() => { - setRoomHistory(JSON.parse(window.localStorage.getItem('nitro.room.history')) ?? []); + setRoomHistory(GetLocalStorage('nitro.room.history') ?? []); }, [ ]); return ( @@ -179,11 +179,11 @@ export const RoomToolsWidgetView: FC<{}> = props => { roomName } - { roomOwner } + { LocalizeText('room.tool.room.owner.prefix') + ' ' + roomOwner } { roomTags && roomTags.length > 0 && - { roomTags.map((tag, index) => handleToolClick('navigator_search_tag', tag) }>#{ tag }) } + { roomTags.map((tag, index) => handleToolClick('navigator_search_tag', tag) }>#{ tag }) } }
diff --git a/src/components/toolbar/ToolbarView.scss b/src/components/toolbar/ToolbarView.scss index ceee9acae..20abed319 100644 --- a/src/components/toolbar/ToolbarView.scss +++ b/src/components/toolbar/ToolbarView.scss @@ -107,6 +107,10 @@ height: 0px; } } + + .margin-friends { + margin-right: 44px; + } } .nitro-toolbar-me { diff --git a/src/components/toolbar/ToolbarView.tsx b/src/components/toolbar/ToolbarView.tsx index c9264428a..f8cc9309e 100644 --- a/src/components/toolbar/ToolbarView.tsx +++ b/src/components/toolbar/ToolbarView.tsx @@ -1,6 +1,6 @@ import { Dispose, DropBounce, EaseOut, JumpBy, Motions, NitroToolbarAnimateIconEvent, PerkAllowancesMessageEvent, PerkEnum, Queue, Wait } from '@nitrots/nitro-renderer'; import { FC, useState } from 'react'; -import { CreateLinkEvent, GetConfiguration, GetSessionDataManager, MessengerIconState, OpenMessengerChat, VisitDesktop } from '../../api'; +import { CreateLinkEvent, GetConfiguration, GetSessionDataManager, LocalizeText, MessengerIconState, OpenMessengerChat, VisitDesktop } from '../../api'; import { Base, Flex, LayoutAvatarImageView, LayoutItemCountView, TransitionAnimation, TransitionAnimationTypes } from '../../common'; import { useAchievements, useFriends, useInventoryUnseenTracker, useMessageEvent, useMessenger, useRoomEngineEvent, useSessionInfo } from '../../hooks'; import { ToolbarMeView } from './ToolbarMeView'; @@ -18,7 +18,7 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props => const { requests = [] } = useFriends(); const { iconState = MessengerIconState.HIDDEN } = useMessenger(); const isMod = GetSessionDataManager().isModerator; - + useMessageEvent(PerkAllowancesMessageEvent, event => { const parser = event.getParser(); @@ -33,7 +33,7 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props => 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'); @@ -70,7 +70,7 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props => - + - - - + <> + { (isVisible) && + + + { LocalizeText('widget.memenu.settings.character') } + onSettings('audio') }>{ LocalizeText('widget.memenu.settings.audio') } + onSettings('other') }>{ LocalizeText('widget.memenu.settings.other') } + + + } + { (selectedSettings) && } + ); } diff --git a/src/components/user-settings/views/UserSettingsWidgetView.tsx b/src/components/user-settings/views/UserSettingsWidgetView.tsx new file mode 100644 index 000000000..79cf22e84 --- /dev/null +++ b/src/components/user-settings/views/UserSettingsWidgetView.tsx @@ -0,0 +1,96 @@ +import { NitroSettingsEvent } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { LocalizeText } from '../../../api'; +import { Button, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; + +interface UserSettingsWidgetViewProps +{ + userSettings: NitroSettingsEvent; + catalogPlaceMultipleObjects: boolean; + catalogSkipPurchaseConfirmation: boolean; + selectedSettings: 'audio' | 'other'; + setCatalogPlaceMultipleObjects: (value: boolean) => void; + setCatalogSkipPurchaseConfirmation: (value: boolean) => void; + saveRangeSlider: (type: string) => void; + processAction: (type: string, value?: any) => void; +} + +export const UserSettingsWidgetView: FC = props => +{ + const { userSettings = null, catalogPlaceMultipleObjects = null, catalogSkipPurchaseConfirmation = null, selectedSettings = null, setCatalogPlaceMultipleObjects = null, setCatalogSkipPurchaseConfirmation = null, saveRangeSlider = null, processAction = null } = props; + + if(!userSettings) return null; + + return ( + + + + { (selectedSettings === 'other') && + + + 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') } + + + } + { (selectedSettings === 'audio') && + + { LocalizeText('widget.memenu.settings.volume') } + + { LocalizeText('widget.memenu.settings.volume.ui') } + + 1) ? 'icon icon-sound-off' : 'icon icon-sound-off-active' } /> + + + processAction('system_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') }/> + + + + + + { LocalizeText('widget.memenu.settings.volume.furni') } + + 1) ? 'icon icon-sound-off' : 'icon icon-sound-off-active' } /> + + + processAction('furni_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') }/> + + + + + + { LocalizeText('widget.memenu.settings.volume.trax') } + + 1) ? 'icon icon-sound-off' : 'icon icon-sound-off-active' } /> + + + processAction('trax_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') }/> + + + + + + } + + + + + + ); +} diff --git a/src/hooks/friends/useFriends.ts b/src/hooks/friends/useFriends.ts index 05437b2a8..c40f22c0e 100644 --- a/src/hooks/friends/useFriends.ts +++ b/src/hooks/friends/useFriends.ts @@ -1,8 +1,9 @@ -import { AcceptFriendMessageComposer, DeclineFriendMessageComposer, FollowFriendMessageComposer, FriendListFragmentEvent, FriendListUpdateComposer, FriendListUpdateEvent, FriendParser, FriendRequestsEvent, GetFriendRequestsComposer, MessengerInitComposer, MessengerInitEvent, NewFriendRequestEvent, RequestFriendComposer, SetRelationshipStatusComposer } from '@nitrots/nitro-renderer'; +import { AcceptFriendMessageComposer, DeclineFriendMessageComposer, FindFriendsProcessResultEvent, FollowFriendMessageComposer, FriendListFragmentEvent, FriendListUpdateComposer, FriendListUpdateEvent, FriendParser, FriendRequestsEvent, GetFriendRequestsComposer, MessengerInitComposer, MessengerInitEvent, NewFriendRequestEvent, RequestFriendComposer, SetRelationshipStatusComposer } from '@nitrots/nitro-renderer'; import { useEffect, useMemo, useState } from 'react'; import { useBetween } from 'use-between'; -import { CloneObject, GetSessionDataManager, MessengerFriend, MessengerRequest, MessengerSettings, SendMessageComposer } from '../../api'; +import { CloneObject, GetSessionDataManager, LocalizeText, MessengerFriend, MessengerRequest, MessengerSettings, SendMessageComposer } from '../../api'; import { useMessageEvent } from '../events'; +import { useNotification } from '../notification'; const useFriendsState = () => { @@ -11,6 +12,7 @@ const useFriendsState = () => const [ sentRequests, setSentRequests ] = useState([]); const [ dismissedRequestIds, setDismissedRequestIds ] = useState([]); const [ settings, setSettings ] = useState(null); + const { simpleAlert } = useNotification(); const onlineFriends = useMemo(() => { @@ -248,6 +250,15 @@ const useFriendsState = () => }); }); + useMessageEvent(FindFriendsProcessResultEvent, event => + { + const parser = event.getParser(); + + if (!parser) return; + + simpleAlert(LocalizeText(!parser.success ? 'friendbar.find.error.text' : 'friendbar.find.success.text'), '', '', '', LocalizeText(!parser.success ? 'friendbar.find.error.title' : 'friendbar.find.success.title')); + }); + useEffect(() => { SendMessageComposer(new MessengerInitComposer()); diff --git a/src/hooks/navigator/useNavigator.ts b/src/hooks/navigator/useNavigator.ts index 18d6ebdbb..459fe17a3 100644 --- a/src/hooks/navigator/useNavigator.ts +++ b/src/hooks/navigator/useNavigator.ts @@ -1,4 +1,4 @@ -import { CanCreateRoomEventEvent, CantConnectMessageParser, DoorbellMessageEvent, FlatAccessDeniedMessageEvent, FlatCreatedEvent, FollowFriendMessageComposer, GenericErrorEvent, GetGuestRoomMessageComposer, GetGuestRoomResultEvent, 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 { CanCreateRoomEventEvent, CantConnectMessageParser, DoorbellMessageEvent, FlatAccessDeniedMessageEvent, FlatCreatedEvent, FollowFriendMessageComposer, GenericErrorEvent, GetGuestRoomMessageComposer, GetGuestRoomResultEvent, GetUserEventCatsMessageComposer, GetUserFlatCatsMessageComposer, HabboWebTools, LegacyExternalInterface, NavigatorCategoryDataParser, NavigatorEventCategoryDataParser, NavigatorHomeRoomEvent, NavigatorMetadataEvent, NavigatorOpenRoomCreatorEvent, NavigatorSavedSearch, NavigatorSearchEvent, NavigatorSearchResultSet, NavigatorSearchesEvent, NavigatorTopLevelContext, RoomDataParser, RoomDoorbellAcceptedEvent, RoomEnterErrorEvent, RoomEntryInfoMessageEvent, RoomForwardEvent, RoomScoreEvent, RoomSettingsUpdatedEvent, SecurityLevel, ThumbnailStatusMessageEvent, UserEventCatsEvent, UserFlatCatsEvent, UserInfoEvent, UserPermissionsEvent } from '@nitrots/nitro-renderer'; import { useState } from 'react'; import { useBetween } from 'use-between'; import { CreateLinkEvent, CreateRoomSession, DoorStateType, GetConfiguration, GetSessionDataManager, INavigatorData, LocalizeText, NotificationAlertType, SendMessageComposer, TryVisitRoom, VisitDesktop } from '../../api'; @@ -13,6 +13,7 @@ const useNavigatorState = () => const [ topLevelContexts, setTopLevelContexts ] = useState(null); const [ doorData, setDoorData ] = useState<{ roomInfo: RoomDataParser, state: number }>({ roomInfo: null, state: DoorStateType.NONE }); const [ searchResult, setSearchResult ] = useState(null); + const [ navigatorSearches, setNavigatorSearches ] = useState(null); const [ navigatorData, setNavigatorData ] = useState({ settingsReceived: false, homeRoomId: 0, @@ -434,9 +435,37 @@ const useNavigatorState = () => VisitDesktop(); }); + useMessageEvent(ThumbnailStatusMessageEvent, event => + { + const parser = event.getParser(); + + if (!parser) return; + + if (parser.ok) + { + simpleAlert(LocalizeText('navigator.thumbnail.camera.success'), NotificationAlertType.DEFAULT, null, null, LocalizeText('navigator.thumbnail.camera.title')); + } + else + { + if (parser.isRenderLimitHit) + { + simpleAlert(LocalizeText('camera.render.count.info'), NotificationAlertType.DEFAULT, null, null, LocalizeText('generic.alert.title')); + } + } + }); + useMessageEvent(NavigatorOpenRoomCreatorEvent, event => CreateLinkEvent('navigator/show')); - return { categories, doorData, setDoorData, topLevelContext, topLevelContexts, searchResult, navigatorData }; + useMessageEvent(NavigatorSearchesEvent, event => + { + const parser = event.getParser(); + + if (!parser) return; + + setNavigatorSearches(parser.searches); + }); + + return { categories, doorData, setDoorData, topLevelContext, topLevelContexts, searchResult, navigatorData, navigatorSearches }; } export const useNavigator = () => useBetween(useNavigatorState); diff --git a/src/hooks/rooms/widgets/furniture/index.ts b/src/hooks/rooms/widgets/furniture/index.ts index 37fa57323..87b3128f8 100644 --- a/src/hooks/rooms/widgets/furniture/index.ts +++ b/src/hooks/rooms/widgets/furniture/index.ts @@ -5,12 +5,14 @@ export * from './useFurnitureCraftingWidget'; export * from './useFurnitureDimmerWidget'; export * from './useFurnitureExchangeWidget'; export * from './useFurnitureExternalImageWidget'; +export * from './useFurnitureFootballGateWidget'; export * from './useFurnitureFriendFurniWidget'; export * from './useFurnitureHighScoreWidget'; export * from './useFurnitureInternalLinkWidget'; export * from './useFurnitureMannequinWidget'; export * from './useFurniturePlaylistEditorWidget'; export * from './useFurniturePresentWidget'; +export * from './useFurnitureRentableSpaceWidget'; export * from './useFurnitureRoomLinkWidget'; export * from './useFurnitureSpamWallPostItWidget'; export * from './useFurnitureStackHeightWidget'; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureFootballGateWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureFootballGateWidget.ts new file mode 100644 index 000000000..3c26750d8 --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureFootballGateWidget.ts @@ -0,0 +1,38 @@ +import { RoomEngineTriggerWidgetEvent } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { GetRoomEngine, IsOwnerOfFurniture } from '../../../../api'; +import { useRoomEngineEvent } from '../../../events'; +import { useFurniRemovedEvent } from '../../engine'; + +const useFurnitureFootballGateWidgetState = () => +{ + const [ objectId, setObjectId ] = useState(-1); + const [ category, setCategory ] = useState(-1); + + const onClose = () => + { + setObjectId(-1); + setCategory(-1); + } + + useRoomEngineEvent(RoomEngineTriggerWidgetEvent.REQUEST_CLOTHING_CHANGE, event => + { + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + + if(!roomObject || !IsOwnerOfFurniture(roomObject)) return; + + setObjectId(event.objectId); + setCategory(event.category); + }); + + useFurniRemovedEvent(((objectId !== -1) && (category !== -1)), event => + { + if((event.id !== objectId) || (event.category !== category)) return; + + onClose(); + }); + + return { objectId, setObjectId, onClose }; +} + +export const useFurnitureFootballGateWidget = useFurnitureFootballGateWidgetState; diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureRentableSpaceWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureRentableSpaceWidget.ts new file mode 100644 index 000000000..906363391 --- /dev/null +++ b/src/hooks/rooms/widgets/furniture/useFurnitureRentableSpaceWidget.ts @@ -0,0 +1,116 @@ +import { RentableSpaceCancelRentMessageComposer, RentableSpaceRentMessageComposer, RentableSpaceStatusMessageEvent, RentableSpaceStatusMessageParser, RoomEngineTriggerWidgetEvent, RoomWidgetEnum } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { GetRoomEngine, GetSessionDataManager, LocalizeText, SendMessageComposer } from '../../../../api'; +import { useMessageEvent, useRoomEngineEvent } from '../../../events'; +import { useNavigator } from '../../../navigator'; +import { useNotification } from '../../../notification'; +import { useFurniRemovedEvent } from '../../engine'; + +const useFurnitureRentableSpaceWidgetState = () => +{ + const [ renter, setRenter ] = useState(null); + const [ itemId, setItemId ] = useState(-1); + const [ category, setCategory ] = useState(-1); + const { navigatorData = null } = useNavigator(); + const { simpleAlert } = useNotification(); + + const isRoomOwner = GetSessionDataManager().userName === navigatorData.enteredGuestRoom?.ownerName; + + const onClose = () => + { + setItemId(-1); + setCategory(-1); + setRenter(null); + } + + const onRent = () => + { + if (!itemId) return; + + SendMessageComposer(new RentableSpaceRentMessageComposer(itemId)); + } + + const onCancelRent = () => + { + if (!itemId) return; + + SendMessageComposer(new RentableSpaceCancelRentMessageComposer(itemId)); + onClose(); + } + + const getRentErrorCode = (code: number) => + { + let errorAlert = ''; + + switch(code) + { + case RentableSpaceStatusMessageParser.SPACE_ALREADY_RENTED: + errorAlert = LocalizeText('rentablespace.widget.error_reason_already_rented'); + break; + case RentableSpaceStatusMessageParser.SPACE_EXTEND_NOT_RENTED: + errorAlert = LocalizeText('rentablespace.widget.error_reason_not_rented'); + break; + case RentableSpaceStatusMessageParser.SPACE_EXTEND_NOT_RENTED_BY_YOU: + errorAlert = LocalizeText('rentablespace.widget.error_reason_not_rented_by_you'); + break; + case RentableSpaceStatusMessageParser.CAN_RENT_ONLY_ONE_SPACE: + errorAlert = LocalizeText('rentablespace.widget.error_reason_can_rent_only_one_space'); + break; + case RentableSpaceStatusMessageParser.NOT_ENOUGH_CREDITS: + errorAlert = LocalizeText('rentablespace.widget.error_reason_not_enough_credits'); + break; + case RentableSpaceStatusMessageParser.NOT_ENOUGH_PIXELS: + errorAlert = LocalizeText('rentablespace.widget.error_reason_not_enough_duckets'); + break; + case RentableSpaceStatusMessageParser.CANT_RENT_NO_PERMISSION: + errorAlert = LocalizeText('rentablespace.widget.error_reason_no_permission'); + break; + case RentableSpaceStatusMessageParser.CANT_RENT_NO_HABBO_CLUB: + errorAlert = LocalizeText('rentablespace.widget.error_reason_no_habboclub'); + break; + case RentableSpaceStatusMessageParser.CANT_RENT: + errorAlert = LocalizeText('rentablespace.widget.error_reason_disabled'); + break; + case RentableSpaceStatusMessageParser.CANT_RENT_GENERIC: + errorAlert = LocalizeText('rentablespace.widget.error_reason_generic'); + break; + } + + onClose(); + return simpleAlert(errorAlert); + } + + useRoomEngineEvent(RoomEngineTriggerWidgetEvent.OPEN_WIDGET, event => + { + if (event.widget !== RoomWidgetEnum.RENTABLESPACE) return; + + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + + if(!roomObject) return; + + setItemId(roomObject.id); + setCategory(event.category); + }); + + useFurniRemovedEvent(((itemId !== -1) && (category !== -1)), event => + { + if((event.id !== itemId) || (event.category !== category)) return; + + onCancelRent(); + }); + + useMessageEvent(RentableSpaceStatusMessageEvent, event => + { + const parser = event.getParser(); + + if (!parser) return; + + if (parser.canRentErrorCode !== 0 && (!isRoomOwner || !GetSessionDataManager().isModerator) || (parser.renterName === '' && parser.canRentErrorCode !== 0)) return getRentErrorCode(parser.canRentErrorCode); + + setRenter(parser); + }); + + return { renter, isRoomOwner, onRent, onCancelRent, onClose }; +} + +export const useFurnitureRentableSpaceWidget = useFurnitureRentableSpaceWidgetState; \ No newline at end of file diff --git a/src/hooks/rooms/widgets/index.ts b/src/hooks/rooms/widgets/index.ts index 99844508f..a4ad26a07 100644 --- a/src/hooks/rooms/widgets/index.ts +++ b/src/hooks/rooms/widgets/index.ts @@ -1,12 +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'; +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';