Compare commits

...

9 Commits

35 changed files with 2398 additions and 404 deletions

View File

@ -1 +1 @@
export const GetUIVersion = () => "2.1.1";
export const GetUIVersion = () => "2.2.0";

View File

@ -5,6 +5,7 @@ export class AvatarInfoName {
public readonly id: number,
public readonly name: string,
public readonly userType: number,
public readonly isFriend: boolean = false
public readonly isFriend: boolean = false,
public readonly relationshipStatus: number = 0
) {}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -104,6 +104,27 @@
// Button Sizes
//
.btn-toggle {
border-image-source: url("@/assets/images/icons/toggle_bg.png");
border-image-slice: 6 6 6 6 fill;
border-image-width: 6px 6px 6px 6px;
cursor: pointer;
.toggle-icon {
background-repeat: no-repeat;
width: 6px;
height: 8px;
&.left {
background-image: url("@/assets/images/icons/toggle_left.png");
}
&.right {
background-image: url("@/assets/images/icons/toggle_right.png");
}
}
}
.btn-lg {
@include button-size($btn-padding-y-lg, $btn-padding-x-lg, $btn-font-size-lg, $btn-border-radius-lg);
}

View File

@ -613,6 +613,42 @@
height: 19px;
}
&.icon-room-history-back-enabled {
background-image: url("@/assets/images/icons/room-history-back-enabled.png");
width: 34px;
height: 36px;
}
&.icon-room-history-back-disabled {
background-image: url("@/assets/images/icons/room-history-back-disabled.png");
width: 34px;
height: 36px;
}
&.icon-room-history-enabled {
background-image: url("@/assets/images/icons/room-history-enabled.png");
width: 33px;
height: 35px;
}
&.icon-room-history-disabled {
background-image: url("@/assets/images/icons/room-history-disabled.png");
width: 33px;
height: 35px;
}
&.icon-room-history-next-enabled {
background-image: url("@/assets/images/icons/room-history-next-enabled.png");
width: 34px;
height: 38px;
}
&.icon-room-history-next-disabled {
background-image: url("@/assets/images/icons/room-history-next-disabled.png");
width: 34px;
height: 38px;
}
&.spin {
animation: rotating 1s linear infinite;
}

View File

@ -4,6 +4,7 @@ import {
GetWardrobeMessageComposer,
IAvatarFigureContainer,
ILinkEventTracker,
SetClothingChangeDataMessageComposer,
UserFigureComposer,
UserWardrobePageEvent,
} from "@nitro/renderer";
@ -47,6 +48,8 @@ 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";
const DEFAULT_MALE_FOOTBALL_GATE: string = "ch-3109-92-1408.lg-3116-82-1408.sh-3115-1408-1408";
const DEFAULT_FEMALE_FOOTBALL_GATE: string = "ch-3112-1408-1408.lg-3116-71-1408.sh-3115-1408-1408";
export const AvatarEditorView: FC<{}> = props => {
const [isVisible, setIsVisible] = useState(false);
@ -62,9 +65,17 @@ export const AvatarEditorView: FC<{}> = props => {
const [lastGender, setLastGender] = useState<string>(null);
const [needsReset, setNeedsReset] = useState(true);
const [isInitalized, setIsInitalized] = useState(false);
const [genderFootballGate, setGenderFootballGate] = useState<string>(null);
const [objectFootballGate, setObjectFootballGate] = useState<number>(null);
const maxWardrobeSlots = useMemo(() => GetConfiguration<number>("avatar.wardrobe.max.slots", 10), []);
const onClose = () => {
setGenderFootballGate(null);
setObjectFootballGate(null);
setIsVisible(false);
};
useMessageEvent<FigureSetIdsMessageEvent>(FigureSetIdsMessageEvent, event => {
const parser = event.getParser();
@ -105,13 +116,18 @@ export const AvatarEditorView: FC<{}> = props => {
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());
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<string, FigureData> = new Map();
@ -167,12 +183,14 @@ export const AvatarEditorView: FC<{}> = props => {
resetCategories();
return;
case AvatarEditorAction.ACTION_SAVE:
SendMessageComposer(new UserFigureComposer(figureData.gender, figureData.getFigureString()));
setIsVisible(false);
!genderFootballGate
? SendMessageComposer(new UserFigureComposer(figureData.gender, figureData.getFigureString()))
: SendMessageComposer(new SetClothingChangeDataMessageComposer(objectFootballGate, genderFootballGate, figureData.getFigureString()));
onClose();
return;
}
},
[figureData, lastFigure, lastGender, figureSetIds, loadAvatarInEditor, resetCategories]
[loadAvatarInEditor, figureData, resetCategories, lastFigure, lastGender, figureSetIds, genderFootballGate, objectFootballGate]
);
const setGender = useCallback(
@ -189,6 +207,9 @@ export const AvatarEditorView: FC<{}> = props => {
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]) {
@ -231,8 +252,8 @@ export const AvatarEditorView: FC<{}> = props => {
useEffect(() => {
if (!categories) return;
selectCategory(AvatarEditorFigureCategory.GENERIC);
}, [categories, selectCategory]);
selectCategory(!genderFootballGate ? AvatarEditorFigureCategory.GENERIC : AvatarEditorFigureCategory.TORSO);
}, [categories, genderFootballGate, selectCategory]);
useEffect(() => {
if (!figureData) return;
@ -271,9 +292,22 @@ export const AvatarEditorView: FC<{}> = props => {
useEffect(() => {
if (!isVisible || !isInitalized || !needsReset) return;
loadAvatarInEditor(GetSessionDataManager().figure, GetSessionDataManager().gender);
loadAvatarInEditor(
!genderFootballGate ? GetSessionDataManager().figure : genderFootballGate === FigureData.MALE ? DEFAULT_MALE_FOOTBALL_GATE : DEFAULT_FEMALE_FOOTBALL_GATE,
!genderFootballGate ? GetSessionDataManager().gender : genderFootballGate
);
setNeedsReset(false);
}, [isVisible, isInitalized, needsReset, loadAvatarInEditor]);
}, [isVisible, isInitalized, needsReset, loadAvatarInEditor, genderFootballGate]);
useEffect(() =>
// This is so when you have the look editor open and you change the mode to Boy or Girl
{
if (!isVisible) return;
return () => {
setNeedsReset(true);
};
}, [isVisible, genderFootballGate]);
useEffect(() => {
if (isVisible) return;
@ -287,7 +321,10 @@ export const AvatarEditorView: FC<{}> = props => {
return (
<NitroCardView uniqueKey="avatar-editor" className="nitro-avatar-editor">
<NitroCardHeaderView headerText={LocalizeText("avatareditor.title")} onCloseClick={event => setIsVisible(false)} />
<NitroCardHeaderView
headerText={!genderFootballGate ? LocalizeText("avatareditor.title") : LocalizeText("widget.furni.clothingchange.editor.title")}
onCloseClick={onClose}
/>
<NitroCardTabsView>
{categories &&
categories.size > 0 &&
@ -300,14 +337,23 @@ export const AvatarEditorView: FC<{}> = props => {
</NitroCardTabsItemView>
);
})}
<NitroCardTabsItemView isActive={isWardrobeVisible} onClick={event => setIsWardrobeVisible(true)}>
{LocalizeText("avatareditor.category.wardrobe")}
</NitroCardTabsItemView>
{!genderFootballGate && (
<NitroCardTabsItemView isActive={isWardrobeVisible} onClick={event => setIsWardrobeVisible(true)}>
{LocalizeText("avatareditor.category.wardrobe")}
</NitroCardTabsItemView>
)}
</NitroCardTabsView>
<NitroCardContentView>
<Grid>
<Column size={9} overflow="hidden">
{activeCategory && !isWardrobeVisible && <AvatarEditorModelView model={activeCategory} gender={figureData.gender} setGender={setGender} />}
{activeCategory && !isWardrobeVisible && (
<AvatarEditorModelView
model={activeCategory}
gender={figureData.gender}
isFromFootballGate={!genderFootballGate ? false : true}
setGender={setGender}
/>
)}
{isWardrobeVisible && (
<AvatarEditorWardrobeView
figureData={figureData}
@ -320,17 +366,19 @@ export const AvatarEditorView: FC<{}> = props => {
<Column size={3} overflow="hidden">
<AvatarEditorFigurePreviewView figureData={figureData} />
<Column grow gap={1}>
<ButtonGroup>
<Button variant="secondary" onClick={event => processAction(AvatarEditorAction.ACTION_RESET)}>
<FaUndo className="fa-icon" />
</Button>
<Button variant="secondary" onClick={event => processAction(AvatarEditorAction.ACTION_CLEAR)}>
<FaTrash className="fa-icon" />
</Button>
<Button variant="secondary" onClick={event => processAction(AvatarEditorAction.ACTION_RANDOMIZE)}>
<FaDice className="fa-icon" />
</Button>
</ButtonGroup>
{!genderFootballGate && (
<ButtonGroup>
<Button variant="secondary" onClick={event => processAction(AvatarEditorAction.ACTION_RESET)}>
<FaUndo className="fa-icon" />
</Button>
<Button variant="secondary" onClick={event => processAction(AvatarEditorAction.ACTION_CLEAR)}>
<FaTrash className="fa-icon" />
</Button>
<Button variant="secondary" onClick={event => processAction(AvatarEditorAction.ACTION_RANDOMIZE)}>
<FaDice className="fa-icon" />
</Button>
</ButtonGroup>
)}
<Button className="w-100" variant="success" onClick={event => processAction(AvatarEditorAction.ACTION_SAVE)}>
{LocalizeText("avatareditor.save")}
</Button>

View File

@ -1,4 +1,4 @@
import {Dispatch, FC, SetStateAction, useCallback, useEffect, useState} from "react";
import {FC, useCallback, useEffect, useState} from "react";
import {CategoryData, FigureData, IAvatarEditorCategoryModel} from "../../../api";
import {Column, Flex, Grid} from "../../../common";
@ -6,14 +6,17 @@ import {AvatarEditorIcon} from "./AvatarEditorIcon";
import {AvatarEditorFigureSetView} from "./figure-set/AvatarEditorFigureSetView";
import {AvatarEditorPaletteSetView} from "./palette-set/AvatarEditorPaletteSetView";
const CATEGORY_FOOTBALL_GATE = ["ch", "cp", "lg", "sh"];
export interface AvatarEditorModelViewProps {
model: IAvatarEditorCategoryModel;
gender: string;
setGender: Dispatch<SetStateAction<string>>;
isFromFootballGate: boolean;
setGender: (gender: string) => void;
}
export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props => {
const {model = null, gender = null, setGender = null} = props;
const {model = null, gender = null, isFromFootballGate = false, setGender = null} = props;
const [activeCategory, setActiveCategory] = useState<CategoryData>(null);
const [maxPaletteCount, setMaxPaletteCount] = useState(1);
@ -70,14 +73,16 @@ export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props => {
const category = model.categories.get(name);
return (
<Flex center pointer key={name} className="category-item" onClick={event => selectCategory(name)}>
<AvatarEditorIcon icon={category.name} selected={activeCategory === category} />
</Flex>
(!isFromFootballGate || (isFromFootballGate && CATEGORY_FOOTBALL_GATE.includes(category.name))) && (
<Flex key={category.name} center pointer className="category-item" onClick={event => selectCategory(name)}>
<AvatarEditorIcon icon={category.name} selected={activeCategory === category} />
</Flex>
)
);
})}
</Column>
<Column size={5} overflow="hidden">
<AvatarEditorFigureSetView model={model} category={activeCategory} setMaxPaletteCount={setMaxPaletteCount} />
<AvatarEditorFigureSetView model={model} category={activeCategory} isFromFootballGate={isFromFootballGate} setMaxPaletteCount={setMaxPaletteCount} />
</Column>
<Column size={5} overflow="hidden">
{maxPaletteCount >= 1 && (

View File

@ -4,14 +4,20 @@ import {AvatarEditorGridPartItem, CategoryData, IAvatarEditorCategoryModel} from
import {AutoGrid} from "../../../../common";
import {AvatarEditorFigureSetItemView} from "./AvatarEditorFigureSetItemView";
const TSHIRT_FOOTBALL_GATE = [3111, 3110, 3109, 3030, 3114, 266, 265, 262, 3113, 3112, 691, 690, 667];
const NUMBER_BEHIND_FOOTBALL_GATE = [3128, 3127, 3126, 3125, 3124, 3123, 3122, 3121, 3120, 3119];
const PANTS_FOOTBALL_GATE = [3116, 281, 275, 715, 700, 696, 3006];
const SHOES_FOOTBALL_GATE = [3115, 3068, 906];
export interface AvatarEditorFigureSetViewProps {
model: IAvatarEditorCategoryModel;
category: CategoryData;
isFromFootballGate: boolean;
setMaxPaletteCount: Dispatch<SetStateAction<number>>;
}
export const AvatarEditorFigureSetView: FC<AvatarEditorFigureSetViewProps> = props => {
const {model = null, category = null, setMaxPaletteCount = null} = props;
const {model = null, category = null, isFromFootballGate = false, setMaxPaletteCount = null} = props;
const elementRef = useRef<HTMLDivElement>(null);
const selectPart = useCallback(
@ -38,7 +44,14 @@ export const AvatarEditorFigureSetView: FC<AvatarEditorFigureSetViewProps> = pro
return (
<AutoGrid innerRef={elementRef} columnCount={3} columnMinHeight={50}>
{category.parts.length > 0 &&
category.parts.map((item, index) => <AvatarEditorFigureSetItemView key={index} partItem={item} onClick={event => selectPart(item)} />)}
category.parts.map(
item =>
(!isFromFootballGate ||
(isFromFootballGate && TSHIRT_FOOTBALL_GATE.includes(item.id)) ||
NUMBER_BEHIND_FOOTBALL_GATE.includes(item.id) ||
PANTS_FOOTBALL_GATE.includes(item.id) ||
SHOES_FOOTBALL_GATE.includes(item.id)) && <AvatarEditorFigureSetItemView key={item.id} partItem={item} onClick={event => selectPart(item)} />
)}
</AutoGrid>
);
};

View File

@ -162,6 +162,49 @@
}
}
}
.friend-bar-item-search {
width: 130px;
margin: 0 3px;
z-index: 0;
position: relative;
text-align: left;
.left-text {
margin-left: 30px;
}
.search-content {
margin-bottom: 105px;
}
&.friend-bar-item-active {
margin-bottom: 21px;
}
.friend-bar-item-head {
&.avatar {
top: -30px;
left: -30px;
}
&.group {
top: -5px;
left: -5px;
}
pointer-events: none;
}
&.friend-bar-search {
.friend-bar-item-head {
top: -3px;
left: 5px;
width: 31px;
height: 34px;
background-image: url("@/assets/images/toolbar/friend-search.png");
}
}
}
}
.nitro-friends-messenger {

View File

@ -1,8 +1,8 @@
import {MouseEventType} from "@nitro/renderer";
import {FindNewFriendsMessageComposer, MouseEventType} from "@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 => {
@ -29,9 +29,19 @@ export const FriendBarItemView: FC<{friend: MessengerFriend}> = props => {
if (!friend) {
return (
<div ref={elementRef} className="btn btn-primary friend-bar-item friend-bar-search">
<div ref={elementRef} className="btn btn-primary friend-bar-item-search friend-bar-search" onClick={event => setVisible(prevValue => !prevValue)}>
<div className="friend-bar-item-head position-absolute" />
<div className="text-truncate">{LocalizeText("friend.bar.find.title")}</div>
<div className="text-truncate left-text">{LocalizeText("friend.bar.find.title")}</div>
{isVisible && (
<>
<div className="search-content">
<div className="bg-white text-black mt-2 mb-2 px-1 py-1">{LocalizeText("friend.bar.find.text")}</div>
<Button variant="white" onClick={() => SendMessageComposer(new FindNewFriendsMessageComposer())}>
{LocalizeText("friend.bar.find.button")}
</Button>
</div>
</>
)}
</div>
);
}

View File

@ -10,6 +10,7 @@
border-bottom-right-radius: $border-radius;
transition: all 0.2s ease;
z-index: 2;
margin-left: -20px;
.list-group-item {
background: transparent;
@ -45,6 +46,15 @@
}
}
.nitro-room-history {
background: rgba($dark, 0.95);
box-shadow: inset 0px 5px lighten(rgba($dark, 0.6), 2.5), inset 0 -4px darken(rgba($dark, 0.6), 4);
transition: all 0.2s ease;
width: 150px;
overflow: hidden;
z-index: 3;
}
.nitro-room-tools-info {
background: rgba($dark, 0.95);
box-shadow: inset 0px 5px lighten(rgba($dark, 0.6), 2.5), inset 0 -4px darken(rgba($dark, 0.6), 4);
@ -53,6 +63,12 @@
}
}
.nitro-room-tools-history {
position: absolute;
left: 0;
margin-left: 2px;
}
.wordquiz-question {
position: absolute;
top: 10px;
@ -99,6 +115,28 @@
height: $nitro-doorbell-height;
}
.toggle-roomtool {
min-height: 95px;
width: 20px;
margin-left: -5px;
padding-left: 10px;
z-index: 3;
}
.room-tool-item {
height: 20px;
cursor: pointer;
}
.margin-icons {
margin-top: -14px;
}
.margin-button-history {
margin-left: 4px;
margin-right: 4px;
}
@import "./avatar-info/AvatarInfoWidgetView";
@import "./chat/ChatWidgetView";
@import "./chat-input/ChatInputView";

View File

@ -1,6 +1,6 @@
import {FC, useMemo} from "react";
import {AvatarInfoName, GetSessionDataManager} from "../../../../../api";
import {AvatarInfoName, GetSessionDataManager, MessengerFriend} from "../../../../../api";
import {ContextMenuView} from "../../context-menu/ContextMenuView";
interface AvatarInfoWidgetNameViewProps {
@ -15,6 +15,17 @@ export const AvatarInfoWidgetNameView: FC<AvatarInfoWidgetNameViewProps> = props
const newClassNames: string[] = ["name-only"];
if (nameInfo.isFriend) newClassNames.push("is-friend");
switch (nameInfo.relationshipStatus) {
case MessengerFriend.RELATIONSHIP_HEART:
newClassNames.push("is-heart");
break;
case MessengerFriend.RELATIONSHIP_SMILE:
newClassNames.push("is-smile");
break;
case MessengerFriend.RELATIONSHIP_BOBBA:
newClassNames.push("is-bobba");
break;
}
return newClassNames;
}, [nameInfo]);
@ -28,6 +39,7 @@ export const AvatarInfoWidgetNameView: FC<AvatarInfoWidgetNameViewProps> = props
classNames={getClassNames}
onClose={onClose}
>
<div className="relation-icon"></div>
<div className="text-shadow">{nameInfo.name}</div>
</ContextMenuView>
);

View File

@ -18,10 +18,33 @@
width: unset;
text-align: center;
font-size: 18px;
display: flex;
align-items: center;
&.is-friend {
background-color: rgba($green, 0.5);
}
&.is-heart {
.relation-icon {
display: block;
background-position: -5px -67px;
}
}
&.is-smile {
.relation-icon {
display: block;
background-position: -57px -67px;
}
}
&.is-bobba {
.relation-icon {
display: block;
background-position: -96px -5px;
}
}
}
&:not(.name-only):not(.menu-hidden) {
@ -125,4 +148,12 @@
}
}
}
.relation-icon {
display: none;
background: url("@/assets/images/friends/friends-spritesheet.png") transparent no-repeat;
width: 16px;
height: 14px;
margin-right: 3px;
}
}

View File

@ -0,0 +1,35 @@
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 (
<NitroCardView className="nitro-football-gate no-resize" theme="primary-slim">
<NitroCardHeaderView headerText={LocalizeText("widget.furni.clothingchange.gender.title")} onCloseClick={onClose} />
<NitroCardContentView className="football-gate-content">
<Flex fullWidth center>
<Column>{LocalizeText("widget.furni.clothingchange.gender.info")}</Column>
</Flex>
<Flex className="mt-4 px-2" justifyContent="between">
<Button className="size-buttons" onClick={e => onGender(FigureData.MALE)}>
{LocalizeText("widget.furni.clothingchange.gender.male")}
</Button>
<Button className="size-buttons" onClick={e => onGender(FigureData.FEMALE)}>
{LocalizeText("widget.furni.clothingchange.gender.female")}
</Button>
</Flex>
</NitroCardContentView>
</NitroCardView>
);
};

View File

@ -0,0 +1,46 @@
import {FriendlyTime} from "@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 (
<NitroCardView className="nitro-guide-tool no-resize" theme="primary-slim">
<NitroCardHeaderView headerText={LocalizeText("rentablespace.widget.title")} onCloseClick={onClose} />
<NitroCardContentView className="text-black">
<Column>
{!renter.rented && (
<>
<Text>{LocalizeText("rentablespace.widget.instructions")}</Text>
<Flex pointer center className="p-2 bg-primary border border-dark rounded text-light h3" onClick={onRent}>
{renter.price + " x"}&nbsp;
<LayoutCurrencyIcon type={-1} className="mt-1" />
&nbsp;
{LocalizeText("catalog.purchase_confirmation.rent")}
</Flex>
</>
)}
{renter.rented && (
<>
<Text bold>{LocalizeText("rentablespace.widget.rented_to_label")}</Text>
<Text italics>{renter.renterName}</Text>
<Text bold>{LocalizeText("rentablespace.widget.expires_label")}</Text>
<Text italics>{FriendlyTime.shortFormat(renter.timeRemaining)}</Text>
{isRoomOwner && (
<Button variant="danger" className="mt-2" onClick={onCancelRent}>
{LocalizeText("rentablespace.widget.cancel_rent")}
</Button>
)}
</>
)}
</Column>
</NitroCardContentView>
</NitroCardView>
);
};

View File

@ -514,3 +514,15 @@
}
}
}
.nitro-football-gate {
width: 300px;
.football-gate-content {
color: black;
.size-buttons {
width: 100px;
}
}
}

View File

@ -7,11 +7,13 @@ 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";
@ -43,6 +45,8 @@ export const FurnitureWidgetsView: FC<{}> = props => {
<FurnitureTrophyView />
<FurnitureContextMenuView />
<FurnitureYoutubeDisplayView />
<FurnitureRentableSpaceView />
<FurnitureFootballGateView />
</Base>
);
};

View File

@ -1,7 +1,7 @@
import {GetGuestRoomResultEvent, NavigatorSearchComposer, RateFlatMessageComposer} from "@nitro/renderer";
import {GetGuestRoomResultEvent, NavigatorSearchComposer, RateFlatMessageComposer, RoomDataParser} from "@nitro/renderer";
import {FC, useEffect, useState} from "react";
import {CreateLinkEvent, GetRoomEngine, LocalizeText, SendMessageComposer} from "../../../../api";
import {CreateLinkEvent, GetRoomEngine, LocalizeText, SendMessageComposer, SetLocalStorage, TryVisitRoom} from "../../../../api";
import {Base, Column, Flex, Text, TransitionAnimation, TransitionAnimationTypes, classNames} from "../../../../common";
import {useMessageEvent, useNavigator, useRoom} from "../../../../hooks";
@ -11,6 +11,9 @@ export const RoomToolsWidgetView: FC<{}> = props => {
const [roomOwner, setRoomOwner] = useState<string>(null);
const [roomTags, setRoomTags] = useState<string[]>(null);
const [isOpen, setIsOpen] = useState<boolean>(false);
const [isOpenHistory, setIsOpenHistory] = useState<boolean>(false);
const [show, setShow] = useState(true);
const [roomHistory, setRoomHistory] = useState<{roomId: number; roomName: string}[]>([]);
const {navigatorData = null} = useNavigator();
const {roomSession = null} = useRoom();
@ -44,9 +47,28 @@ export const RoomToolsWidgetView: FC<{}> = props => {
CreateLinkEvent(`navigator/search/${value}`);
SendMessageComposer(new NavigatorSearchComposer("hotel_view", `tag:${value}`));
return;
case "room_history":
if (roomHistory.length > 0) setIsOpenHistory(prevValue => !prevValue);
return;
case "room_history_back":
TryVisitRoom(roomHistory[roomHistory.findIndex(room => room.roomId === navigatorData.currentRoomId) - 1].roomId);
return;
case "room_history_next":
TryVisitRoom(roomHistory[roomHistory.findIndex(room => room.roomId === navigatorData.currentRoomId) + 1].roomId);
return;
}
};
const onChangeRoomHistory = (roomId: number, roomName: string) => {
let newStorage = JSON.parse(window.localStorage.getItem("nitro.room.history"));
if (newStorage && newStorage.filter((room: RoomDataParser) => room.roomId === roomId).length > 0) return;
if (newStorage && newStorage.length >= 10) newStorage.shift();
const newData = !newStorage ? [{roomId: roomId, roomName: roomName}] : [...newStorage, {roomId: roomId, roomName: roomName}];
setRoomHistory(newData);
return SetLocalStorage("nitro.room.history", newData);
};
useMessageEvent<GetGuestRoomResultEvent>(GetGuestRoomResultEvent, event => {
const parser = event.getParser();
@ -55,63 +77,184 @@ export const RoomToolsWidgetView: FC<{}> = props => {
if (roomName !== parser.data.roomName) setRoomName(parser.data.roomName);
if (roomOwner !== parser.data.ownerName) setRoomOwner(parser.data.ownerName);
if (roomTags !== parser.data.tags) setRoomTags(parser.data.tags);
onChangeRoomHistory(parser.data.roomId, parser.data.roomName);
});
useEffect(() => {
const handleTabClose = () => {
if (JSON.parse(window.localStorage.getItem("nitro.room.history"))) window.localStorage.removeItem("nitro.room.history");
};
window.addEventListener("beforeunload", handleTabClose);
return () => {
window.removeEventListener("beforeunload", handleTabClose);
};
}, []);
useEffect(() => {
setIsOpen(true);
const timeout = setTimeout(() => setIsOpen(false), 5000);
return () => clearTimeout(timeout);
}, [roomName, roomOwner, roomTags]);
}, [roomName, roomOwner, roomTags, show]);
useEffect(() => {
setRoomHistory(JSON.parse(window.localStorage.getItem("nitro.room.history")) ?? []);
}, []);
return (
<Flex className="nitro-room-tools-container" gap={2}>
<Column center className="nitro-room-tools p-2">
<Base pointer title={LocalizeText("room.settings.button.text")} className="icon icon-cog" onClick={() => handleToolClick("settings")} />
<Base
pointer
title={LocalizeText("room.zoom.button.text")}
onClick={() => handleToolClick("zoom")}
className={classNames("icon", !isZoomedIn && "icon-zoom-less", isZoomedIn && "icon-zoom-more")}
/>
<Base pointer title={LocalizeText("room.chathistory.button.text")} onClick={() => handleToolClick("chat_history")} className="icon icon-chat-history" />
{navigatorData.canRate && (
<Base pointer title={LocalizeText("room.like.button.text")} onClick={() => handleToolClick("like_room")} className="icon icon-like-room" />
)}
</Column>
<Column justifyContent="center">
<TransitionAnimation type={TransitionAnimationTypes.SLIDE_LEFT} inProp={isOpen} timeout={300}>
<Column center>
<Column className="nitro-room-tools-info rounded py-2 px-3">
<Column gap={1}>
<Text wrap variant="white" fontSize={4}>
{roomName}
</Text>
<Text variant="muted" fontSize={5}>
{roomOwner}
</Text>
<div className="btn-toggle toggle-roomtool d-flex align-items-center" onClick={() => setShow(!show)}>
<div className={"toggle-icon " + (!show ? "right" : "left")} />
</div>
{show && (
<>
<Column gap={0} center className="nitro-room-tools p-3 px-3">
<Flex>
<Column center className="margin-icons p-2">
<Base pointer title={LocalizeText("room.settings.button.text")} className="icon icon-cog" onClick={() => handleToolClick("settings")} />
<Base
pointer
title={LocalizeText("room.zoom.button.text")}
onClick={() => handleToolClick("zoom")}
className={classNames("icon", !isZoomedIn && "icon-zoom-less", isZoomedIn && "icon-zoom-more")}
/>
<Base
pointer
title={LocalizeText("room.chathistory.button.text")}
onClick={() => handleToolClick("chat_history")}
className="icon icon-chat-history"
/>
{navigatorData.canRate && (
<Base pointer title={LocalizeText("room.like.button.text")} onClick={() => handleToolClick("like_room")} className="icon icon-like-room" />
)}
<Base pointer onClick={() => handleToolClick("toggle_room_link")} className="icon icon-room-link" />
</Column>
{roomTags && roomTags.length > 0 && (
<Flex gap={2}>
{roomTags.map((tag, index) => (
<Text
key={index}
small
pointer
variant="white"
className="rounded bg-primary p-1"
onClick={() => handleToolClick("navigator_search_tag", tag)}
>
#{tag}
</Text>
))}
<Column>
<Flex className="w-100 room-tool-item">
<Text variant="muted" underline small onClick={() => handleToolClick("settings")}>
{LocalizeText("room.settings.button.text")}
</Text>
</Flex>
)}
</Column>
<Flex className="w-100 room-tool-item">
<Text variant="muted" underline small onClick={() => handleToolClick("zoom")}>
{LocalizeText("room.zoom.button.text")}
</Text>
</Flex>
<Flex className="w-100 room-tool-item">
<Text variant="muted" underline small onClick={() => handleToolClick("chat_history")}>
{LocalizeText("room.chathistory.button.text")}
</Text>
</Flex>
{navigatorData.canRate && (
<Flex className="w-100 room-tool-item">
<Text variant="muted" underline small onClick={() => handleToolClick("like_room")}>
{LocalizeText("room.like.button.text")}
</Text>
</Flex>
)}
<Flex className="w-100 room-tool-item">
<Text variant="muted" underline small onClick={() => handleToolClick("toggle_room_link")}>
{LocalizeText("navigator.embed.caption")}
</Text>
</Flex>
</Column>
</Flex>
<Flex justifyContent="center">
<Base
pointer={roomHistory.length > 1 && roomHistory[0]?.roomId !== navigatorData.currentRoomId}
title={LocalizeText("room.history.button.back.tooltip")}
className={`icon ${
roomHistory?.length === 0 || roomHistory[0]?.roomId === navigatorData.currentRoomId
? "icon-room-history-back-disabled"
: "icon-room-history-back-enabled"
}`}
onClick={() =>
roomHistory?.length === 0 || roomHistory[0]?.roomId === navigatorData.currentRoomId ? null : handleToolClick("room_history_back")
}
/>
<Base
pointer={roomHistory?.length > 0}
title={LocalizeText("room.history.button.tooltip")}
className={`icon ${roomHistory?.length === 0 ? "icon-room-history-disabled" : "icon-room-history-enabled"} margin-button-history`}
onClick={() => (roomHistory?.length === 0 ? null : handleToolClick("room_history"))}
/>
<Base
pointer={roomHistory.length > 1 && roomHistory[roomHistory.length - 1]?.roomId !== navigatorData.currentRoomId}
title={LocalizeText("room.history.button.forward.tooltip")}
className={`icon ${
roomHistory?.length === 0 || roomHistory[roomHistory.length - 1]?.roomId === navigatorData.currentRoomId
? "icon-room-history-next-disabled"
: "icon-room-history-next-enabled"
}`}
onClick={() =>
roomHistory?.length === 0 || roomHistory[roomHistory.length - 1]?.roomId === navigatorData.currentRoomId
? null
: handleToolClick("room_history_next")
}
/>
</Flex>
</Column>
</TransitionAnimation>
</Column>
<Flex className="nitro-room-tools-history" style={{bottom: !navigatorData.canRate ? "180px" : "210px"}}>
<TransitionAnimation type={TransitionAnimationTypes.SLIDE_LEFT} inProp={isOpenHistory}>
<Column center>
<Column className="nitro-room-history rounded py-2 px-3">
<Column gap={1}>
{roomHistory.length > 0 &&
roomHistory.map(history => {
return (
<Text
key={history.roomId}
bold={history.roomId === navigatorData.currentRoomId}
variant={history.roomId === navigatorData.currentRoomId ? "white" : "muted"}
pointer
onClick={() => TryVisitRoom(history.roomId)}
>
{history.roomName}
</Text>
);
})}
</Column>
</Column>
</Column>
</TransitionAnimation>
</Flex>
<Column justifyContent="center">
<TransitionAnimation type={TransitionAnimationTypes.SLIDE_LEFT} inProp={isOpen} timeout={300}>
<Column center>
<Column className="nitro-room-tools-info rounded py-2 px-3">
<Column gap={1}>
<Text wrap variant="white" fontSize={4}>
{roomName}
</Text>
<Text variant="muted" fontSize={5}>
{roomOwner}
</Text>
</Column>
{roomTags && roomTags.length > 0 && (
<Flex gap={2}>
{roomTags.map((tag, index) => (
<Text
key={index}
small
pointer
variant="white"
className="rounded bg-primary p-1"
onClick={() => handleToolClick("navigator_search_tag", tag)}
>
#{tag}
</Text>
))}
</Flex>
)}
</Column>
</Column>
</TransitionAnimation>
</Column>
</>
)}
</Flex>
);
};

View File

@ -1,6 +1,7 @@
import {
AcceptFriendMessageComposer,
DeclineFriendMessageComposer,
FindFriendsProcessResultEvent,
FollowFriendMessageComposer,
FriendListFragmentEvent,
FriendListUpdateComposer,
@ -17,8 +18,9 @@ import {
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 = () => {
const [friends, setFriends] = useState<MessengerFriend[]>([]);
@ -26,6 +28,7 @@ const useFriendsState = () => {
const [sentRequests, setSentRequests] = useState<number[]>([]);
const [dismissedRequestIds, setDismissedRequestIds] = useState<number[]>([]);
const [settings, setSettings] = useState<MessengerSettings>(null);
const {simpleAlert} = useNotification();
const onlineFriends = useMemo(() => {
const onlineFriends = friends.filter(friend => friend.online);
@ -223,6 +226,20 @@ const useFriendsState = () => {
});
});
useMessageEvent<FindFriendsProcessResultEvent>(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());

View File

@ -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";

View File

@ -0,0 +1,35 @@
import {RoomEngineTriggerWidgetEvent} from "@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<number>(-1);
const [category, setCategory] = useState<number>(-1);
const onClose = () => {
setObjectId(-1);
setCategory(-1);
};
useRoomEngineEvent<RoomEngineTriggerWidgetEvent>(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;

View File

@ -0,0 +1,119 @@
import {
RentableSpaceCancelRentMessageComposer,
RentableSpaceRentMessageComposer,
RentableSpaceStatusMessageEvent,
RentableSpaceStatusMessageParser,
RoomEngineTriggerWidgetEvent,
RoomWidgetEnum,
} from "@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<RentableSpaceStatusMessageParser>(null);
const [itemId, setItemId] = useState<number>(-1);
const [category, setCategory] = useState<number>(-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>(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>(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;

View File

@ -132,8 +132,11 @@ const useAvatarInfoWidgetState = () => {
event.addedUsers.forEach(user => {
if (user.webID === GetSessionDataManager().userId) return;
if (friends.find(friend => friend.id === user.webID)) {
addedNameBubbles.push(new AvatarInfoName(user.roomIndex, RoomObjectCategory.UNIT, user.webID, user.name, user.type, true));
const addedNameBubblesUserFriend = friends.find(friend => friend.id === user.webID);
if (addedNameBubblesUserFriend) {
addedNameBubbles.push(
new AvatarInfoName(user.roomIndex, RoomObjectCategory.UNIT, user.webID, user.name, user.type, true, addedNameBubblesUserFriend.relationshipStatus)
);
}
});

View File

@ -1,6 +1,17 @@
import {IMessageDataWrapper, IMessageParser} from "../../../../../../api";
export class RentableSpaceStatusMessageParser implements IMessageParser {
public static readonly SPACE_ALREADY_RENTED = 100;
public static readonly SPACE_EXTEND_NOT_RENTED = 101;
public static readonly SPACE_EXTEND_NOT_RENTED_BY_YOU = 102;
public static readonly CAN_RENT_ONLY_ONE_SPACE = 103;
public static readonly NOT_ENOUGH_CREDITS = 200;
public static readonly NOT_ENOUGH_PIXELS = 201;
public static readonly CANT_RENT_NO_PERMISSION = 202;
public static readonly CANT_RENT_NO_HABBO_CLUB = 203;
public static readonly CANT_RENT = 300;
public static readonly CANT_RENT_GENERIC = 400;
private _rented: boolean;
private _renterId: number;
private _renterName: string;

View File

@ -1,4 +1,4 @@
import {AdvancedMap, RoomObjectVariable, RoomWidgetEnum} from "../../../../../api";
import {RoomObjectVariable, RoomWidgetEnum} from "../../../../../api";
import {RoomObjectDataRequestEvent} from "../../../../../events";
import {FurnitureLogic} from "./FurnitureLogic";
@ -17,7 +17,7 @@ export class FurnitureRentableSpaceLogic extends FurnitureLogic {
this.eventDispatcher.dispatchEvent(new RoomObjectDataRequestEvent(RoomObjectDataRequestEvent.RODRE_CURRENT_USER_ID, this.object));
}
const renterId = this.object.model.getValue<AdvancedMap<string, string>>(RoomObjectVariable.FURNITURE_DATA).getValue("renterId");
const renterId = (this.object.model.getValue<string>(RoomObjectVariable.FURNITURE_DATA) as any)["renterId"] as string;
const userId = this.object.model.getValue<number>(RoomObjectVariable.SESSION_CURRENT_USER_ID);
if (renterId) {

1913
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -37,7 +37,7 @@
"@pixi/ticker": "~6.5.0",
"@pixi/tilemap": "^3.2.2",
"@pixi/utils": "~6.5.0",
"@swc/helpers": "~0.4.11",
"@swc/helpers": "~0.5.3",
"@tanstack/react-virtual": "^3.0.0-alpha.0",
"gifuct-js": "^2.1.2",
"howler": "^2.2.3",
@ -59,8 +59,8 @@
"@nrwl/react": "15.8.6",
"@nrwl/vite": "15.8.6",
"@nrwl/workspace": "15.8.6",
"@swc/cli": "~0.1.55",
"@swc/core": "^1.2.173",
"@swc/cli": "~0.1.62",
"@swc/core": "^1.3.94",
"@testing-library/react": "14.0.0",
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
"@types/howler": "^2.2.7",
@ -70,7 +70,7 @@
"@types/react-dom": "18.0.11",
"@typescript-eslint/eslint-plugin": "^5.36.1",
"@typescript-eslint/parser": "^5.36.1",
"@vitejs/plugin-react-swc": "^3.2.0",
"@vitejs/plugin-react-swc": "^3.4.0",
"@vitest/coverage-c8": "~0.25.8",
"@vitest/ui": "^0.25.8",
"cypress": "^12.2.0",
@ -87,9 +87,9 @@
"react-test-renderer": "18.2.0",
"sass": "^1.55.0",
"typescript": "~4.9.5",
"vite": "^4.0.1",
"vite": "^4.5.0",
"vite-plugin-eslint": "^1.8.1",
"vite-tsconfig-paths": "^4.0.2",
"vitest": "^0.25.8"
"vite-tsconfig-paths": "^4.2.1",
"vitest": "^0.34.6"
}
}