More updates to V2
6
.editorconfig
Normal file
@ -0,0 +1,6 @@
|
||||
[*]
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 4
|
@ -105,6 +105,17 @@
|
||||
{
|
||||
"prevent": true
|
||||
}
|
||||
],
|
||||
"react/jsx-sort-props": [
|
||||
"error",
|
||||
{
|
||||
"callbacksLast": true,
|
||||
"shorthandFirst": true,
|
||||
"shorthandLast": false,
|
||||
"ignoreCase": true,
|
||||
"noSortAlphabetically": false,
|
||||
"reservedFirst": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,12 @@
|
||||
- Open `public/ui-config.json`
|
||||
- Update `camera.url, thumbnails.url, url.prefix, habbopages.url`
|
||||
- You can override any variable by passing it to `NitroConfig` in the index.html
|
||||
|
||||
nitro-renderer>yarn install
|
||||
\nitro-renderer>yarn link
|
||||
\nitro-react>yarn install
|
||||
yarn link "@nitrots/nitro-renderer"
|
||||
yarn start
|
||||
|
||||
## Usage
|
||||
|
||||
|
49
css-utils/CSSColorUtils.js
Normal file
@ -0,0 +1,49 @@
|
||||
const lightenHexColor = (hex, percent) =>
|
||||
{
|
||||
// Remove the hash symbol if present
|
||||
hex = hex.replace(/^#/, '');
|
||||
|
||||
// Convert hex to RGB
|
||||
let r = parseInt(hex.substring(0, 2), 16);
|
||||
let g = parseInt(hex.substring(2, 4), 16);
|
||||
let b = parseInt(hex.substring(4, 6), 16);
|
||||
|
||||
// Adjust RGB values
|
||||
r = Math.round(Math.min(255, r + 255 * percent));
|
||||
g = Math.round(Math.min(255, g + 255 * percent));
|
||||
b = Math.round(Math.min(255, b + 255 * percent));
|
||||
|
||||
// Convert RGB back to hex
|
||||
const result = ((r << 16) | (g << 8) | b).toString(16);
|
||||
|
||||
// Make sure result has 6 digits
|
||||
return '#' + result.padStart(6, '0');
|
||||
}
|
||||
|
||||
const generateShades = (colors) =>
|
||||
{
|
||||
for (let color in colors)
|
||||
{
|
||||
let hex = colors[color]
|
||||
let extended = {}
|
||||
const shades = [ 50, 100, 200, 300, 400, 500, 600, 700, 900, 950 ];
|
||||
|
||||
for (let i = 0; i < shades.length; i++)
|
||||
{
|
||||
let shade = shades[i];
|
||||
extended[shade] = lightenHexColor(hex, shades[(shades.length - 1 - i) ] / 1000);
|
||||
}
|
||||
|
||||
colors[color] = {
|
||||
DEFAULT: hex,
|
||||
...extended
|
||||
}
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateShades,
|
||||
lightenHexColor
|
||||
}
|
15
package.json
@ -10,7 +10,10 @@
|
||||
"eslint": "eslint src --ext .ts,.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-virtual": "3.0.0-beta.60",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@tanstack/react-virtual": "3.2.0",
|
||||
"dompurify": "^3.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-bootstrap": "^2.2.2",
|
||||
"react-dom": "^18.2.0",
|
||||
@ -20,6 +23,7 @@
|
||||
"use-between": "^1.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@types/node": "^20.11.30",
|
||||
"@types/react": "^18.2.67",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
@ -27,13 +31,20 @@
|
||||
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
||||
"@typescript-eslint/parser": "^7.3.1",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||
"eslint-plugin-react": "^7.34.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss-nested": "^6.0.1",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-tailwindcss": "^0.5.13",
|
||||
"sass": "^1.72.0",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^5.4.2",
|
||||
"vite": "^5.1.6"
|
||||
"vite": "^5.1.6",
|
||||
"vite-tsconfig-paths": "^4.3.2"
|
||||
}
|
||||
}
|
||||
|
8
postcss.config.js
Normal file
@ -0,0 +1,8 @@
|
||||
/** @type {import("postcss-load-config").Config} */
|
||||
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
7
prettier.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
/** @type {import("prettier").Config} */
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
'prettier-plugin-tailwindcss'
|
||||
]
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"socket.url": "ws://localhost:2096",
|
||||
"asset.url": "http://localhost/gamedata",
|
||||
"image.library.url": "http://localhost/gamedata/c_images/",
|
||||
"hof.furni.url": "http://localhost/gamedata",
|
||||
"asset.url": "http://192.168.0.8/gamedata",
|
||||
"image.library.url": "http://192.168.0.8/gamedata/c_images/",
|
||||
"hof.furni.url": "http://192.168.0.8/gamedata",
|
||||
"images.url": "${asset.url}/images",
|
||||
"gamedata.url": "${asset.url}",
|
||||
"sounds.url": "${asset.url}/sounds/%sample%.mp3",
|
||||
@ -23,12 +23,12 @@
|
||||
"badge.asset.url": "${image.library.url}album1584/%badgename%.gif",
|
||||
"furni.rotation.bounce.steps": 20,
|
||||
"furni.rotation.bounce.height": 0.0625,
|
||||
"enable.avatar.arrow": false,
|
||||
"system.log.debug": false,
|
||||
"system.log.warn": false,
|
||||
"system.log.error": false,
|
||||
"enable.avatar.arrow": true,
|
||||
"system.log.debug": true,
|
||||
"system.log.warn": true,
|
||||
"system.log.error": true,
|
||||
"system.log.events": false,
|
||||
"system.log.packets": false,
|
||||
"system.log.packets": true,
|
||||
"system.fps.animation": 24,
|
||||
"system.fps.max": 60,
|
||||
"system.pong.manually": true,
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"image.library.notifications.url": "${image.library.url}notifications/%image%.png",
|
||||
"achievements.images.url": "${image.library.url}Quests/%image%.png",
|
||||
"camera.url": "http://localhost/camera/photo",
|
||||
"camera.url": "http://192.168.0.8/camera/photo",
|
||||
"thumbnails.url": "/camera/photo/thumb/%thumbnail%.png",
|
||||
"url.prefix": "",
|
||||
"habbopages.url": "${url.prefix}/",
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/images/chat/chatbubbles/bubble_39.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/images/chat/chatbubbles/bubble_39_pointer.png
Normal file
After Width: | Height: | Size: 105 B |
BIN
src/assets/images/chat/chatbubbles/bubble_40.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/images/chat/chatbubbles/bubble_40_pointer.png
Normal file
After Width: | Height: | Size: 105 B |
BIN
src/assets/images/chat/chatbubbles/bubble_41.png
Normal file
After Width: | Height: | Size: 359 B |
BIN
src/assets/images/chat/chatbubbles/bubble_41_pointer.png
Normal file
After Width: | Height: | Size: 125 B |
BIN
src/assets/images/chat/chatbubbles/bubble_42.png
Normal file
After Width: | Height: | Size: 364 B |
BIN
src/assets/images/chat/chatbubbles/bubble_42_pointer.png
Normal file
After Width: | Height: | Size: 125 B |
BIN
src/assets/images/chat/chatbubbles/bubble_43.png
Normal file
After Width: | Height: | Size: 358 B |
BIN
src/assets/images/chat/chatbubbles/bubble_43_pointer.png
Normal file
After Width: | Height: | Size: 125 B |
BIN
src/assets/images/chat/chatbubbles/bubble_44.png
Normal file
After Width: | Height: | Size: 357 B |
BIN
src/assets/images/chat/chatbubbles/bubble_44_pointer.png
Normal file
After Width: | Height: | Size: 125 B |
BIN
src/assets/images/chat/chatbubbles/bubble_45.png
Normal file
After Width: | Height: | Size: 342 B |
BIN
src/assets/images/chat/chatbubbles/bubble_45_pointer.png
Normal file
After Width: | Height: | Size: 125 B |
BIN
src/assets/images/chat/chatbubbles/bubble_46.png
Normal file
After Width: | Height: | Size: 343 B |
BIN
src/assets/images/chat/chatbubbles/bubble_46_pointer.png
Normal file
After Width: | Height: | Size: 125 B |
BIN
src/assets/images/chat/chatbubbles/bubble_47.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/images/chat/chatbubbles/bubble_47_pointer.png
Normal file
After Width: | Height: | Size: 125 B |
BIN
src/assets/images/chat/chatbubbles/bubble_48.png
Normal file
After Width: | Height: | Size: 361 B |
BIN
src/assets/images/chat/chatbubbles/bubble_48_pointer.png
Normal file
After Width: | Height: | Size: 125 B |
BIN
src/assets/images/chat/chatbubbles/bubble_49.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src/assets/images/chat/chatbubbles/bubble_49_pointer.png
Normal file
After Width: | Height: | Size: 125 B |
BIN
src/assets/images/chat/chatbubbles/bubble_50.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src/assets/images/chat/chatbubbles/bubble_50_pointer.png
Normal file
After Width: | Height: | Size: 125 B |
BIN
src/assets/images/chat/chatbubbles/bubble_51.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
src/assets/images/chat/chatbubbles/bubble_51_pointer.png
Normal file
After Width: | Height: | Size: 125 B |
BIN
src/assets/images/chat/chatbubbles/bubble_52.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/images/chat/chatbubbles/bubble_52_pointer.png
Normal file
After Width: | Height: | Size: 125 B |
BIN
src/assets/images/chat/chatbubbles/bubble_53.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src/assets/images/chat/chatbubbles/bubble_53_pointer.png
Normal file
After Width: | Height: | Size: 125 B |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
BIN
src/assets/images/notifications/coolui.png
Normal file
After Width: | Height: | Size: 18 KiB |
@ -114,3 +114,25 @@
|
||||
.btn-sm {
|
||||
@include button-size($btn-padding-y-sm, $btn-padding-x-sm, $btn-font-size-sm, $btn-border-radius-sm);
|
||||
}
|
||||
|
||||
// Tool-Bar button left side
|
||||
.btn-toggle {
|
||||
border-image-source: url("@/assets/images/buttons/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/buttons/toggle_left.png");
|
||||
}
|
||||
|
||||
&.right {
|
||||
background-image: url("@/assets/images/buttons/toggle_right.png");
|
||||
}
|
||||
}
|
||||
}
|
33
src/common/layout/LayoutNotificationCredits.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { NotificationAlertType } from '../../api';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroCardViewProps } from '../card';
|
||||
|
||||
export interface LayoutNotificationCreditsProps extends NitroCardViewProps
|
||||
{
|
||||
title?: string;
|
||||
type?: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const LayoutNotificationCredits : FC<LayoutNotificationCreditsProps> = props =>
|
||||
{
|
||||
const { title = '', onClose = null, classNames = [], children = null,type = NotificationAlertType.DEFAULT, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = ['nitro-alert', 'nitro-alert-credits'];
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ classNames, type ]);
|
||||
|
||||
return (
|
||||
<NitroCardView classNames={ getClassNames } theme="primary" { ...rest }>
|
||||
<NitroCardHeaderView headerText={ title } onCloseClick={ onClose } />
|
||||
<NitroCardContentView grow justifyContent="between" overflow="hidden" className="text-black" gap={ 0 }>
|
||||
{ children }
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
}
|
@ -12,6 +12,7 @@ export * from './LayoutItemCountView';
|
||||
export * from './LayoutLoadingSpinnerView';
|
||||
export * from './LayoutMiniCameraView';
|
||||
export * from './LayoutNotificationAlertView';
|
||||
export * from './LayoutNotificationCredits';
|
||||
export * from './LayoutNotificationBubbleView';
|
||||
export * from './LayoutPetImageView';
|
||||
export * from './LayoutProgressBar';
|
||||
|
@ -1,336 +0,0 @@
|
||||
.nitro-avatar-editor-spritesheet {
|
||||
background: url('@/assets/images/avatareditor/avatar-editor-spritesheet.png') transparent no-repeat;
|
||||
|
||||
&.arrow-left-icon {
|
||||
width: 28px;
|
||||
height: 21px;
|
||||
background-position: -226px -131px;
|
||||
}
|
||||
|
||||
&.arrow-right-icon {
|
||||
width: 28px;
|
||||
height: 21px;
|
||||
background-position: -226px -162px;
|
||||
}
|
||||
|
||||
&.ca-icon {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
background-position: -226px -61px;
|
||||
|
||||
&.selected {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
background-position: -226px -96px;
|
||||
}
|
||||
}
|
||||
|
||||
&.cc-icon {
|
||||
width: 31px;
|
||||
height: 29px;
|
||||
background-position: -145px -5px;
|
||||
|
||||
&.selected {
|
||||
width: 31px;
|
||||
height: 29px;
|
||||
background-position: -145px -44px;
|
||||
}
|
||||
}
|
||||
|
||||
&.ch-icon {
|
||||
width: 29px;
|
||||
height: 24px;
|
||||
background-position: -186px -39px;
|
||||
|
||||
&.selected {
|
||||
width: 29px;
|
||||
height: 24px;
|
||||
background-position: -186px -73px;
|
||||
}
|
||||
}
|
||||
|
||||
&.clear-icon {
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
background-position: -145px -157px;
|
||||
}
|
||||
|
||||
&.cp-icon {
|
||||
width: 30px;
|
||||
height: 24px;
|
||||
background-position: -145px -264px;
|
||||
|
||||
&.selected {
|
||||
width: 30px;
|
||||
height: 24px;
|
||||
background-position: -186px -5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.ea-icon {
|
||||
width: 35px;
|
||||
height: 16px;
|
||||
background-position: -226px -193px;
|
||||
|
||||
&.selected {
|
||||
width: 35px;
|
||||
height: 16px;
|
||||
background-position: -226px -219px;
|
||||
}
|
||||
}
|
||||
|
||||
&.fa-icon {
|
||||
width: 27px;
|
||||
height: 20px;
|
||||
background-position: -186px -137px;
|
||||
|
||||
&.selected {
|
||||
width: 27px;
|
||||
height: 20px;
|
||||
background-position: -186px -107px;
|
||||
}
|
||||
}
|
||||
|
||||
&.female-icon {
|
||||
width: 18px;
|
||||
height: 27px;
|
||||
background-position: -186px -202px;
|
||||
|
||||
&.selected {
|
||||
width: 18px;
|
||||
height: 27px;
|
||||
background-position: -186px -239px;
|
||||
}
|
||||
}
|
||||
|
||||
&.ha-icon {
|
||||
width: 25px;
|
||||
height: 22px;
|
||||
background-position: -226px -245px;
|
||||
|
||||
&.selected {
|
||||
width: 25px;
|
||||
height: 22px;
|
||||
background-position: -226px -277px;
|
||||
}
|
||||
}
|
||||
|
||||
&.he-icon {
|
||||
width: 31px;
|
||||
height: 27px;
|
||||
background-position: -145px -83px;
|
||||
|
||||
&.selected {
|
||||
width: 31px;
|
||||
height: 27px;
|
||||
background-position: -145px -120px;
|
||||
}
|
||||
}
|
||||
|
||||
&.hr-icon {
|
||||
width: 29px;
|
||||
height: 25px;
|
||||
background-position: -145px -194px;
|
||||
|
||||
&.selected {
|
||||
width: 29px;
|
||||
height: 25px;
|
||||
background-position: -145px -229px;
|
||||
}
|
||||
}
|
||||
|
||||
&.lg-icon {
|
||||
width: 19px;
|
||||
height: 20px;
|
||||
background-position: -303px -45px;
|
||||
|
||||
&.selected {
|
||||
width: 19px;
|
||||
height: 20px;
|
||||
background-position: -303px -75px;
|
||||
}
|
||||
}
|
||||
|
||||
&.loading-icon {
|
||||
width: 21px;
|
||||
height: 25px;
|
||||
background-position: -186px -167px;
|
||||
}
|
||||
|
||||
|
||||
&.male-icon {
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
background-position: -186px -276px;
|
||||
|
||||
&.selected {
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
background-position: -272px -5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.sellable-icon {
|
||||
width: 17px;
|
||||
height: 15px;
|
||||
background-position: -303px -105px;
|
||||
}
|
||||
|
||||
|
||||
&.sh-icon {
|
||||
width: 37px;
|
||||
height: 10px;
|
||||
background-position: -303px -5px;
|
||||
|
||||
&.selected {
|
||||
width: 37px;
|
||||
height: 10px;
|
||||
background-position: -303px -25px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.spotlight-icon {
|
||||
width: 130px;
|
||||
height: 305px;
|
||||
background-position: -5px -5px;
|
||||
}
|
||||
|
||||
|
||||
&.wa-icon {
|
||||
width: 36px;
|
||||
height: 18px;
|
||||
background-position: -226px -5px;
|
||||
|
||||
&.selected {
|
||||
width: 36px;
|
||||
height: 18px;
|
||||
background-position: -226px -33px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-avatar-editor-wardrobe-figure-preview {
|
||||
background-color: $pale-sky;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
|
||||
.avatar-image {
|
||||
position: absolute;
|
||||
bottom: -15px;
|
||||
margin: 0 auto;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.avatar-shadow {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 25px;
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
margin: 0 auto;
|
||||
border-radius: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.20);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 75%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-radius: 50%;
|
||||
background-color: $pale-sky;
|
||||
box-shadow: 0 0 8px 2px rgba($white,.6);
|
||||
transform: scale(2);
|
||||
}
|
||||
|
||||
.button-container {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 5;
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-avatar-editor {
|
||||
width: $avatar-editor-width;
|
||||
height: $avatar-editor-height;
|
||||
|
||||
.category-item {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.figure-preview-container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
background-color: $pale-sky;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
|
||||
.arrow-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
bottom: 12px;
|
||||
z-index: 5;
|
||||
|
||||
.icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-image {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 50px;
|
||||
margin: 0 auto;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.avatar-spotlight {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.avatar-shadow {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 15px;
|
||||
width: 70px;
|
||||
height: 30px;
|
||||
margin: 0 auto;
|
||||
border-radius: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.20);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 75%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-radius: 50%;
|
||||
background-color: $pale-sky;
|
||||
box-shadow: 0 0 8px 2px rgba($white,.6);
|
||||
transform: scale(2);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
import { AddLinkEventTracker, AvatarEditorFigureCategory, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { FaDice, FaTrash, FaUndo } from 'react-icons/fa';
|
||||
import { AvatarEditorAction, LocalizeText } from '../../api';
|
||||
import { Button, ButtonGroup, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common';
|
||||
import { useAvatarEditor } from '../../hooks';
|
||||
import { AvatarEditorModelView } from './views/AvatarEditorModelView';
|
||||
|
||||
const DEFAULT_MALE_FIGURE: string = 'hr-100.hd-180-7.ch-215-66.lg-270-79.sh-305-62.ha-1002-70.wa-2007';
|
||||
const DEFAULT_FEMALE_FIGURE: string = 'hr-515-33.hd-600-1.ch-635-70.lg-716-66-62.sh-735-68';
|
||||
|
||||
export const AvatarEditorNewView: FC<{}> = props =>
|
||||
{
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
const { setIsVisible: setEditorVisibility, avatarModels, activeModelKey, setActiveModelKey } = useAvatarEditor();
|
||||
|
||||
const processAction = (action: string) =>
|
||||
{
|
||||
switch(action)
|
||||
{
|
||||
case AvatarEditorAction.ACTION_CLEAR:
|
||||
return;
|
||||
case AvatarEditorAction.ACTION_RESET:
|
||||
return;
|
||||
case AvatarEditorAction.ACTION_RANDOMIZE:
|
||||
return;
|
||||
case AvatarEditorAction.ACTION_SAVE:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const linkTracker: ILinkEventTracker = {
|
||||
linkReceived: (url: string) =>
|
||||
{
|
||||
const parts = url.split('/');
|
||||
|
||||
if(parts.length < 2) return;
|
||||
|
||||
switch(parts[1])
|
||||
{
|
||||
case 'show':
|
||||
setIsVisible(true);
|
||||
return;
|
||||
case 'hide':
|
||||
setIsVisible(false);
|
||||
return;
|
||||
case 'toggle':
|
||||
setIsVisible(prevValue => !prevValue);
|
||||
return;
|
||||
}
|
||||
},
|
||||
eventUrlPrefix: 'avatar-editor/'
|
||||
};
|
||||
|
||||
AddLinkEventTracker(linkTracker);
|
||||
|
||||
return () => RemoveLinkEventTracker(linkTracker);
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setEditorVisibility(isVisible)
|
||||
}, [ isVisible, setEditorVisibility ]);
|
||||
|
||||
if(!isVisible) return null;
|
||||
|
||||
return (
|
||||
<NitroCardView uniqueKey="avatar-editor" className="nitro-avatar-editor">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('avatareditor.title') } onCloseClick={ event => setIsVisible(false) } />
|
||||
<NitroCardTabsView>
|
||||
{ Object.keys(avatarModels).map(modelKey =>
|
||||
{
|
||||
const isActive = (activeModelKey === modelKey);
|
||||
|
||||
return (
|
||||
<NitroCardTabsItemView key={ modelKey } isActive={ isActive } onClick={ event => setActiveModelKey(modelKey) }>
|
||||
{ LocalizeText(`avatareditor.category.${ modelKey }`) }
|
||||
</NitroCardTabsItemView>
|
||||
);
|
||||
}) }
|
||||
</NitroCardTabsView>
|
||||
<NitroCardContentView>
|
||||
<Grid>
|
||||
<Column size={ 9 } overflow="hidden">
|
||||
{ ((activeModelKey.length > 0) && (activeModelKey !== AvatarEditorFigureCategory.WARDROBE)) &&
|
||||
<AvatarEditorModelView name={ activeModelKey } categories={ avatarModels[activeModelKey] } /> }
|
||||
{ (activeModelKey === AvatarEditorFigureCategory.WARDROBE) }
|
||||
</Column>
|
||||
<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>
|
||||
<Button className="w-100" variant="success" onClick={ event => processAction(AvatarEditorAction.ACTION_SAVE) }>
|
||||
{ LocalizeText('avatareditor.save') }
|
||||
</Button>
|
||||
</Column>
|
||||
</Column>
|
||||
</Grid>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { Base, BaseProps } from '../../../common';
|
||||
|
||||
type AvatarIconType = 'male' | 'female' | 'clear' | 'sellable' | string;
|
||||
|
||||
export interface AvatarEditorIconProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
icon: AvatarIconType;
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
export const AvatarEditorIcon: FC<AvatarEditorIconProps> = props =>
|
||||
{
|
||||
const { icon = null, selected = false, classNames = [], children = null, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'nitro-avatar-editor-spritesheet' ];
|
||||
|
||||
if(icon && icon.length) newClassNames.push(icon + '-icon');
|
||||
|
||||
if(selected) newClassNames.push('selected');
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ icon, selected, classNames ]);
|
||||
|
||||
return <Base classNames={ getClassNames } { ...rest } />
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
import { AvatarEditorFigureCategory } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { FigureData, IAvatarEditorCategory } from '../../../api';
|
||||
import { Column, Flex, Grid } from '../../../common';
|
||||
import { useAvatarEditor } from '../../../hooks';
|
||||
import { AvatarEditorIcon } from './AvatarEditorIcon';
|
||||
import { AvatarEditorFigureSetView } from './figure-set';
|
||||
import { AvatarEditorPaletteSetView } from './palette-set';
|
||||
|
||||
export const AvatarEditorModelView: FC<{
|
||||
name: string,
|
||||
categories: IAvatarEditorCategory[]
|
||||
}> = props =>
|
||||
{
|
||||
const { name = '', categories = [] } = props;
|
||||
const [ activeSetType, setActiveSetType ] = useState<string>('');
|
||||
const { maxPaletteCount = 1 } = useAvatarEditor();
|
||||
|
||||
const activeCategory = useMemo(() =>
|
||||
{
|
||||
return categories.find(category => category.setType === activeSetType) ?? null;
|
||||
}, [ categories, activeSetType ]);
|
||||
|
||||
const setGender = (gender: string) =>
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!activeCategory) return;
|
||||
|
||||
// we need to run this when we change which parts r selected
|
||||
/* for(const partItem of activeCategory.partItems)
|
||||
{
|
||||
if(!partItem || !part.isSelected) continue;
|
||||
|
||||
setMaxPaletteCount(part.maxColorIndex || 1);
|
||||
|
||||
break;
|
||||
} */
|
||||
}, [ activeCategory ])
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!categories || !categories.length) return;
|
||||
|
||||
setActiveSetType(categories[0]?.setType)
|
||||
}, [ categories ]);
|
||||
|
||||
if(!activeCategory) return null;
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<Column size={ 2 }>
|
||||
{ (name === AvatarEditorFigureCategory.GENERIC) &&
|
||||
<>
|
||||
<Flex center pointer className="category-item" onClick={ event => setGender(FigureData.MALE) }>
|
||||
<AvatarEditorIcon icon="male" selected={ false } />
|
||||
</Flex>
|
||||
<Flex center pointer className="category-item" onClick={ event => setGender(FigureData.FEMALE) }>
|
||||
<AvatarEditorIcon icon="female" selected={ false } />
|
||||
</Flex>
|
||||
</> }
|
||||
{ (name !== AvatarEditorFigureCategory.GENERIC) && (categories.length > 0) && categories.map(category =>
|
||||
{
|
||||
return (
|
||||
<Flex center pointer key={ category.setType } className="category-item" onClick={ event => setActiveSetType(category.setType) }>
|
||||
<AvatarEditorIcon icon={ category.setType } selected={ (activeSetType === category.setType) } />
|
||||
</Flex>
|
||||
);
|
||||
}) }
|
||||
</Column>
|
||||
<Column size={ 5 } overflow="hidden">
|
||||
<AvatarEditorFigureSetView category={ activeCategory } />
|
||||
</Column>
|
||||
<Column size={ 5 } overflow="hidden">
|
||||
{ (maxPaletteCount >= 1) &&
|
||||
<AvatarEditorPaletteSetView category={ activeCategory } paletteIndex={ 0 } /> }
|
||||
{ (maxPaletteCount === 2) &&
|
||||
<AvatarEditorPaletteSetView category={ activeCategory } paletteIndex={ 1 } /> }
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { AvatarEditorThumbnailsHelper, FigureData, GetConfigurationValue, IAvatarEditorCategoryPartItem } from '../../../../api';
|
||||
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common';
|
||||
import { useAvatarEditor } from '../../../../hooks';
|
||||
import { AvatarEditorIcon } from '../AvatarEditorIcon';
|
||||
|
||||
export const AvatarEditorFigureSetItemView: FC<{
|
||||
setType: string;
|
||||
partItem: IAvatarEditorCategoryPartItem;
|
||||
isSelected: boolean;
|
||||
} & LayoutGridItemProps> = props =>
|
||||
{
|
||||
const { setType = null, partItem = null, isSelected = false, ...rest } = props;
|
||||
const [ assetUrl, setAssetUrl ] = useState<string>('');
|
||||
const { selectedColorParts = null, getFigureStringWithFace = null } = useAvatarEditor();
|
||||
|
||||
const isHC = !GetConfigurationValue<boolean>('hc.disabled', false) && ((partItem.partSet?.clubLevel ?? 0) > 0);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!setType || !setType.length || !partItem) return;
|
||||
|
||||
const loadImage = async () =>
|
||||
{
|
||||
const isHC = !GetConfigurationValue<boolean>('hc.disabled', false) && ((partItem.partSet?.clubLevel ?? 0) > 0);
|
||||
|
||||
let url: string = null;
|
||||
|
||||
if(setType === FigureData.FACE)
|
||||
{
|
||||
url = await AvatarEditorThumbnailsHelper.buildForFace(getFigureStringWithFace(partItem.id), isHC);
|
||||
}
|
||||
else
|
||||
{
|
||||
url = await AvatarEditorThumbnailsHelper.build(setType, partItem, partItem.usesColor, selectedColorParts[setType] ?? null, isHC);
|
||||
}
|
||||
|
||||
if(url && url.length) setAssetUrl(url);
|
||||
}
|
||||
|
||||
loadImage();
|
||||
}, [ setType, partItem, selectedColorParts, getFigureStringWithFace ]);
|
||||
|
||||
if(!partItem) return null;
|
||||
|
||||
return (
|
||||
<LayoutGridItem itemImage={ (partItem.isClear ? undefined : assetUrl) } itemActive={ isSelected } style={ { width: '100%', 'flex': '1' } } { ...rest }>
|
||||
{ !partItem.isClear && isHC && <LayoutCurrencyIcon className="position-absolute end-1 bottom-1" type="hc" /> }
|
||||
{ partItem.isClear && <AvatarEditorIcon icon="clear" /> }
|
||||
{ !partItem.isClear && partItem.partSet.isSellable && <AvatarEditorIcon icon="sellable" position="absolute" className="end-1 bottom-1" /> }
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
import { FC, useRef } from 'react';
|
||||
import { IAvatarEditorCategory, IAvatarEditorCategoryPartItem } from '../../../../api';
|
||||
import { InfiniteGrid } from '../../../../common';
|
||||
import { useAvatarEditor } from '../../../../hooks';
|
||||
import { AvatarEditorFigureSetItemView } from './AvatarEditorFigureSetItemView';
|
||||
|
||||
export const AvatarEditorFigureSetView: FC<{
|
||||
category: IAvatarEditorCategory
|
||||
}> = props =>
|
||||
{
|
||||
const { category = null } = props;
|
||||
const { selectedParts = null, selectEditorPart } = useAvatarEditor();
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const isPartItemSelected = (partItem: IAvatarEditorCategoryPartItem) =>
|
||||
{
|
||||
if(!category || !category.setType || !selectedParts || !selectedParts[category.setType]) return false;
|
||||
|
||||
const partId = selectedParts[category.setType];
|
||||
|
||||
return (partId === partItem.id);
|
||||
}
|
||||
|
||||
const columnCount = 3;
|
||||
|
||||
return (
|
||||
<InfiniteGrid rows={ category.partItems } columnCount={ columnCount } overscan={ 5 } itemRender={ (item: IAvatarEditorCategoryPartItem) =>
|
||||
{
|
||||
if(!item) return null;
|
||||
|
||||
return (
|
||||
<AvatarEditorFigureSetItemView key={ item.id } setType={ category.setType } partItem={ item } isSelected={ isPartItemSelected(item) } onClick={ event => selectEditorPart(category.setType, item.partSet?.id ?? -1) } />
|
||||
)
|
||||
} } />
|
||||
);
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
export * from './AvatarEditorFigureSetItemView';
|
||||
export * from './AvatarEditorFigureSetView';
|
@ -1,27 +0,0 @@
|
||||
import { ColorConverter, IPartColor } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { GetConfigurationValue } from '../../../../api';
|
||||
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common';
|
||||
|
||||
export interface AvatarEditorPaletteSetItemProps extends LayoutGridItemProps
|
||||
{
|
||||
setType: string;
|
||||
partColor: IPartColor;
|
||||
isSelected: boolean;
|
||||
}
|
||||
|
||||
// its disabled if its hc and you dont have it
|
||||
export const AvatarEditorPaletteSetItem: FC<AvatarEditorPaletteSetItemProps> = props =>
|
||||
{
|
||||
const { setType = null, partColor = null, isSelected = false, ...rest } = props;
|
||||
|
||||
if(!partColor) return null;
|
||||
|
||||
const isHC = !GetConfigurationValue<boolean>('hc.disabled', false) && (partColor.clubLevel > 0);
|
||||
|
||||
return (
|
||||
<LayoutGridItem itemHighlight itemColor={ ColorConverter.int2rgb(partColor.rgb) } itemActive={ isSelected } className="clear-bg" { ...rest }>
|
||||
{ isHC && <LayoutCurrencyIcon className="position-absolute end-1 bottom-1" type="hc" /> }
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
import { IPartColor } from '@nitrots/nitro-renderer';
|
||||
import { FC, useRef } from 'react';
|
||||
import { IAvatarEditorCategory } from '../../../../api';
|
||||
import { AutoGrid } from '../../../../common';
|
||||
import { useAvatarEditor } from '../../../../hooks';
|
||||
import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView';
|
||||
|
||||
export const AvatarEditorPaletteSetView: FC<{
|
||||
category: IAvatarEditorCategory,
|
||||
paletteIndex: number;
|
||||
}> = props =>
|
||||
{
|
||||
const { category = null, paletteIndex = -1 } = props;
|
||||
const paletteSet = category?.colorItems[paletteIndex] ?? null;
|
||||
const { selectedColors = null, selectEditorColor } = useAvatarEditor();
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const isPartColorSelected = (partColor: IPartColor) =>
|
||||
{
|
||||
if(!category || !category.setType || !selectedColors || !selectedColors[category.setType] || !selectedColors[category.setType][paletteIndex]) return false;
|
||||
|
||||
const colorId = selectedColors[category.setType][paletteIndex];
|
||||
|
||||
return (colorId === partColor.id);
|
||||
}
|
||||
|
||||
return (
|
||||
<AutoGrid innerRef={ elementRef } gap={ 1 } columnCount={ 5 } columnMinWidth={ 30 }>
|
||||
{ (paletteSet.length > 0) && paletteSet.map(item =>
|
||||
<AvatarEditorPaletteSetItem key={ item.id } setType={ category.setType } partColor={ item } isSelected={ isPartColorSelected(item) } onClick={ event => selectEditorColor(category.setType, paletteIndex, item.id) } />) }
|
||||
</AutoGrid>
|
||||
);
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
export * from './AvatarEditorPaletteSetItemView';
|
||||
export * from './AvatarEditorPaletteSetView';
|
@ -258,7 +258,7 @@
|
||||
}
|
||||
|
||||
.nitro-avatar-editor {
|
||||
width: $avatar-editor-width;
|
||||
width: $avatar-editor-width + 50px;
|
||||
height: $avatar-editor-height;
|
||||
|
||||
.category-item {
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { GetAvatarRenderManager, IAvatarFigureContainer, SaveWardrobeOutfitMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback } from 'react';
|
||||
import { FaSave } from 'react-icons/fa';
|
||||
import { GiClothes } from 'react-icons/gi';
|
||||
import { GetClubMemberLevel, GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../api';
|
||||
import { Base, Button, Flex, InfiniteGrid, LayoutAvatarImageView, LayoutCurrencyIcon, LayoutGridItem } from '../../common';
|
||||
import { useAvatarEditor } from '../../hooks';
|
||||
@ -48,11 +50,10 @@ export const AvatarEditorWardrobeView: FC<{}> = props =>
|
||||
<LayoutAvatarImageView figure={ figureContainer.getFigureString() } gender={ gender } direction={ 2 } /> }
|
||||
<Base className="avatar-shadow" />
|
||||
{ !hcDisabled && (clubLevel > 0) && <LayoutCurrencyIcon className="position-absolute top-1 start-1" type="hc" /> }
|
||||
<Flex gap={ 1 } className="button-container">
|
||||
<Button variant="link" fullWidth onClick={ event => saveFigureAtWardrobeIndex(index) }>{ LocalizeText('avatareditor.wardrobe.save') }</Button>
|
||||
{ figureContainer &&
|
||||
<Button variant="link" fullWidth onClick={ event => wearFigureAtIndex(index) } disabled={ (clubLevel > GetClubMemberLevel()) }>{ LocalizeText('widget.generic_usable.button.use') }</Button> }
|
||||
</Flex>
|
||||
<Flex gap={1} className="button-container">
|
||||
<Button variant="link" fullWidth onClick={() => saveFigureAtWardrobeIndex(index)} title={LocalizeText('avatareditor.wardrobe.save')}> <FaSave size={20} style={{ color: 'black' }} /> </Button>
|
||||
{figureContainer && <Button variant="link" fullWidth onClick={event => wearFigureAtIndex(index)} disabled={(clubLevel > GetClubMemberLevel())} title={LocalizeText('widget.generic_usable.button.use')}> <GiClothes size={20} style={{ color: 'white' }} /> </Button>}
|
||||
</Flex>
|
||||
</LayoutGridItem>
|
||||
)
|
||||
} } />
|
||||
|
@ -1,55 +0,0 @@
|
||||
import { AvatarDirectionAngle } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { FigureData } from '../../../api';
|
||||
import { Base, Column, LayoutAvatarImageView } from '../../../common';
|
||||
import { AvatarEditorIcon } from './AvatarEditorIcon';
|
||||
|
||||
export interface AvatarEditorFigurePreviewViewProps
|
||||
{
|
||||
figureData: FigureData;
|
||||
}
|
||||
|
||||
export const AvatarEditorFigurePreviewView: FC<AvatarEditorFigurePreviewViewProps> = props =>
|
||||
{
|
||||
const { figureData = null } = props;
|
||||
const [ updateId, setUpdateId ] = useState(-1);
|
||||
|
||||
const rotateFigure = (direction: number) =>
|
||||
{
|
||||
if(direction < AvatarDirectionAngle.MIN_DIRECTION)
|
||||
{
|
||||
direction = (AvatarDirectionAngle.MAX_DIRECTION + (direction + 1));
|
||||
}
|
||||
|
||||
if(direction > AvatarDirectionAngle.MAX_DIRECTION)
|
||||
{
|
||||
direction = (direction - (AvatarDirectionAngle.MAX_DIRECTION + 1));
|
||||
}
|
||||
|
||||
figureData.direction = direction;
|
||||
}
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!figureData) return;
|
||||
|
||||
figureData.notify = () => setUpdateId(prevValue => (prevValue + 1));
|
||||
|
||||
return () =>
|
||||
{
|
||||
figureData.notify = null;
|
||||
}
|
||||
}, [ figureData ] );
|
||||
|
||||
return (
|
||||
<Column className="figure-preview-container" overflow="hidden" position="relative">
|
||||
<LayoutAvatarImageView figure={ figureData.getFigureString() } direction={ figureData.direction } scale={ 2 } />
|
||||
<AvatarEditorIcon className="avatar-spotlight" icon="spotlight" />
|
||||
<Base className="avatar-shadow" />
|
||||
<Base className="arrow-container">
|
||||
<AvatarEditorIcon pointer icon="arrow-left" onClick={ event => rotateFigure(figureData.direction + 1) } />
|
||||
<AvatarEditorIcon pointer icon="arrow-right" onClick={ event => rotateFigure(figureData.direction - 1) } />
|
||||
</Base>
|
||||
</Column>
|
||||
);
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { Base, BaseProps } from '../../../common';
|
||||
|
||||
type AvatarIconType = 'male' | 'female' | 'clear' | 'sellable' | string;
|
||||
|
||||
export interface AvatarEditorIconProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
icon: AvatarIconType;
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
export const AvatarEditorIcon: FC<AvatarEditorIconProps> = props =>
|
||||
{
|
||||
const { icon = null, selected = false, classNames = [], children = null, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'nitro-avatar-editor-spritesheet' ];
|
||||
|
||||
if(icon && icon.length) newClassNames.push(icon + '-icon');
|
||||
|
||||
if(selected) newClassNames.push('selected');
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ icon, selected, classNames ]);
|
||||
|
||||
return <Base classNames={ getClassNames } { ...rest } />
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
import { Dispatch, FC, SetStateAction, useCallback, useEffect, useState } from 'react';
|
||||
import { CategoryData, FigureData, IAvatarEditorCategoryModel } from '../../../api';
|
||||
import { Column, Flex, Grid } from '../../../common';
|
||||
import { AvatarEditorIcon } from './AvatarEditorIcon';
|
||||
import { AvatarEditorFigureSetView } from './figure-set/AvatarEditorFigureSetView';
|
||||
import { AvatarEditorPaletteSetView } from './palette-set/AvatarEditorPaletteSetView';
|
||||
export interface AvatarEditorModelViewProps
|
||||
{
|
||||
model: IAvatarEditorCategoryModel;
|
||||
gender: string;
|
||||
setGender: Dispatch<SetStateAction<string>>;
|
||||
}
|
||||
|
||||
export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props =>
|
||||
{
|
||||
const { model = null, gender = null, setGender = null } = props;
|
||||
const [ activeCategory, setActiveCategory ] = useState<CategoryData>(null);
|
||||
const [ maxPaletteCount, setMaxPaletteCount ] = useState(1);
|
||||
|
||||
const selectCategory = useCallback((name: string) =>
|
||||
{
|
||||
const category = model.categories.get(name);
|
||||
|
||||
if(!category) return;
|
||||
|
||||
category.init();
|
||||
|
||||
setActiveCategory(category);
|
||||
|
||||
for(const part of category.parts)
|
||||
{
|
||||
if(!part || !part.isSelected) continue;
|
||||
|
||||
setMaxPaletteCount(part.maxColorIndex || 1);
|
||||
|
||||
break;
|
||||
}
|
||||
}, [ model ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
model.init();
|
||||
|
||||
for(const name of model.categories.keys())
|
||||
{
|
||||
selectCategory(name);
|
||||
|
||||
break;
|
||||
}
|
||||
}, [ model, selectCategory ]);
|
||||
|
||||
if(!model || !activeCategory) return null;
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<Column size={ 2 }>
|
||||
{ model.canSetGender &&
|
||||
<>
|
||||
<Flex center pointer className="category-item" onClick={ event => setGender(FigureData.MALE) }>
|
||||
<AvatarEditorIcon icon="male" selected={ (gender === FigureData.MALE) } />
|
||||
</Flex>
|
||||
<Flex center pointer className="category-item" onClick={ event => setGender(FigureData.FEMALE) }>
|
||||
<AvatarEditorIcon icon="female" selected={ (gender === FigureData.FEMALE) } />
|
||||
</Flex>
|
||||
</> }
|
||||
{ !model.canSetGender && model.categories && (model.categories.size > 0) && Array.from(model.categories.keys()).map(name =>
|
||||
{
|
||||
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>
|
||||
);
|
||||
}) }
|
||||
</Column>
|
||||
<Column size={ 5 } overflow="hidden">
|
||||
<AvatarEditorFigureSetView model={ model } category={ activeCategory } setMaxPaletteCount={ setMaxPaletteCount } />
|
||||
</Column>
|
||||
<Column size={ 5 } overflow="hidden">
|
||||
{ (maxPaletteCount >= 1) &&
|
||||
<AvatarEditorPaletteSetView model={ model } category={ activeCategory } paletteSet={ activeCategory.getPalette(0) } paletteIndex={ 0 } /> }
|
||||
{ (maxPaletteCount === 2) &&
|
||||
<AvatarEditorPaletteSetView model={ model } category={ activeCategory } paletteSet={ activeCategory.getPalette(1) } paletteIndex={ 1 } /> }
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
import { GetAvatarRenderManager, IAvatarFigureContainer, SaveWardrobeOutfitMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { Dispatch, FC, SetStateAction, useCallback, useMemo } from 'react';
|
||||
import { FigureData, GetClubMemberLevel, GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../../api';
|
||||
import { AutoGrid, Base, Button, Flex, LayoutAvatarImageView, LayoutCurrencyIcon, LayoutGridItem } from '../../../common';
|
||||
|
||||
export interface AvatarEditorWardrobeViewProps
|
||||
{
|
||||
figureData: FigureData;
|
||||
savedFigures: [ IAvatarFigureContainer, string ][];
|
||||
setSavedFigures: Dispatch<SetStateAction<[ IAvatarFigureContainer, string][]>>;
|
||||
loadAvatarInEditor: (figure: string, gender: string, reset?: boolean) => void;
|
||||
}
|
||||
|
||||
export const AvatarEditorWardrobeView: FC<AvatarEditorWardrobeViewProps> = props =>
|
||||
{
|
||||
const { figureData = null, savedFigures = [], setSavedFigures = null, loadAvatarInEditor = null } = props;
|
||||
|
||||
const hcDisabled = GetConfigurationValue<boolean>('hc.disabled', false);
|
||||
|
||||
const wearFigureAtIndex = useCallback((index: number) =>
|
||||
{
|
||||
if((index >= savedFigures.length) || (index < 0)) return;
|
||||
|
||||
const [ figure, gender ] = savedFigures[index];
|
||||
|
||||
loadAvatarInEditor(figure.getFigureString(), gender);
|
||||
}, [ savedFigures, loadAvatarInEditor ]);
|
||||
|
||||
const saveFigureAtWardrobeIndex = useCallback((index: number) =>
|
||||
{
|
||||
if(!figureData || (index >= savedFigures.length) || (index < 0)) return;
|
||||
|
||||
const newFigures = [ ...savedFigures ];
|
||||
|
||||
const figure = figureData.getFigureString();
|
||||
const gender = figureData.gender;
|
||||
|
||||
newFigures[index] = [ GetAvatarRenderManager().createFigureContainer(figure), gender ];
|
||||
|
||||
setSavedFigures(newFigures);
|
||||
SendMessageComposer(new SaveWardrobeOutfitMessageComposer((index + 1), figure, gender));
|
||||
}, [ figureData, savedFigures, setSavedFigures ]);
|
||||
|
||||
const figures = useMemo(() =>
|
||||
{
|
||||
if(!savedFigures || !savedFigures.length) return [];
|
||||
|
||||
const items: JSX.Element[] = [];
|
||||
|
||||
savedFigures.forEach(([ figureContainer, gender ], index) =>
|
||||
{
|
||||
let clubLevel = 0;
|
||||
|
||||
if(figureContainer) clubLevel = GetAvatarRenderManager().getFigureClubLevel(figureContainer, gender);
|
||||
|
||||
items.push(
|
||||
<LayoutGridItem key={ index } position="relative" overflow="hidden" className="nitro-avatar-editor-wardrobe-figure-preview">
|
||||
{ figureContainer &&
|
||||
<LayoutAvatarImageView figure={ figureContainer.getFigureString() } gender={ gender } direction={ 2 } /> }
|
||||
<Base className="avatar-shadow" />
|
||||
{ !hcDisabled && (clubLevel > 0) && <LayoutCurrencyIcon className="position-absolute top-1 start-1" type="hc" /> }
|
||||
<Flex gap={ 1 } className="button-container">
|
||||
<Button variant="link" fullWidth onClick={ event => saveFigureAtWardrobeIndex(index) }>{ LocalizeText('avatareditor.wardrobe.save') }</Button>
|
||||
{ figureContainer &&
|
||||
<Button variant="link" fullWidth onClick={ event => wearFigureAtIndex(index) } disabled={ (clubLevel > GetClubMemberLevel()) }>{ LocalizeText('widget.generic_usable.button.use') }</Button> }
|
||||
</Flex>
|
||||
</LayoutGridItem>
|
||||
);
|
||||
});
|
||||
|
||||
return items;
|
||||
}, [ savedFigures, hcDisabled, saveFigureAtWardrobeIndex, wearFigureAtIndex ]);
|
||||
|
||||
return (
|
||||
<AutoGrid columnCount={ 5 } columnMinWidth={ 80 } columnMinHeight={ 140 }>
|
||||
{ figures }
|
||||
</AutoGrid>
|
||||
);
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { AvatarEditorGridPartItem, GetConfigurationValue } from '../../../../api';
|
||||
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common';
|
||||
import { AvatarEditorIcon } from '../AvatarEditorIcon';
|
||||
|
||||
export interface AvatarEditorFigureSetItemViewProps extends LayoutGridItemProps
|
||||
{
|
||||
partItem: AvatarEditorGridPartItem;
|
||||
}
|
||||
|
||||
export const AvatarEditorFigureSetItemView: FC<AvatarEditorFigureSetItemViewProps> = props =>
|
||||
{
|
||||
const { partItem = null, children = null, ...rest } = props;
|
||||
const [ updateId, setUpdateId ] = useState(-1);
|
||||
|
||||
const hcDisabled = GetConfigurationValue<boolean>('hc.disabled', false);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const rerender = () => setUpdateId(prevValue => (prevValue + 1));
|
||||
|
||||
partItem.notify = rerender;
|
||||
|
||||
return () => partItem.notify = null;
|
||||
}, [ partItem ]);
|
||||
|
||||
return (
|
||||
<LayoutGridItem itemImage={ (partItem.isClear ? undefined : partItem.imageUrl) } itemActive={ partItem.isSelected } { ...rest }>
|
||||
{ !hcDisabled && partItem.isHC && <LayoutCurrencyIcon className="position-absolute end-1 bottom-1" type="hc" /> }
|
||||
{ partItem.isClear && <AvatarEditorIcon icon="clear" /> }
|
||||
{ partItem.isSellable && <AvatarEditorIcon icon="sellable" position="absolute" className="end-1 bottom-1" /> }
|
||||
{ children }
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
import { Dispatch, FC, SetStateAction, useCallback, useEffect, useRef } from 'react';
|
||||
import { AvatarEditorGridPartItem, CategoryData, IAvatarEditorCategoryModel } from '../../../../api';
|
||||
import { AutoGrid } from '../../../../common';
|
||||
import { AvatarEditorFigureSetItemView } from './AvatarEditorFigureSetItemView';
|
||||
|
||||
export interface AvatarEditorFigureSetViewProps
|
||||
{
|
||||
model: IAvatarEditorCategoryModel;
|
||||
category: CategoryData;
|
||||
setMaxPaletteCount: Dispatch<SetStateAction<number>>;
|
||||
}
|
||||
|
||||
export const AvatarEditorFigureSetView: FC<AvatarEditorFigureSetViewProps> = props =>
|
||||
{
|
||||
const { model = null, category = null, setMaxPaletteCount = null } = props;
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const selectPart = useCallback((item: AvatarEditorGridPartItem) =>
|
||||
{
|
||||
const index = category.parts.indexOf(item);
|
||||
|
||||
if(index === -1) return;
|
||||
|
||||
model.selectPart(category.name, index);
|
||||
|
||||
const partItem = category.getCurrentPart();
|
||||
|
||||
setMaxPaletteCount(partItem.maxColorIndex || 1);
|
||||
}, [ model, category, setMaxPaletteCount ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!model || !category || !elementRef || !elementRef.current) return;
|
||||
|
||||
elementRef.current.scrollTop = 0;
|
||||
}, [ model, category ]);
|
||||
|
||||
return (
|
||||
<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) } />) }
|
||||
</AutoGrid>
|
||||
);
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
export * from './AvatarEditorFigureSetItemView';
|
||||
export * from './AvatarEditorFigureSetView';
|
@ -1,6 +0,0 @@
|
||||
export * from './AvatarEditorFigurePreviewView';
|
||||
export * from './AvatarEditorIcon';
|
||||
export * from './AvatarEditorModelView';
|
||||
export * from './AvatarEditorWardrobeView';
|
||||
export * from './figure-set';
|
||||
export * from './palette-set';
|
@ -1,32 +0,0 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { AvatarEditorGridColorItem, GetConfigurationValue } from '../../../../api';
|
||||
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common';
|
||||
|
||||
export interface AvatarEditorPaletteSetItemProps extends LayoutGridItemProps
|
||||
{
|
||||
colorItem: AvatarEditorGridColorItem;
|
||||
}
|
||||
|
||||
export const AvatarEditorPaletteSetItem: FC<AvatarEditorPaletteSetItemProps> = props =>
|
||||
{
|
||||
const { colorItem = null, children = null, ...rest } = props;
|
||||
const [ updateId, setUpdateId ] = useState(-1);
|
||||
|
||||
const hcDisabled = GetConfigurationValue<boolean>('hc.disabled', false);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const rerender = () => setUpdateId(prevValue => (prevValue + 1));
|
||||
|
||||
colorItem.notify = rerender;
|
||||
|
||||
return () => colorItem.notify = null;
|
||||
}, [ colorItem ]);
|
||||
|
||||
return (
|
||||
<LayoutGridItem itemHighlight itemColor={ colorItem.color } itemActive={ colorItem.isSelected } className="clear-bg" { ...rest }>
|
||||
{ !hcDisabled && colorItem.isHC && <LayoutCurrencyIcon className="position-absolute end-1 bottom-1" type="hc" /> }
|
||||
{ children }
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import { FC, useCallback, useEffect, useRef } from 'react';
|
||||
import { AvatarEditorGridColorItem, CategoryData, IAvatarEditorCategoryModel } from '../../../../api';
|
||||
import { AutoGrid } from '../../../../common';
|
||||
import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView';
|
||||
|
||||
export interface AvatarEditorPaletteSetViewProps
|
||||
{
|
||||
model: IAvatarEditorCategoryModel;
|
||||
category: CategoryData;
|
||||
paletteSet: AvatarEditorGridColorItem[];
|
||||
paletteIndex: number;
|
||||
}
|
||||
|
||||
export const AvatarEditorPaletteSetView: FC<AvatarEditorPaletteSetViewProps> = props =>
|
||||
{
|
||||
const { model = null, category = null, paletteSet = [], paletteIndex = -1 } = props;
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const selectColor = useCallback((item: AvatarEditorGridColorItem) =>
|
||||
{
|
||||
const index = paletteSet.indexOf(item);
|
||||
|
||||
if(index === -1) return;
|
||||
|
||||
model.selectColor(category.name, index, paletteIndex);
|
||||
}, [ model, category, paletteSet, paletteIndex ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!model || !category || !elementRef || !elementRef.current) return;
|
||||
|
||||
elementRef.current.scrollTop = 0;
|
||||
}, [ model, category ]);
|
||||
|
||||
return (
|
||||
<AutoGrid innerRef={ elementRef } gap={ 1 } columnCount={ 5 } columnMinWidth={ 30 }>
|
||||
{ (paletteSet.length > 0) && paletteSet.map((item, index) =>
|
||||
<AvatarEditorPaletteSetItem key={ index } colorItem={ item } onClick={ event => selectColor(item) } />) }
|
||||
</AutoGrid>
|
||||
);
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
export * from './AvatarEditorPaletteSetItemView';
|
||||
export * from './AvatarEditorPaletteSetView';
|
@ -124,9 +124,10 @@
|
||||
position: relative;
|
||||
padding-left:38px;
|
||||
text-align: left;
|
||||
pointer-events: all;
|
||||
|
||||
&.friend-bar-item-active {
|
||||
margin-bottom:21px;
|
||||
|
||||
}
|
||||
|
||||
&.friend-bar-search-item-active {
|
||||
@ -177,6 +178,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
.friends-myinfo {
|
||||
> :first-child {
|
||||
border-bottom: 1px dashed white;
|
||||
}
|
||||
|
||||
.myinfo-avatar {
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.avatar-image {
|
||||
margin-top: -17px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-friends-messenger {
|
||||
width: $messenger-width;
|
||||
height: $messenger-height;
|
||||
@ -270,4 +288,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { FindNewFriendsMessageComposer, MouseEventType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useRef, useState } from 'react';
|
||||
import { GetUserProfile, LocalizeText, MessengerFriend, OpenMessengerChat, SendMessageComposer } from '../../../../api';
|
||||
import { GetUserProfile, LocalizeText, MessengerFriend, OpenMessengerChat, SendMessageComposer } from '../../../../api';
|
||||
import { Base, Button, LayoutAvatarImageView, LayoutBadgeImageView } from '../../../../common';
|
||||
import { useFriends } from '../../../../hooks';
|
||||
|
||||
@ -49,10 +49,10 @@ export const FriendBarItemView: FC<{ friend: MessengerFriend }> = props =>
|
||||
return (
|
||||
<div ref={ elementRef } className={ 'btn btn-friendsgensuccess friend-bar-item ' + (isVisible ? 'friend-bar-item-active' : '') } onClick={ event => setVisible(prevValue => !prevValue) }>
|
||||
<div className={ `friend-bar-item-head position-absolute ${ friend.id > 0 ? 'avatar': 'group' }` }>
|
||||
{ (friend.id > 0) && <LayoutAvatarImageView headOnly={ !isVisible } figure={ friend.figure } direction={ 2 } /> }
|
||||
<LayoutAvatarImageView headOnly={true} figure={
|
||||
{ (friend.id > 0) && <LayoutAvatarImageView headOnly={ !isVisible } figure={ friend.figure } direction={ isVisible ? 2 : 3 } /> }
|
||||
<LayoutAvatarImageView headOnly={ !isVisible } figure={
|
||||
friend.id > 0 ? friend.figure : (friend.id <= 0 && friend.figure === 'ADM') ? 'ha-3409-1413-70.lg-285-89.ch-3032-1334-109.sh-3016-110.hd-185-1359.ca-3225-110-62.wa-3264-62-62.fa-1206-90.hr-3322-1403' : friend.figure
|
||||
} isgroup={friend.id <= 0 ? 1 : 0} direction={friend.id > 0 ? 2 : 3} />
|
||||
} isgroup={friend.id <= 0 ? 1 : 0} direction={isVisible ? 2 : 3} />
|
||||
</div>
|
||||
|
||||
<div className="text-truncate">{ friend.name }</div>
|
||||
|
@ -116,18 +116,17 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
<Column>
|
||||
{ visibleThreads && (visibleThreads.length > 0) && visibleThreads.map(thread => {
|
||||
return (
|
||||
<LayoutGridItem key={ thread.threadId } itemActive={ (activeThread === thread) } onClick={ event => setActiveThreadId(thread.threadId) }>
|
||||
{ thread.unread &&
|
||||
<LayoutItemCountView count={ thread.unreadCount } /> }
|
||||
<Flex fullWidth alignItems="center" gap={ 1 }>
|
||||
<Flex alignItems="center" className="friend-head px-1">
|
||||
<LayoutAvatarImageView figure={
|
||||
thread.participant.id > 0 ? thread.participant.figure : thread.participant.id <= 0 && thread.participant.figure === 'ADM' ? 'ha-3409-1413-70.lg-285-89.ch-3032-1334-109.sh-3016-110.hd-185-1359.ca-3225-110-62.wa-3264-62-62.fa-1206-90.hr-3322-1403' : thread.participant.figure
|
||||
} headOnly={true} direction={thread.participant.id > 0 ? 2 : 3} scale={0.9} />
|
||||
</Flex>
|
||||
<Text truncate grow>{ thread.participant.name }</Text>
|
||||
</Flex>
|
||||
</LayoutGridItem>
|
||||
<LayoutGridItem key={ thread.threadId } itemActive={ (activeThread === thread) } onClick={ event => setActiveThreadId(thread.threadId) }>
|
||||
{ thread.unread && <LayoutItemCountView count={ thread.unreadCount } /> }
|
||||
<Flex fullWidth alignItems="center" gap={ 1 }>
|
||||
<Flex alignItems="center" className="friend-head px-2">
|
||||
<LayoutAvatarImageView figure={
|
||||
thread.participant.id > 0 ? thread.participant.figure : thread.participant.id <= 0 && thread.participant.figure === 'ADM' ? 'ha-3409-1413-70.lg-285-89.ch-3032-1334-109.sh-3016-110.hd-185-1359.ca-3225-110-62.wa-3264-62-62.fa-1206-90.hr-3322-1403' : thread.participant.figure
|
||||
} headOnly={true} direction={thread.participant.id > 0 ? 2 : 3} />
|
||||
</Flex>
|
||||
<Text truncate grow>{ thread.participant.name }</Text>
|
||||
</Flex>
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}) }
|
||||
</Column>
|
||||
@ -138,19 +137,17 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
<>
|
||||
<Text bold center>{ LocalizeText('messenger.window.separator', [ 'FRIEND_NAME' ], [ activeThread.participant.name ]) }</Text>
|
||||
<Flex alignItems="center" justifyContent="between" gap={ 1 }>
|
||||
<Flex gap={ 1 }>
|
||||
<ButtonGroup>
|
||||
<Button onClick={ followFriend }>
|
||||
<Base className="nitro-friends-spritesheet icon-follow" />
|
||||
</Button>
|
||||
<Button onClick={ openProfile }>
|
||||
<Base className="nitro-friends-spritesheet icon-profile-sm" />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<Button variant="danger" onClick={ () => report(ReportType.IM, { reportedUserId: activeThread.participant.id }) }>
|
||||
{ LocalizeText('messenger.window.button.report') }
|
||||
</Button>
|
||||
</Flex>
|
||||
{activeThread && activeThread.participant.id > 0 && (
|
||||
<Flex gap={1}>
|
||||
<ButtonGroup>
|
||||
<Button onClick={followFriend}><Base className="nitro-friends-spritesheet icon-follow" /></Button>
|
||||
<Button onClick={openProfile}><Base className="nitro-friends-spritesheet icon-profile-sm" /></Button>
|
||||
</ButtonGroup>
|
||||
<Button variant="danger" onClick={() => report(ReportType.IM, { reportedUserId: activeThread.participant.id })}>
|
||||
{LocalizeText('messenger.window.button.report')}
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
<Button onClick={ event => closeThread(activeThread.threadId) }>
|
||||
<FaTimes className="fa-icon" />
|
||||
</Button>
|
||||
@ -172,4 +169,4 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
}
|
||||
}
|
@ -70,4 +70,4 @@ export const FriendsMessengerThreadGroup: FC<{ thread: MessengerThread, group: M
|
||||
</Base> }
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
@ -13,4 +13,4 @@ export const FriendsMessengerThreadView: FC<{ thread: MessengerThread }> = props
|
||||
{ (thread.groups.length > 0) && thread.groups.map((group, index) => <FriendsMessengerThreadGroup key={ index } thread={ thread } group={ group } />) }
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, Nitro
|
||||
import { useInventoryTrade, useInventoryUnseenTracker, useMessageEvent, useNitroEvent } from '../../hooks';
|
||||
import { InventoryBadgeView } from './views/badge/InventoryBadgeView';
|
||||
import { InventoryBotView } from './views/bot/InventoryBotView';
|
||||
import { InventoryFurnitureDeleteView } from './views/furniture/InventoryFurnitureDeleteView';
|
||||
import { InventoryFurnitureView } from './views/furniture/InventoryFurnitureView';
|
||||
import { InventoryTradeView } from './views/furniture/InventoryTradeView';
|
||||
import { InventoryPetView } from './views/pet/InventoryPetView';
|
||||
@ -142,6 +143,7 @@ export const InventoryView: FC<{}> = props =>
|
||||
{ (currentTab === TAB_BADGES ) &&
|
||||
<InventoryBadgeView /> }
|
||||
</NitroCardContentView>
|
||||
<InventoryFurnitureDeleteView />
|
||||
</> }
|
||||
{ isTrading &&
|
||||
<NitroCardContentView>
|
||||
|
@ -0,0 +1,96 @@
|
||||
import { DeleteItemMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useState } from 'react';
|
||||
import { FaCaretLeft, FaCaretRight } from 'react-icons/fa';
|
||||
import { FurnitureItem, LocalizeText, ProductTypeEnum, SendMessageComposer } from '../../../../api';
|
||||
import { Button, Column, Flex, Grid, LayoutFurniImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
||||
import { DeleteItemConfirmEvent } from '../../../../events';
|
||||
import { useNotification, useUiEvent } from '../../../../hooks';
|
||||
|
||||
export const InventoryFurnitureDeleteView : FC<{}> = props =>
|
||||
{
|
||||
const [ item, setItem ] = useState<FurnitureItem>(null);
|
||||
const [ amount, setAmount ] = useState(1);
|
||||
const [ tempAmount, setTempAmount ] = useState('1');
|
||||
const [ maxAmount, setMaxAmount ] = useState(1);
|
||||
|
||||
const updateAmount = (amnt: string) =>
|
||||
{
|
||||
let newValue: number = parseInt(amnt);
|
||||
|
||||
if(isNaN(newValue) || (newValue === amount)) return;
|
||||
|
||||
newValue = Math.max(newValue, 1);
|
||||
newValue = Math.min(newValue, maxAmount);
|
||||
|
||||
if(newValue === amount) return;
|
||||
|
||||
setTempAmount(newValue.toString());
|
||||
setAmount(newValue);
|
||||
}
|
||||
|
||||
const { showConfirm = null } = useNotification();
|
||||
|
||||
useUiEvent<DeleteItemConfirmEvent>(DeleteItemConfirmEvent.DELETE_ITEM_CONFIRM, event => {
|
||||
setItem(event.item);
|
||||
setMaxAmount(event.amount);
|
||||
});
|
||||
|
||||
if(!item) return null;
|
||||
|
||||
const getFurniTitle = (item ? LocalizeText(item.isWallItem ? 'wallItem.name.' + item.type : 'roomItem.name.' + item.type) : '');
|
||||
const getFurniDescription = (item ? LocalizeText(item.isWallItem ? 'wallItem.desc.' + item.type : 'roomItem.desc.' + item.type) : '');
|
||||
|
||||
const deleteItem = () =>
|
||||
{
|
||||
if(!item) return;
|
||||
|
||||
showConfirm(LocalizeText('inventory.delete.confirm_delete.info', [ 'furniname', 'amount' ], [ getFurniTitle, amount.toString() ]), () =>
|
||||
{
|
||||
SendMessageComposer(new DeleteItemMessageComposer(item.id, amount));
|
||||
setItem(null);
|
||||
setAmount(1);
|
||||
setMaxAmount(1);
|
||||
setTempAmount('1');
|
||||
},
|
||||
() =>
|
||||
{
|
||||
setItem(null);
|
||||
setAmount(1);
|
||||
setMaxAmount(1);
|
||||
setTempAmount('1');
|
||||
}, null, null, LocalizeText('inventory.delete.confirm_delete.title'));
|
||||
}
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-catalog-layout-marketplace-post-offer" theme="primary-slim">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('inventory.delete.confirm_delete.title') } onCloseClick={ event => { setItem(null); setAmount(1); setMaxAmount(1); setTempAmount('1'); } } />
|
||||
<NitroCardContentView overflow="hidden">
|
||||
<Grid fullHeight>
|
||||
<Column center className="bg-muted rounded p-2" size={ 4 } overflow="hidden">
|
||||
<LayoutFurniImageView productType={ item.isWallItem ? ProductTypeEnum.WALL : ProductTypeEnum.FLOOR } productClassId={ item.type } extraData={ item.extra.toString() } />
|
||||
</Column>
|
||||
<Column size={ 8 } justifyContent="between" overflow="hidden">
|
||||
<Column grow gap={ 1 }>
|
||||
<Text fontWeight="bold">{ getFurniTitle }</Text>
|
||||
<Text truncate shrink>{ getFurniDescription }</Text>
|
||||
</Column>
|
||||
<Column overflow="auto">
|
||||
<Text>{ LocalizeText('inventory.delete.amount') }</Text>
|
||||
<Flex alignItems="center" gap={ 1 }>
|
||||
<FaCaretLeft className="text-black cursor-pointer fa-icon" onClick={ event => updateAmount((amount - 1).toString()) } />
|
||||
<input className="form-control form-control-sm quantity-input" type="number" min={ 1 } max={ maxAmount } value={ tempAmount } onChange={ event => updateAmount(event.target.value) } placeholder={ LocalizeText('inventory.delete.amount') } />
|
||||
<FaCaretRight className="text-black cursor-pointer fa-icon" onClick={ event => updateAmount((amount + 1).toString()) } />
|
||||
<Button onClick={ event => updateAmount(maxAmount.toString()) }>
|
||||
{ LocalizeText('inventory.delete.max_amount.button') }
|
||||
</Button>
|
||||
</Flex>
|
||||
<Button disabled={ (amount > maxAmount) } onClick={ deleteItem }>
|
||||
{ LocalizeText('inventory.delete.confirm_delete.button') }
|
||||
</Button>
|
||||
</Column>
|
||||
</Column>
|
||||
</Grid>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
)
|
||||
}
|
@ -3,6 +3,8 @@ import { FC, useEffect, useState } from 'react';
|
||||
import { DispatchUiEvent, FurniCategory, GroupItem, LocalizeText, UnseenItemCategory, attemptItemPlacement } from '../../../../api';
|
||||
import { AutoGrid, Button, Column, Grid, LayoutLimitedEditionCompactPlateView, LayoutRarityLevelView, LayoutRoomPreviewerView, Text } from '../../../../common';
|
||||
import { CatalogPostMarketplaceOfferEvent } from '../../../../events';
|
||||
import { DeleteItemConfirmEvent } from '../../../../events';
|
||||
import { FaTrashAlt } from 'react-icons/fa'
|
||||
import { useInventoryFurni, useInventoryUnseenTracker } from '../../../../hooks';
|
||||
import { InventoryCategoryEmptyView } from '../InventoryCategoryEmptyView';
|
||||
import { InventoryFurnitureItemView } from './InventoryFurnitureItemView';
|
||||
@ -14,6 +16,11 @@ interface InventoryFurnitureViewProps
|
||||
roomPreviewer: RoomPreviewer;
|
||||
}
|
||||
|
||||
const FILTER_TYPE_EVERYTHING = 'inventory.filter.option.everything';
|
||||
const FILTER_TYPE_FLOOR = 'inventory.furni.tab.floor';
|
||||
const FILTER_TYPE_WALL = 'inventory.furni.tab.wall';
|
||||
const FILTER_TYPE_WIRED = 'inventory.furni.tab.wired';
|
||||
|
||||
const attemptPlaceMarketplaceOffer = (groupItem: GroupItem) =>
|
||||
{
|
||||
const item = groupItem.getLastItem();
|
||||
@ -25,6 +32,15 @@ const attemptPlaceMarketplaceOffer = (groupItem: GroupItem) =>
|
||||
DispatchUiEvent(new CatalogPostMarketplaceOfferEvent(item));
|
||||
}
|
||||
|
||||
const attemptDeleteItem = (groupItem: GroupItem) =>
|
||||
{
|
||||
const item = groupItem.getLastItem();
|
||||
|
||||
if(!item) return false;
|
||||
|
||||
DispatchUiEvent(new DeleteItemConfirmEvent(item, groupItem.getTotalCount()));
|
||||
}
|
||||
|
||||
export const InventoryFurnitureView: FC<InventoryFurnitureViewProps> = props =>
|
||||
{
|
||||
const { roomSession = null, roomPreviewer = null } = props;
|
||||
@ -32,6 +48,32 @@ export const InventoryFurnitureView: FC<InventoryFurnitureViewProps> = props =>
|
||||
const [ filteredGroupItems, setFilteredGroupItems ] = useState<GroupItem[]>([]);
|
||||
const { groupItems = [], selectedItem = null, activate = null, deactivate = null } = useInventoryFurni();
|
||||
const { resetItems = null } = useInventoryUnseenTracker();
|
||||
const [ filterType = string, setFilterType ] = useState(FILTER_TYPE_EVERYTHING);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const filteredItems = groupItems.filter(item =>
|
||||
{
|
||||
const isWallItem = item.isWallItem;
|
||||
const isFloorItem = !isWallItem;
|
||||
const isWiredItem = item.name.startsWith('WIRED');
|
||||
|
||||
switch (filterType)
|
||||
{
|
||||
case FILTER_TYPE_WALL:
|
||||
return isWallItem;
|
||||
case FILTER_TYPE_FLOOR:
|
||||
return isFloorItem;
|
||||
case FILTER_TYPE_WIRED:
|
||||
return isWiredItem;
|
||||
case FILTER_TYPE_EVERYTHING:
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
setFilteredGroupItems(filteredItems);
|
||||
}, [ groupItems, filterType ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
@ -114,6 +156,11 @@ export const InventoryFurnitureView: FC<InventoryFurnitureViewProps> = props =>
|
||||
<Grid>
|
||||
<Column size={ 7 } overflow="hidden">
|
||||
<InventoryFurnitureSearchView groupItems={ groupItems } setGroupItems={ setFilteredGroupItems } />
|
||||
<select className="form-select form-select-sm" value={ filterType } onChange={ e => setFilterType(e.target.value) }>
|
||||
{ [ FILTER_TYPE_EVERYTHING, FILTER_TYPE_FLOOR, FILTER_TYPE_WALL, FILTER_TYPE_WIRED ].map(type => (
|
||||
<option key={ type } value={ type }>{ LocalizeText(type) }</option>
|
||||
)) }
|
||||
</select>
|
||||
<AutoGrid columnCount={ 5 }>
|
||||
{ filteredGroupItems && (filteredGroupItems.length > 0) && filteredGroupItems.map((item, index) => <InventoryFurnitureItemView key={ index } groupItem={ item } />) }
|
||||
</AutoGrid>
|
||||
@ -121,6 +168,11 @@ export const InventoryFurnitureView: FC<InventoryFurnitureViewProps> = props =>
|
||||
<Column size={ 5 } overflow="auto">
|
||||
<Column overflow="hidden" position="relative">
|
||||
<LayoutRoomPreviewerView roomPreviewer={ roomPreviewer } height={ 140 } />
|
||||
{ selectedItem &&
|
||||
<Button variant="danger" className="bottom-2 end-2" position="absolute" onClick={ event => attemptDeleteItem(selectedItem) }>
|
||||
<FaTrashAlt className="fa-icon" />
|
||||
</Button>
|
||||
}
|
||||
{ selectedItem && selectedItem.stuffData.isUnique &&
|
||||
<LayoutLimitedEditionCompactPlateView className="top-2 end-2" position="absolute" uniqueNumber={ selectedItem.stuffData.uniqueNumber } uniqueSeries={ selectedItem.stuffData.uniqueSeries } /> }
|
||||
{ (selectedItem && selectedItem.stuffData.rarityLevel > -1) &&
|
||||
@ -128,7 +180,7 @@ export const InventoryFurnitureView: FC<InventoryFurnitureViewProps> = props =>
|
||||
</Column>
|
||||
{ selectedItem &&
|
||||
<Column grow justifyContent="between" gap={ 2 }>
|
||||
<Text grow truncate>{ selectedItem.name }</Text>
|
||||
<Text grow>{ selectedItem.name }</Text>
|
||||
<Column gap={ 1 }>
|
||||
{ !!roomSession &&
|
||||
<Button variant="success" onClick={ event => attemptItemPlacement(selectedItem) }>
|
||||
@ -143,4 +195,4 @@ export const InventoryFurnitureView: FC<InventoryFurnitureViewProps> = props =>
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,26 +1,28 @@
|
||||
.nitro-loading {
|
||||
position: relative;
|
||||
background: #1d1a24;
|
||||
background-image: radial-gradient(#1d1a24, #003a6b);
|
||||
z-index: 100;
|
||||
|
||||
.connecting-duck {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
right: 0;
|
||||
left: 0;
|
||||
background: url('@/assets/images/loading/caja.gif') no-repeat top left;
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
zoom: 1.5;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.nitro-logo {
|
||||
width: 47px;
|
||||
height: 65px;
|
||||
background: transparent url('https://assets.nitrodev.co/logos/react-loader.png') no-repeat center;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.connecting-duck {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
right: 0;
|
||||
left: 0;
|
||||
background: url('@/assets/images/loading/loading.gif') no-repeat top left;
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
zoom: 1.5;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.logo {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
background: url('@/assets/images/notifications/coolui.png') no-repeat top left; /* Fixed the typo here */
|
||||
width: 150px;
|
||||
height: 100px;
|
||||
}
|
@ -1,32 +1,30 @@
|
||||
import { FC } from 'react';
|
||||
import { Base, Column, LayoutProgressBar, Text } from '../../common';
|
||||
import { Base, Column, Text } from '../../common';
|
||||
|
||||
interface LoadingViewProps
|
||||
{
|
||||
isError: boolean;
|
||||
message: string;
|
||||
percent: number;
|
||||
}
|
||||
|
||||
export const LoadingView: FC<LoadingViewProps> = props =>
|
||||
{
|
||||
const { isError = false, message = '', percent = 0 } = props;
|
||||
const { isError = false, message = '' } = props;
|
||||
|
||||
return (
|
||||
<Column fullHeight position="relative" className="nitro-loading">
|
||||
<Base fullHeight className="container h-100">
|
||||
<Column fullHeight alignItems="center" justifyContent="end">
|
||||
<Base className="connecting-duck" />
|
||||
<Base className="logo" />
|
||||
<Column size={ 6 } className="text-center py-4">
|
||||
{ isError && (message && message.length) ?
|
||||
<Base className="fs-4 text-shadow">{ message }</Base>
|
||||
:
|
||||
<>
|
||||
<Text fontSize={ 4 } variant="white" className="text-shadow">The hotel is loading { percent.toFixed() }%...</Text>
|
||||
<LayoutProgressBar progress={ percent } className="mt-2 large" />
|
||||
<Text fontSize={ 4 } variant="white" className="text-shadow">The hotel is loading ...</Text>
|
||||
</>
|
||||
}
|
||||
|
||||
</Column>
|
||||
</Column>
|
||||
</Base>
|
||||
|
@ -35,6 +35,13 @@
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.nitro-alert-credits {
|
||||
width: 395px;
|
||||
.notification-text {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.nitro-alert-moderation,
|
||||
&.nitro-alert-alert {
|
||||
@ -57,3 +64,11 @@
|
||||
height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-coolui-logo {
|
||||
width: 150px;
|
||||
height: 78px;
|
||||
position: relative;
|
||||
background-image: url("@/assets/images/notifications/coolui.png");
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FC } from 'react';
|
||||
import { GetRendererVersion, GetUIVersion, NotificationAlertItem } from '../../../../api';
|
||||
import { Button, Column, Flex, Grid, LayoutNotificationAlertView, LayoutNotificationAlertViewProps, Text } from '../../../../common';
|
||||
import { Button, Column, Flex, Grid, LayoutNotificationCredits, LayoutNotificationAlertViewProps, Text } from '../../../../common';
|
||||
|
||||
interface NotificationDefaultAlertViewProps extends LayoutNotificationAlertViewProps
|
||||
{
|
||||
@ -9,15 +9,12 @@ interface NotificationDefaultAlertViewProps extends LayoutNotificationAlertViewP
|
||||
|
||||
export const NitroSystemAlertView: FC<NotificationDefaultAlertViewProps> = props =>
|
||||
{
|
||||
const { title = 'Nitro', onClose = null, ...rest } = props;
|
||||
const { title = 'Nitro CoolUI Edit', onClose = null, ...rest } = props;
|
||||
|
||||
return (
|
||||
<LayoutNotificationAlertView title={ title } onClose={ onClose } { ...rest }>
|
||||
<LayoutNotificationCredits title={ title } onClose={ onClose } { ...rest }>
|
||||
<Grid>
|
||||
<Column center size={ 5 }>
|
||||
<object data="https://assets.nitrodev.co/logos/nitro-n-dark.svg" width="100" height="100"> </object>
|
||||
</Column>
|
||||
<Column size={ 7 }>
|
||||
<Column size={ 12 }>
|
||||
<Column alignItems="center" gap={ 0 }>
|
||||
<Text bold fontSize={ 4 }>Nitro React</Text>
|
||||
<Text>v{ GetUIVersion() }</Text>
|
||||
@ -25,15 +22,29 @@ export const NitroSystemAlertView: FC<NotificationDefaultAlertViewProps> = props
|
||||
<Column alignItems="center">
|
||||
<Text><b>Renderer:</b> v{ GetRendererVersion() }</Text>
|
||||
<Column fullWidth gap={ 1 }>
|
||||
<Button fullWidth variant="success" onClick={ event => window.open('https://discord.nitrodev.co') }>Discord</Button>
|
||||
<Flex gap={ 1 }>
|
||||
<Button fullWidth onClick={ event => window.open('https://git.krews.org/nitro/nitro-react') }>Git</Button>
|
||||
<Button fullWidth onClick={ event => window.open('https://git.krews.org/nitro/nitro-react/-/issues') }>Bug Report</Button>
|
||||
</Flex>
|
||||
<Button fullWidth variant="success" onClick={ event => window.open('https://discord.nitrodev.co') }>Nitro Discord</Button>
|
||||
<Button fullWidth onClick={ event => window.open('https://git.krews.org/nitro/nitro-react') }>Nitro Git</Button>
|
||||
</Flex>
|
||||
</Column>
|
||||
</Column>
|
||||
</Column>
|
||||
<div className="nitro-coolui-logo"></div>
|
||||
<Column size={ 10 }>
|
||||
<Column alignItems="left" gap={ 0 }>
|
||||
<Text center bold fontSize={ 5 }>Cool UI</Text>
|
||||
<Text>Was created by Wassehk</Text>
|
||||
<Text>- DuckieTM (Re-Design)</Text>
|
||||
<Text>- Jonas (Contributing)</Text>
|
||||
<Text>- Ohlucas (Sunset resources)</Text>
|
||||
<Text center bold small>v2.2.0</Text>
|
||||
<Flex gap={ 1 }>
|
||||
<Button fullWidth variant="success" onClick={ event => window.open('https://discord.nitrodev.co') }>CoolUI Discord</Button>
|
||||
<Button fullWidth onClick={ event => window.open('https://github.com/duckietm/Nitro-Cool-UI') }>CoolUI Git</Button>
|
||||
</Flex>
|
||||
</Column>
|
||||
</Column>
|
||||
</Grid>
|
||||
</LayoutNotificationAlertView>
|
||||
</LayoutNotificationCredits>
|
||||
);
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
.nitro-room-tools-container {
|
||||
position: absolute;
|
||||
bottom: $toolbar-height + 65px;
|
||||
bottom: $toolbar-height + 25px;
|
||||
left: 0;
|
||||
|
||||
.nitro-room-tools {
|
||||
background: rgba($dark, .95);
|
||||
background: #212131;
|
||||
box-shadow: inset 0px 5px lighten(rgba($dark, .6), 2.5), inset 0 -4px darken(rgba($dark, .6), 4);
|
||||
border-top-right-radius: $border-radius;
|
||||
border-bottom-right-radius: $border-radius;
|
||||
transition: all .2s ease;
|
||||
z-index: 2;
|
||||
z-index: 71;
|
||||
margin-left: -20px;
|
||||
|
||||
.list-group-item {
|
||||
background: transparent;
|
||||
@ -45,14 +46,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-room-history {
|
||||
background: #212131;
|
||||
box-shadow: inset 0px 5px lighten(rgba($dark, .6), 2.5), inset 0 -4px darken(rgba($dark, .6), 4);
|
||||
transition: all .2s ease;
|
||||
width: 150px;
|
||||
overflow: hidden;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.nitro-room-tools-info {
|
||||
background: rgba($dark, .95);
|
||||
background: #212131;
|
||||
box-shadow: inset 0px 5px lighten(rgba($dark, .6), 2.5), inset 0 -4px darken(rgba($dark, .6), 4);
|
||||
transition: all .2s ease;
|
||||
max-width: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-room-tools-history {
|
||||
position: absolute;
|
||||
left: calc(100% - 2px);
|
||||
margin-left: 2px;
|
||||
height: 5%;
|
||||
}
|
||||
|
||||
.wordquiz-question {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
@ -99,24 +116,49 @@
|
||||
height: $nitro-doorbell-height;
|
||||
}
|
||||
|
||||
.nitro-room-tools-history {
|
||||
background: #212131;
|
||||
box-shadow: inset 0 5px rgba(38, 38, 57, 0.6), inset 0 -4px rgba(25, 25, 37, 0.6);
|
||||
border-radius: 5px;
|
||||
transition: all 0.2s ease;
|
||||
margin-left: 3px;
|
||||
max-height: 104px;
|
||||
overflow-y: auto;
|
||||
z-index: 5;
|
||||
padding-left: 6px;
|
||||
.toggle-roomtool {
|
||||
min-height: 95px;
|
||||
width: 20px;
|
||||
margin-left: -5px;
|
||||
padding-left: 10px;
|
||||
z-index: 72;
|
||||
}
|
||||
|
||||
.nitro-room-history-rooms {
|
||||
position: fixed;
|
||||
transition: all 0.2s ease;
|
||||
max-height: 40px;
|
||||
margin-left: 2px;
|
||||
bottom: 75px;
|
||||
.room-tool-item {
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.margin-icons {
|
||||
margin-top: -14px;
|
||||
}
|
||||
|
||||
.margin-button-history {
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.text-no-promote {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.icon-style {
|
||||
margin-top: 1px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.arrow-right-style {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.tags {
|
||||
background-color: #1B2834;
|
||||
color: #1A75A6;
|
||||
font-size: 10px;
|
||||
|
||||
&:hover {
|
||||
color: #419AD2;
|
||||
}
|
||||
}
|
||||
|
||||
@import './avatar-info/AvatarInfoWidgetView';
|
||||
@ -127,4 +169,4 @@
|
||||
@import './friend-request/FriendRequestDialogView';
|
||||
@import './furniture/FurnitureWidgets';
|
||||
@import './mysterybox/MysteryBoxExtensionView';
|
||||
@import './pet-package/PetPackageWidgetView';
|
||||
@import './pet-package/PetPackageWidgetView';
|
@ -686,6 +686,186 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble-39 {
|
||||
border-image-source: url("@/assets/images/chat/chatbubbles/bubble_39.png");
|
||||
|
||||
border-image-slice: 16 6 7 32 fill;
|
||||
border-image-width: 16px 6px 7px 32px;
|
||||
border-image-outset: 5px 0px 0px 3px;
|
||||
|
||||
.pointer {
|
||||
background: url("@/assets/images/chat/chatbubbles/bubble_39_pointer.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble-40 {
|
||||
border-image-source: url("@/assets/images/chat/chatbubbles/bubble_40.png");
|
||||
|
||||
border-image-slice: 16 6 7 32 fill;
|
||||
border-image-width: 16px 6px 7px 32px;
|
||||
border-image-outset: 5px 0px 0px 3px;
|
||||
|
||||
.pointer {
|
||||
background: url("@/assets/images/chat/chatbubbles/bubble_40_pointer.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble-41 {
|
||||
border-image-source: url("@/assets/images/chat/chatbubbles/bubble_41.png");
|
||||
|
||||
border-image-slice: 16 7 6 33 fill;
|
||||
border-image-width: 16px 6px 6px 32px;
|
||||
border-image-outset: 7px 0px 0px 5px;
|
||||
|
||||
.pointer {
|
||||
background: url("@/assets/images/chat/chatbubbles/bubble_41_pointer.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble-42 {
|
||||
border-image-source: url("@/assets/images/chat/chatbubbles/bubble_42.png");
|
||||
|
||||
border-image-slice: 16 7 6 33 fill;
|
||||
border-image-width: 16px 6px 6px 32px;
|
||||
border-image-outset: 7px 0px 0px 5px;
|
||||
|
||||
.pointer {
|
||||
background: url("@/assets/images/chat/chatbubbles/bubble_42_pointer.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble-43 {
|
||||
border-image-source: url("@/assets/images/chat/chatbubbles/bubble_43.png");
|
||||
|
||||
border-image-slice: 16 7 6 33 fill;
|
||||
border-image-width: 16px 6px 6px 32px;
|
||||
border-image-outset: 7px 0px 0px 5px;
|
||||
|
||||
.pointer {
|
||||
background: url("@/assets/images/chat/chatbubbles/bubble_43_pointer.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble-44 {
|
||||
border-image-source: url("@/assets/images/chat/chatbubbles/bubble_44.png");
|
||||
|
||||
border-image-slice: 16 7 6 33 fill;
|
||||
border-image-width: 16px 6px 6px 32px;
|
||||
border-image-outset: 7px 0px 0px 5px;
|
||||
|
||||
.pointer {
|
||||
background: url("@/assets/images/chat/chatbubbles/bubble_44_pointer.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble-45 {
|
||||
border-image-source: url("@/assets/images/chat/chatbubbles/bubble_45.png");
|
||||
|
||||
border-image-slice: 16 7 6 33 fill;
|
||||
border-image-width: 16px 6px 6px 32px;
|
||||
border-image-outset: 7px 0px 0px 5px;
|
||||
|
||||
.pointer {
|
||||
background: url("@/assets/images/chat/chatbubbles/bubble_45_pointer.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble-46 {
|
||||
border-image-source: url("@/assets/images/chat/chatbubbles/bubble_46.png");
|
||||
|
||||
border-image-slice: 16 7 6 33 fill;
|
||||
border-image-width: 16px 6px 6px 32px;
|
||||
border-image-outset: 7px 0px 0px 5px;
|
||||
|
||||
.pointer {
|
||||
background: url("@/assets/images/chat/chatbubbles/bubble_46_pointer.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble-47 {
|
||||
border-image-source: url("@/assets/images/chat/chatbubbles/bubble_47.png");
|
||||
|
||||
border-image-slice: 16 7 6 33 fill;
|
||||
border-image-width: 16px 6px 6px 32px;
|
||||
border-image-outset: 7px 0px 0px 5px;
|
||||
|
||||
.pointer {
|
||||
background: url("@/assets/images/chat/chatbubbles/bubble_47_pointer.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble-48 {
|
||||
border-image-source: url("@/assets/images/chat/chatbubbles/bubble_48.png");
|
||||
|
||||
border-image-slice: 16 7 6 33 fill;
|
||||
border-image-width: 16px 6px 6px 32px;
|
||||
border-image-outset: 7px 0px 0px 5px;
|
||||
|
||||
.pointer {
|
||||
background: url("@/assets/images/chat/chatbubbles/bubble_48_pointer.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble-49 {
|
||||
border-image-source: url("@/assets/images/chat/chatbubbles/bubble_49.png");
|
||||
|
||||
border-image-slice: 16 7 6 33 fill;
|
||||
border-image-width: 16px 6px 6px 32px;
|
||||
border-image-outset: 7px 0px 0px 5px;
|
||||
|
||||
.pointer {
|
||||
background: url("@/assets/images/chat/chatbubbles/bubble_49_pointer.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble-50 {
|
||||
border-image-source: url("@/assets/images/chat/chatbubbles/bubble_50.png");
|
||||
|
||||
border-image-slice: 16 7 6 33 fill;
|
||||
border-image-width: 16px 6px 6px 32px;
|
||||
border-image-outset: 7px 0px 0px 5px;
|
||||
|
||||
.pointer {
|
||||
background: url("@/assets/images/chat/chatbubbles/bubble_50_pointer.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble-51 {
|
||||
border-image-source: url("@/assets/images/chat/chatbubbles/bubble_51.png");
|
||||
|
||||
border-image-slice: 16 7 6 33 fill;
|
||||
border-image-width: 16px 6px 6px 32px;
|
||||
border-image-outset: 7px 0px 0px 5px;
|
||||
|
||||
.pointer {
|
||||
background: url("@/assets/images/chat/chatbubbles/bubble_51_pointer.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble-52 {
|
||||
border-image-source: url("@/assets/images/chat/chatbubbles/bubble_52.png");
|
||||
|
||||
border-image-slice: 16 7 6 33 fill;
|
||||
border-image-width: 16px 6px 6px 32px;
|
||||
border-image-outset: 7px 0px 0px 5px;
|
||||
|
||||
.pointer {
|
||||
background: url("@/assets/images/chat/chatbubbles/bubble_52_pointer.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble-53 {
|
||||
border-image-source: url("@/assets/images/chat/chatbubbles/bubble_53.png");
|
||||
|
||||
border-image-slice: 16 7 6 33 fill;
|
||||
border-image-width: 16px 6px 6px 32px;
|
||||
border-image-outset: 7px 0px 0px 5px;
|
||||
|
||||
.pointer {
|
||||
background: url("@/assets/images/chat/chatbubbles/bubble_53_pointer.png");
|
||||
}
|
||||
}
|
||||
|
||||
.user-container {
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
@ -916,4 +1096,64 @@
|
||||
background: url('@/assets/images/chat/chatbubbles/bubble_38_extra.png');
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble-39 {
|
||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_39.png");
|
||||
}
|
||||
|
||||
&.bubble-40 {
|
||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_40.png");
|
||||
}
|
||||
|
||||
&.bubble-41 {
|
||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_41.png");
|
||||
}
|
||||
|
||||
&.bubble-42 {
|
||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_42.png");
|
||||
}
|
||||
|
||||
&.bubble-43 {
|
||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_43.png");
|
||||
}
|
||||
|
||||
&.bubble-44 {
|
||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_44.png");
|
||||
}
|
||||
|
||||
&.bubble-45 {
|
||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_45.png");
|
||||
}
|
||||
|
||||
&.bubble-46 {
|
||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_46.png");
|
||||
}
|
||||
|
||||
&.bubble-47 {
|
||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_47.png");
|
||||
}
|
||||
|
||||
&.bubble-48 {
|
||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_48.png");
|
||||
}
|
||||
|
||||
&.bubble-49 {
|
||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_49.png");
|
||||
}
|
||||
|
||||
&.bubble-50 {
|
||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_50.png");
|
||||
}
|
||||
|
||||
&.bubble-51 {
|
||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_51.png");
|
||||
}
|
||||
|
||||
&.bubble-52 {
|
||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_52.png");
|
||||
}
|
||||
|
||||
&.bubble-53 {
|
||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_53.png");
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,24 @@
|
||||
import { CreateLinkEvent, GetGuestRoomResultEvent, GetRoomEngine, NavigatorSearchComposer, RateFlatMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { CreateLinkEvent, GetGuestRoomResultEvent, GetRoomEngine, NavigatorSearchComposer, RateFlatMessageComposer, RoomDataParser } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText, SendMessageComposer, TryVisitRoom } from '../../../../api';
|
||||
import { LocalizeText, SendMessageComposer, SetLocalStorage, TryVisitRoom } from '../../../../api';
|
||||
import { Base, Column, Flex, Text, TransitionAnimation, TransitionAnimationTypes, classNames } from '../../../../common';
|
||||
import { useMessageEvent, useNavigator, useRoom } from '../../../../hooks';
|
||||
|
||||
export const RoomToolsWidgetView: FC<{}> = props =>
|
||||
{
|
||||
const [ areBubblesMuted, setAreBubblesMuted ] = useState(false);
|
||||
const [ isZoomedIn, setIsZoomedIn ] = useState<boolean>(false);
|
||||
const [ roomName, setRoomName ] = useState<string>(null);
|
||||
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 { navigatorData = null } = useNavigator();
|
||||
const [ roomHistory, setRoomHistory ] = useState<{ roomId: number, roomName: string }[]>([]);
|
||||
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();
|
||||
const [areBubblesMuted, setAreBubblesMuted] = useState(false);
|
||||
|
||||
useEffect(() => { if (!roomName) { setRoomName(LocalizeText('landing.view.generic.welcome.first_login')); } }, [roomName]);
|
||||
|
||||
const handleToolClick = (action: string, value?: string) =>
|
||||
{
|
||||
@ -46,6 +49,11 @@ export const RoomToolsWidgetView: FC<{}> = props =>
|
||||
if (bubbleElement) {
|
||||
bubbleElement.classList.toggle('icon-chat-disablebubble');
|
||||
}
|
||||
const hiddenbubblesTextElement = document.getElementById('hiddenbubblesText');
|
||||
if (hiddenbubblesTextElement) {
|
||||
const newText = areBubblesMuted ? LocalizeText('room.unmute.button.text') : LocalizeText('room.mute.button.text');
|
||||
hiddenbubblesTextElement.innerText = newText;
|
||||
}
|
||||
setAreBubblesMuted(!areBubblesMuted);
|
||||
const bubbleIcon = document.getElementById('bubbleIcon');
|
||||
if (bubbleIcon) {
|
||||
@ -62,54 +70,60 @@ export const RoomToolsWidgetView: FC<{}> = props =>
|
||||
CreateLinkEvent(`navigator/search/${ value }`);
|
||||
SendMessageComposer(new NavigatorSearchComposer('hotel_view', `tag:${ value }`));
|
||||
return;
|
||||
case 'room_history':
|
||||
if (roomHistory.length > 0) {
|
||||
const roomHistoryTool = document.getElementById("roomhistorytool");
|
||||
if (!isOpenHistory) {
|
||||
roomHistoryTool.style.display = "block";
|
||||
setIsOpenHistory(true);
|
||||
} else {
|
||||
setIsOpenHistory(false);
|
||||
roomHistoryTool.style.display = "none";
|
||||
}
|
||||
}
|
||||
return;
|
||||
case 'room_history_back':
|
||||
TryVisitRoom(roomHistory[roomHistory.findIndex(room => room.roomId === navigatorData.currentRoomId) - 1].roomId)
|
||||
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;
|
||||
TryVisitRoom(roomHistory[roomHistory.findIndex(room => room.roomId === navigatorData.currentRoomId) + 1].roomId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const onChangeRoomHistory = (roomId: number, roomName: string) => {
|
||||
const newStorage = JSON.parse(window.localStorage.getItem('nitro.room.history')) || [];
|
||||
|
||||
if (newStorage.some(room => room.roomId === roomId)) return;
|
||||
const onChangeRoomHistory = (roomId: number, roomName: string) =>
|
||||
{
|
||||
let newStorage = JSON.parse(window.localStorage.getItem('nitro.room.history'));
|
||||
|
||||
if (newStorage.length >= 10) newStorage.shift();
|
||||
if (newStorage && newStorage.filter( (room: RoomDataParser) => room.roomId === roomId ).length > 0) return;
|
||||
|
||||
const newData = [...newStorage, { roomId, roomName }];
|
||||
if (newStorage && newStorage.length >= 10) newStorage.shift();
|
||||
|
||||
const newData = !newStorage ? [ { roomId: roomId, roomName: roomName } ] : [ ...newStorage, { roomId: roomId, roomName: roomName } ];
|
||||
|
||||
setRoomHistory(newData);
|
||||
SetLocalStorage('nitro.room.history', newData);
|
||||
};
|
||||
return SetLocalStorage('nitro.room.history', newData );
|
||||
}
|
||||
|
||||
useMessageEvent<GetGuestRoomResultEvent>(GetGuestRoomResultEvent, event => {
|
||||
CreateLinkEvent('nitrobubblehidden/hide');
|
||||
useMessageEvent<GetGuestRoomResultEvent>(GetGuestRoomResultEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if (!parser.roomEnter || (parser.data.roomId !== roomSession.roomId)) return;
|
||||
if(!parser.roomEnter || (parser.data.roomId !== roomSession.roomId)) return;
|
||||
|
||||
const { roomName, ownerName, tags } = parser.data;
|
||||
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);
|
||||
|
||||
if (roomName !== roomSession.roomName) { setRoomName(roomName); }
|
||||
if (ownerName !== roomSession.ownerName) { setRoomOwner(ownerName); }
|
||||
if (JSON.stringify(tags) !== JSON.stringify(roomSession.tags)) { setRoomTags(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);
|
||||
@ -117,58 +131,86 @@ export const RoomToolsWidgetView: FC<{}> = props =>
|
||||
const timeout = setTimeout(() => setIsOpen(false), 5000);
|
||||
|
||||
return () => clearTimeout(timeout);
|
||||
}, [ roomName, roomOwner, roomTags ]);
|
||||
|
||||
useEffect(() => {
|
||||
setRoomHistory(JSON.parse(window.localStorage.getItem('nitro.room.history')) || []);
|
||||
}, []);
|
||||
}, [ 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={areBubblesMuted ? LocalizeText('room.unmute.button.text') : LocalizeText('room.mute.button.text')} className={areBubblesMuted ? "iconleftgen icon icon-chat-disablebubble" : "iconleftgen icon icon-chat-enablebubble"} onClick={ () => handleToolClick('hiddenbubbles') } />
|
||||
<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>
|
||||
</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 className="nitro-room-history-rooms" justifyContent="bottom">
|
||||
<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>
|
||||
<div className="nitro-room-tools-history" id="roomhistorytool" style={ { display: "none",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>
|
||||
<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 gap-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" />
|
||||
<Base pointer onClick={ () => handleToolClick('hiddenbubbles') } className={`icon ${areBubblesMuted ? 'icon-chat-disablebubble' : 'icon-chat-enablebubble'}`} />
|
||||
</Column>
|
||||
<Column className="d-flex flex-column">
|
||||
<Flex className="w-100 room-tool-item">
|
||||
<Text variant="muted" underline small onClick={ () => handleToolClick('settings') }>{ LocalizeText('room.settings.button.text') }</Text>
|
||||
</Flex>
|
||||
<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>
|
||||
<Flex className="w-100 room-tool-item">
|
||||
<Text variant="muted" underline small onClick={() => handleToolClick('hiddenbubbles')}> {areBubblesMuted ? LocalizeText('room.unmute.button.text') : LocalizeText('room.mute.button.text')}</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>
|
||||
<Flex className="nitro-room-tools-history" style={ { bottom: !navigatorData.canRate ? '180px' : '210px' } }>
|
||||
<TransitionAnimation type={ TransitionAnimationTypes.SLIDE_LEFT } inProp={ isOpenHistory }>
|
||||
<Column center>
|
||||
<Column className="px-3 py-2 rounded nitro-room-history">
|
||||
<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="px-3 py-2 rounded nitro-room-tools-info" overflow="hidden">
|
||||
<Column gap={1}> <Text wrap variant="white" fontSize={4} truncate>{roomName}</Text> <Text variant="muted" fontSize={5} truncate>{roomOwner}</Text> </Column>
|
||||
{roomTags && roomTags.length > 0 ? (
|
||||
<Flex gap={2}> {roomTags.map((tag, index) => ( <Text key={index} small pointer truncate variant="white" className="rounded bg-primary p-1" onClick={() => handleToolClick('navigator_search_tag', tag)} > #{tag} </Text> ))}
|
||||
</Flex> ) : ( <Text variant="muted"> { LocalizeText('navigator.notagsfound') } </Text> )}
|
||||
</Column>
|
||||
</Column>
|
||||
</TransitionAnimation>
|
||||
</Column>
|
||||
</>
|
||||
) }
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
27
src/events/inventory/DeleteItemConfirmEvent.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { FurnitureItem } from '../../api';
|
||||
import { NitroEvent } from '@nitrots/nitro-renderer';
|
||||
|
||||
export class DeleteItemConfirmEvent extends NitroEvent
|
||||
{
|
||||
public static DELETE_ITEM_CONFIRM = 'DIC_DELETE_ITEM_CONFIRM';
|
||||
|
||||
private _item: FurnitureItem;
|
||||
private _amount: number;
|
||||
|
||||
constructor(item: FurnitureItem, amount: number)
|
||||
{
|
||||
super(DeleteItemConfirmEvent.DELETE_ITEM_CONFIRM);
|
||||
this._item = item;
|
||||
this._amount = amount;
|
||||
}
|
||||
|
||||
public get item(): FurnitureItem
|
||||
{
|
||||
return this._item;
|
||||
}
|
||||
|
||||
public get amount(): number
|
||||
{
|
||||
return this._amount;
|
||||
}
|
||||
}
|
@ -1 +1,2 @@
|
||||
export * from './InventoryFurniAddedEvent';
|
||||
export * from './DeleteItemConfirmEvent';
|
||||
export * from './InventoryFurniAddedEvent';
|
51
tailwind.config.js
Normal file
@ -0,0 +1,51 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
|
||||
const { generateShades } = require('./css-utils/CSSColorUtils');
|
||||
|
||||
const colors = {
|
||||
'toolbar': '#555555',
|
||||
'card-header': '#1E7295',
|
||||
'card-close': '#921911',
|
||||
'card-tabs': '#185D79',
|
||||
'card-border': '#283F5D',
|
||||
'card-tab-item': '#B6BEC5',
|
||||
'card-tab-item-active': '#DFDFDF',
|
||||
'card-content-area': '#DFDFDF'
|
||||
};
|
||||
|
||||
const boxShadow = {
|
||||
'inner1px': 'inset 0 0 0 1px rgba(255,255,255,.3)'
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: [ 'Ubuntu', 'sans-serif' ],
|
||||
},
|
||||
colors: generateShades(colors),
|
||||
boxShadow,
|
||||
spacing: {
|
||||
'card-header': '33px',
|
||||
'card-tabs': '33px',
|
||||
'navigator-w': '420px',
|
||||
'navigator-h': '440px',
|
||||
'inventory-w': '528px',
|
||||
'inventory-h': '320px'
|
||||
},
|
||||
zIndex: {
|
||||
'toolbar': ''
|
||||
}
|
||||
},
|
||||
},
|
||||
darkMode: 'class',
|
||||
plugins: [
|
||||
require('@tailwindcss/forms'),
|
||||
require('@headlessui/tailwindcss')({ prefix: 'ui' })
|
||||
],
|
||||
content: [
|
||||
'./index.html',
|
||||
'./src/**/*.{html,js,jsx,ts,tsx}'
|
||||
]
|
||||
}
|
@ -20,7 +20,10 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
"jsx": "react-jsx",
|
||||
"paths": {
|
||||
"@layout/*": ["layout/*"],
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
|
@ -2,9 +2,10 @@
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { resolve } from 'path';
|
||||
import { defineConfig } from 'vite';
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [ react() ],
|
||||
plugins: [ react(), tsconfigPaths() ],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src'),
|
||||
|