Compare commits
9 Commits
7a489dcee9
...
f389dbaa71
Author | SHA1 | Date |
---|---|---|
Niklas | f389dbaa71 | |
Niklas | 3b171435c1 | |
Niklas | c6cf366aa4 | |
Niklas | e160833554 | |
Niklas | 983e77a0bc | |
Niklas | 6c48eea770 | |
Niklas | 6710132cc0 | |
Niklas | 267249ab7b | |
Niklas | a6da048ccc |
|
@ -1 +1 @@
|
|||
export const GetUIVersion = () => "2.1.1";
|
||||
export const GetUIVersion = () => "2.2.0";
|
||||
|
|
|
@ -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
|
||||
) {}
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 267 B |
After Width: | Height: | Size: 280 B |
After Width: | Height: | Size: 393 B |
After Width: | Height: | Size: 402 B |
After Width: | Height: | Size: 260 B |
After Width: | Height: | Size: 267 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.6 KiB |
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 && (
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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"}
|
||||
<LayoutCurrencyIcon type={-1} className="mt-1" />
|
||||
|
||||
{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>
|
||||
);
|
||||
};
|
|
@ -514,3 +514,15 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-football-gate {
|
||||
width: 300px;
|
||||
|
||||
.football-gate-content {
|
||||
color: black;
|
||||
|
||||
.size-buttons {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
14
package.json
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|