This commit is contained in:
laynester 2024-04-02 23:53:44 -04:00
parent 81702e1a58
commit da9aba9867
59 changed files with 829 additions and 320 deletions

View File

@ -6,9 +6,9 @@
"typescript.format.placeOpenBraceOnNewLineForFunctions": true, "typescript.format.placeOpenBraceOnNewLineForFunctions": true,
"editor.wordWrap": "on", "editor.wordWrap": "on",
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true, "source.fixAll.eslint": "explicit",
"source.fixAll.sortJSON": false, "source.fixAll.sortJSON": "never",
"source.organizeImports": true "source.organizeImports": "explicit"
}, },
"editor.formatOnSave": false, "editor.formatOnSave": false,
"git.ignoreLimitWarning": true, "git.ignoreLimitWarning": true,

View File

@ -51,8 +51,8 @@ $nitro-widget-crafting-height: 300px;
$chat-history-width: 300px; $chat-history-width: 300px;
$chat-history-height: 300px; $chat-history-height: 300px;
$friends-list-width: 250px; $friends-list-width: 275px;
$friends-list-height: 300px; $friends-list-height: 325px;
$help-width: 450px; $help-width: 450px;
$help-height: 290px; $help-height: 290px;

View File

@ -22,6 +22,7 @@ export const App: FC<{}> = props =>
if(!NitroConfig) throw new Error('NitroConfig is not defined!'); if(!NitroConfig) throw new Error('NitroConfig is not defined!');
Nitro.bootstrap(); Nitro.bootstrap();
Nitro.instance.application.renderer.backgroundColor = 0xFFFFFF;
} }
const handler = useCallback(async (event: NitroEvent) => const handler = useCallback(async (event: NitroEvent) =>

View File

@ -62,4 +62,42 @@ export class ColorUtils
return 'rgba(' + [ r, g, b, 1 ].join(',') + ')'; return 'rgba(' + [ r, g, b, 1 ].join(',') + ')';
} }
public static hslToHex(h, s, l)
{
// Convert HSL to RGB first
let r, g, b;
if (s === 0)
{
r = g = b = l; // achromatic
}
else
{
const hue2rgb = (p, q, t) =>
{
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
// Convert RGB to hexadecimal
const toHex = (c) =>
{
const hex = Math.round(c * 255).toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return `#${ toHex(r) }${ toHex(g) }${ toHex(b) }`;
}
} }

View File

@ -2,5 +2,5 @@ export function LocalizeFormattedNumber(number: number): string
{ {
if(!number || isNaN(number)) return '0'; if(!number || isNaN(number)) return '0';
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' '); return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}; };

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -15,7 +15,6 @@
cursor: if($enable-button-pointers, pointer, null); cursor: if($enable-button-pointers, pointer, null);
user-select: none; user-select: none;
background-color: transparent; background-color: transparent;
border: $btn-border-width solid transparent;
@include button-size($btn-padding-y, $btn-padding-x, $btn-font-size, $btn-border-radius); @include button-size($btn-padding-y, $btn-padding-x, $btn-font-size, $btn-border-radius);
@include transition($btn-transition); @include transition($btn-transition);

View File

@ -168,7 +168,7 @@ $_luminance-list: .0008 .001 .0011 .0013 .0015 .0017 .002 .0022 .0025 .0027 .003
$l1: luminance($background); $l1: luminance($background);
$l2: luminance(opaque($background, $foreground)); $l2: luminance(opaque($background, $foreground));
@return if($l1 > $l2, divide($l1 + .05, $l2 + .05), divide($l2 + .05, $l1 + .05)); @return if($l1 > $l2, divide($l1 + .01, $l2 + .01), divide($l2 + .01, $l1 + .01));
} }
// Return WCAG2.0 relative luminance // Return WCAG2.0 relative luminance

View File

@ -51,7 +51,7 @@ body {
line-height: var(--#{$variable-prefix}body-line-height); line-height: var(--#{$variable-prefix}body-line-height);
color: var(--#{$variable-prefix}body-color); color: var(--#{$variable-prefix}body-color);
text-align: var(--#{$variable-prefix}body-text-align); text-align: var(--#{$variable-prefix}body-text-align);
background-color: var(--#{$variable-prefix}body-bg); // 2 background-color: #001d2a; // 2
-webkit-text-size-adjust: 100%; // 3 -webkit-text-size-adjust: 100%; // 3
-webkit-tap-highlight-color: rgba($black, 0); // 4 -webkit-tap-highlight-color: rgba($black, 0); // 4
} }

View File

@ -42,7 +42,7 @@ $purple: #6f42c1 !default;
$pink: #d63384 !default; $pink: #d63384 !default;
$red: #a81a12 !default; $red: #a81a12 !default;
$orange: #fd7e14 !default; $orange: #fd7e14 !default;
$yellow: #ffc107 !default; $yellow: #fcbe00 !default;
$green: #00800b !default; $green: #00800b !default;
$teal: #20c997 !default; $teal: #20c997 !default;
$cyan: #0dcaf0 !default; $cyan: #0dcaf0 !default;
@ -70,10 +70,10 @@ $colors: (
// scss-docs-end colors-map // scss-docs-end colors-map
// scss-docs-start theme-color-variables // scss-docs-start theme-color-variables
$primary: #1E7295 !default; $primary: #3986b1 !default;
$secondary: #185D79 !default; $secondary: darken($primary,10) !default;
$tertiary: #2DABC2 !default; $tertiary: $primary !default;
$quaternary: #2B91A7 !default; $quaternary: darken($primary,10) !default;
$mirage: #131e25 !default; $mirage: #131e25 !default;
$aztec: #0d171d !default; $aztec: #0d171d !default;
$cello-light: #21516e !default; $cello-light: #21516e !default;
@ -91,9 +91,13 @@ $info: $cyan !default;
$warning: $yellow !default; $warning: $yellow !default;
$danger: $red !default; $danger: $red !default;
$light: #DFDFDF !default; $f-grey: #555555;
$dark: rgba(28,28,32,.9803921568627451) !default; $f-grey-sec: #444444;
$light: #e3e3d8 !default;
$dark: $f-grey-sec !default;
$light-dark: $gray-800 !default; $light-dark: $gray-800 !default;
// scss-docs-end theme-color-variables // scss-docs-end theme-color-variables
$bg-tertiary-split-background: repeating-linear-gradient($tertiary, $tertiary 50%, $quaternary 50%, $quaternary 100%) !default; $bg-tertiary-split-background: repeating-linear-gradient($tertiary, $tertiary 50%, $quaternary 50%, $quaternary 100%) !default;
@ -127,7 +131,8 @@ $theme-colors: (
"black": $black, "black": $black,
"muted": $muted, "muted": $muted,
"purple": $purple, "purple": $purple,
"gainsboro": $gainsboro "gainsboro": $gainsboro,
'f-grey': $f-grey
) !default; ) !default;
// scss-docs-end theme-colors-map // scss-docs-end theme-colors-map
@ -560,11 +565,11 @@ $border-widths: (
5: 5px 5: 5px
) !default; ) !default;
$border-color: #283F5D !default; $border-color: black !default;
// scss-docs-end border-variables // scss-docs-end border-variables
// scss-docs-start border-radius-variables // scss-docs-start border-radius-variables
$border-radius: .25rem !default; $border-radius: .30rem !default;
$border-radius-sm: .2rem !default; $border-radius-sm: .2rem !default;
$border-radius-lg: .3rem !default; $border-radius-lg: .3rem !default;
$border-radius-pill: 50rem !default; $border-radius-pill: 50rem !default;
@ -619,7 +624,7 @@ $font-family-code: var(--#{$variable-prefix}font-monospace) !default;
// $font-size-root affects the value of `rem`, which is used for as well font sizes, paddings, and margins // $font-size-root affects the value of `rem`, which is used for as well font sizes, paddings, and margins
// $font-size-base affects the font size of the body text // $font-size-base affects the font size of the body text
$font-size-root: null !default; $font-size-root: null !default;
$font-size-base: .9rem !default; // Assumes the browser default, typically `16px` $font-size-base: .75rem !default; // Assumes the browser default, typically `16px`
$font-size-sm: $font-size-base * .875 !default; $font-size-sm: $font-size-base * .875 !default;
$font-size-lg: $font-size-base * 1.25 !default; $font-size-lg: $font-size-base * 1.25 !default;
@ -1129,7 +1134,7 @@ $nav-link-hover-color: null !default;
$nav-link-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out !default; $nav-link-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out !default;
$nav-link-disabled-color: $gray-600 !default; $nav-link-disabled-color: $gray-600 !default;
$nav-tabs-border-color: #283F5D !default; $nav-tabs-border-color: black !default;
$nav-tabs-border-width: $border-width !default; $nav-tabs-border-width: $border-width !default;
$nav-tabs-border-radius: $border-radius !default; $nav-tabs-border-radius: $border-radius !default;
$nav-tabs-link-bg: $muted !default; $nav-tabs-link-bg: $muted !default;

View File

@ -20,13 +20,31 @@
) { ) {
color: $color; color: $color;
@include gradient-bg($background); @include gradient-bg($background);
border-color: $border; border: 1px solid darken($background, 20%);
@include box-shadow($btn-box-shadow); box-shadow: inset 0 -2px darken($background, 5%);
filter: drop-shadow(0px 1px 0 rgba(white,.3)) drop-shadow(0px -1px 0 rgba(black,.2));
position: relative;
&:hover { &::before {
color: $hover-color; content:'';
@include gradient-bg($hover-background); height:2px;
border-color: $hover-border; position: absolute;
top:0;
left:0;
right:0;
margin:1px;
border-radius: 1px;
background: lighten($background, 10%);
}
&:hover,
&.inset {
background: darken($background, 10%);
box-shadow: inset 0 2px darken($background, 15%);
&::before {
height: 0;
}
} }
.btn-check:focus + &, .btn-check:focus + &,
@ -83,12 +101,27 @@
$active-color: color-contrast($active-background) $active-color: color-contrast($active-background)
) { ) {
color: $color; color: $color;
border-color: $color; border: 1px solid $color;
border-radius: 0 !important;
box-shadow: none;
filter:unset !important;
position: relative;
&:hover { &:hover {
color: $color-hover; color: $color-hover;
background-color: $active-background;
border-color: $active-border; border-color: $active-border;
&::before {
content:'';
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin:1px;
background-color: $active-background;
z-index: -2;
}
} }
.btn-check:focus + &, .btn-check:focus + &,

View File

@ -1,4 +1,4 @@
@font-face { @font-face {
font-family: Ubuntu; font-family: Ubuntu;
src: url('@/assets/webfonts/Ubuntu-C.ttf'); src: url('@/assets/webfonts/Ubuntu.ttf');
} }

View File

@ -25,8 +25,8 @@
&.icon-catalog { &.icon-catalog {
background-image: url('@/assets/images/toolbar/icons/catalog.png'); background-image: url('@/assets/images/toolbar/icons/catalog.png');
width: 37px; width: 33px;
height: 36px; height: 33px;
} }
&.icon-game { &.icon-game {
@ -53,8 +53,8 @@
&.icon-inventory { &.icon-inventory {
background-image: url('@/assets/images/toolbar/icons/inventory.png'); background-image: url('@/assets/images/toolbar/icons/inventory.png');
height: 41px; height: 29px;
width: 44px; width: 28px;
} }
&.icon-modtools { &.icon-modtools {
@ -367,6 +367,25 @@
height: 22px; height: 22px;
} }
&.icon-search {
background: url('@/assets/images/fusion/friends/search.png');
width: 18px;
height: 18px;
}
&.icon-online-friends {
background: url('@/assets/images/fusion/friends/online.png');
width: 18px;
height: 18px;
}
&.icon-offline-friends {
background: url('@/assets/images/fusion/friends/online.png');
width: 18px;
height: 18px;
filter: saturate(0);
}
&.icon-arrows { &.icon-arrows {
background: url('@/assets/images/icons/arrows.png'); background: url('@/assets/images/icons/arrows.png');
width: 17px; width: 17px;

View File

@ -4,6 +4,7 @@
@import './slider'; @import './slider';
@import './icons'; @import './icons';
@import './utils'; @import './utils';
@import './inputs.scss';
.btn-sm { .btn-sm {
min-height: 28px; min-height: 28px;

View File

@ -0,0 +1,12 @@
.fi {
&.fi-white {
background:transparent;
border:1px solid white;
color:white;
}
outline: none !important;
&* {
outline: none !important;
}
}

View File

@ -8,17 +8,22 @@ export interface ButtonProps extends FlexProps
size?: ButtonSizeType; size?: ButtonSizeType;
active?: boolean; active?: boolean;
disabled?: boolean; disabled?: boolean;
outline?: boolean;
tp?: boolean;
} }
export const Button: FC<ButtonProps> = props => export const Button: FC<ButtonProps> = props =>
{ {
const { variant = 'primary', size = 'sm', active = false, disabled = false, classNames = [], ...rest } = props; const { variant = 'primary', size = 'sm', active = false, disabled = false, classNames = [], outline = false, tp = false, ...rest } = props;
const getClassNames = useMemo(() => const getClassNames = useMemo(() =>
{ {
const newClassNames: string[] = [ 'btn' ]; const newClassNames: string[] = [ 'btn' ];
if(variant) newClassNames.push('btn-' + variant); if(outline && variant) newClassNames.push('btn-outline-' + variant)
else if (variant) newClassNames.push('btn-' + variant);
if (tp) newClassNames.push('btn-transparent');
if(size) newClassNames.push('btn-' + size); if(size) newClassNames.push('btn-' + size);
@ -26,10 +31,10 @@ export const Button: FC<ButtonProps> = props =>
if(disabled) newClassNames.push('disabled'); if(disabled) newClassNames.push('disabled');
if(classNames.length) newClassNames.push(...classNames); if (classNames.length) newClassNames.push(...classNames);
return newClassNames; return newClassNames;
}, [ variant, size, active, disabled, classNames ]); }, [ variant, size, active, disabled, classNames, outline ]);
return <Flex center classNames={ getClassNames } { ...rest } />; return <Flex center classNames={ getClassNames } { ...rest } />;
} }

View File

@ -1,5 +1,5 @@
import { FC, MouseEvent, useMemo } from 'react'; import { FC, MouseEvent, useMemo } from 'react';
import { FaFlag, FaTimes } from 'react-icons/fa'; import { FaFlag } from 'react-icons/fa';
import { Base, Column, ColumnProps, Flex } from '..'; import { Base, Column, ColumnProps, Flex } from '..';
interface NitroCardHeaderViewProps extends ColumnProps interface NitroCardHeaderViewProps extends ColumnProps
@ -32,16 +32,14 @@ export const NitroCardHeaderView: FC<NitroCardHeaderViewProps> = props =>
return ( return (
<Column center position="relative" classNames={ getClassNames } { ...rest }> <Column center position="relative" classNames={ getClassNames } { ...rest }>
<Flex fullWidth center> <Flex fullWidth alignItems="center" justifyContent="between" className="nitro-header-stuff">
<span className="nitro-card-header-text">{ headerText }</span> <span className="nitro-card-header-text">{ headerText }</span>
{ isGalleryPhoto && { isGalleryPhoto &&
<Base position="absolute" className="end-4 nitro-card-header-report-camera" onClick={ onReportPhoto }> <Base position="absolute" className="end-4 nitro-card-header-report-camera" onClick={ onReportPhoto }>
<FaFlag className="fa-icon" /> <FaFlag className="fa-icon" />
</Base> </Base>
} }
<Flex center position="absolute" className="end-2 nitro-card-header-close" onMouseDownCapture={ onMouseDown } onClick={ onCloseClick }> <Flex center position="absolute" className="end-2 nitro-card-header-close" onMouseDownCapture={ onMouseDown } onClick={ onCloseClick } />
<FaTimes className="fa-icon w-12 h-12" />
</Flex>
</Flex> </Flex>
</Column> </Column>
); );

View File

@ -1,5 +1,8 @@
$nitro-card-header-height: 33px; $nitro-card-header-height: 29px;
$nitro-card-tabs-height: 33px; $nitro-card-tabs-height: 29px;
$lp: lighten($primary, 15);
$ls: lighten($secondary, 15);
.nitro-card { .nitro-card {
resize: both; resize: both;
@ -9,40 +12,41 @@ $nitro-card-tabs-height: 33px;
max-height: 100vh !important; max-height: 100vh !important;
} }
&.theme-primary { &.theme-primary,
&.theme-yellow {
border: $border-width solid $border-color; border: $border-width solid $border-color;
border-bottom-width: 3px;
box-shadow: 0 0 10px 0px black;
.nitro-card-header { .nitro-card-header {
min-height: 33px; min-height:$nitro-card-header-height;
max-height: 33px; max-height: $nitro-card-header-height;
background: $primary; background: $primary;
border-bottom: 1px solid $secondary;
border-radius: $border-radius $border-radius 0 0;
box-shadow: 0 1px $primary, inset 0 1px $lp, inset 1px 0 $lp, inset -1px 0 $lp;
.nitro-header-stuff {
justify-content: center !important;
}
.nitro-card-header-text { .nitro-card-header-text {
color: $white; color: $white;
text-shadow: 0px 4px 4px rgba($black, 0.25); text-shadow: 0px -1px rgba(black, 0.5);
@include font-size($h4-font-size); @include font-size($h4-font-size);
} }
.nitro-card-header-close { .nitro-card-header-close {
cursor: pointer; width:19px;
padding: 2px 2px; height:20px;
line-height: 1; background-image: url(@/assets/images/fusion/cards/card-close.png);
border-radius: $border-radius;
box-shadow: 0 0 0 1.6px $white;
border: 2px solid #921911;
background: repeating-linear-gradient(
rgba(245, 80, 65, 1),
rgba(245, 80, 65, 1) 50%,
rgba(194, 48, 39, 1) 50%,
rgba(194, 48, 39, 1) 100%
);
&:hover { &:hover {
filter: brightness(1.2); background-position: -38px 0px;
} }
&:active { &:active {
filter: brightness(0.8); background-position: -19px 0px;
} }
} }
@ -72,97 +76,91 @@ $nitro-card-tabs-height: 33px;
.nitro-card-tabs { .nitro-card-tabs {
background-color: $secondary; background-color: $secondary;
box-shadow: inset 1px 0 $ls, inset -1px 0 $ls, inset 0 -1px $ls;
.nav-item { .nav-item {
padding: $nav-link-padding-y $nav-link-padding-x; padding: $nav-link-padding-y $nav-link-padding-x;
background-color: $muted; background-color: rgba($light, 0.7);
color: $black; color: $black;
z-index: 1; z-index: 1;
margin-bottom: -1px; margin-bottom: -1px;
border-color: rgba(black, 0.7) !important;
border-bottom: 0px solid black;
margin-left:-1px;
&.active { &.active {
background-color: $light; background-color: $light;
border-color: $border-color $border-color $light !important; border-color: $border-color $border-color $light !important;
border-bottom: 1px solid black; border-width: 2px !important;
&:before { &:before {
background: $white; background: $white;
} }
}
&:before { box-shadow: 0 2px $light, inset 0 -2px $light, inset 0 0 0 2px white;
content: '';
position: absolute;
width: 93%;
height: 3px;
border-radius: 0.25rem;
top: 1.5px;
left: 0;
right: 0;
margin: auto;
background: #c2c9d1;
z-index: 1;
} }
} }
} }
.content-area { .content-area {
background-color: $light; background-color: $light;
box-shadow: inset 0 0 0 2px white;
} }
} }
&.theme-primary-slim { &.theme-primary-slim,
&.theme-yellow-slim {
border: $border-width solid $border-color; border: $border-width solid $border-color;
background-color: $f-grey-sec;
box-shadow: inset 0 0 0 2px white;
border-radius: $border-radius-lg !important;
border-bottom-width: 3px;
box-shadow: inset 0 0 0 1px rgba(white, 0.3), $box-shadow !important;
padding:1px;
padding-bottom: 3px;
.nitro-card-header { .nitro-card-header {
position: relative; position: relative;
min-height: 28px; min-height: 29px;
max-height: 28px; max-height: 29px;
background: repeating-linear-gradient($tertiary, $tertiary 50%, $quaternary 50%, $quaternary 100%); background: repeating-linear-gradient($f-grey, $f-grey 50%, $f-grey-sec 50%, $f-grey-sec 100%);
border-bottom: 2px solid darken($quaternary, 5); border-radius: $border-radius-sm $border-radius-sm 0 0;
box-shadow: 0 2px white;
width: 100%;
margin: 0;
padding-top:2px;
&:before { &::before {
content:'';
position: absolute; position: absolute;
content: ' '; height:1px;
top: 0; top:0;
bottom:0;
left: 0; left: 0;
width: 100%; right:0;
height: 2px; margin:auto;
background-color: rgba($white, 0.3); background: $f-grey-sec;
box-shadow: 0 1px $f-grey;
z-index: 1;
}
> * {
z-index: 3;
} }
.nitro-card-header-text { .nitro-card-header-text {
color: $white; color: white;
text-shadow: 0px 4px 4px rgba($black, 0.25); text-shadow: 0px -1px rgba(black, 0.5);
@include font-size($h5-font-size); @include font-size($h5-font-size);
min-height: 21px;
} }
.nitro-card-header-close { .nitro-card-header-close {
cursor: pointer; width:19px;
padding: 0px 2px; height:20px;
line-height: 1; background-image: url(@/assets/images/fusion/cards/card-close.png);
@include font-size($h7-font-size);
border-radius: $border-radius;
box-shadow: 0 0 0 1.6px $white;
border: 2px solid #921911;
background: repeating-linear-gradient(
rgba(245, 80, 65, 1),
rgba(245, 80, 65, 1) 50%,
rgba(194, 48, 39, 1) 50%,
rgba(194, 48, 39, 1) 100%
);
&:hover { &:hover {
filter: brightness(1.2); background-position: -38px 0px;
} }
&:active { &:active {
filter: brightness(0.8); background-position: -19px 0px;
} }
} }
@ -197,7 +195,102 @@ $nitro-card-tabs-height: 33px;
} }
.content-area { .content-area {
z-index: 4;
border:1px solid black;
border-radius: 0 0 $border-radius-sm $border-radius-sm;
background-color: $light; background-color: $light;
margin:0 2px;
width: auto !important;
box-shadow: 0 -1px rgba(white,.3);
}
}
&.theme-yellow {
background: darken($warning, 10);
box-shadow: inset 1px 0 $warning, inset -1px 0 $warning, inset 0 -1px $warning;
.nitro-card-header {
background: $warning;
box-shadow: 0 1px $warning, inset 0 1px lighten($warning,15), inset 1px 0 lighten($warning,15), inset -1px 0 lighten($warning,15);
border-bottom: 1px solid darken($warning, 15);
}
.nitro-card-tabs {
border-bottom:0;
background-color: darken($warning, 10);
box-shadow: inset 1px 0 $warning, inset -1px 0 $warning;
gap:3px;
padding-left:5px;
padding-right:5px;
.nav-item {
background-color: $warning;
margin-left: 0px;
border-radius: $border-radius-lg;
box-shadow: 0 1px $warning, 0 -1px darken($warning, 20), inset 0 -2px darken($warning, 10);
width: 100%;
&.active {
background-color: #b67f04;
border-color: $border-color !important;
border-width: 1px !important;
box-shadow: inset 0 3px darken($warning, 25), 0 1px $warning, 0 -1px darken($warning, 20);
&:before {
background: $white;
}
}
}
}
.content-area {
background-image: url(@/assets/images/fusion/cards/yellow-bg.png);
color:white;
box-shadow: inset 0 0 0 2px rgba(0,0,0,.3);
border:1px solid black;
margin:5px;
width: auto !important;
}
}
&.theme-yellow-slim {
background-color: darken($warning,8);
padding:1px 3px 3px 3px;
box-shadow: inset 0 0 0 1px lighten($warning,20), $box-shadow !important;
.nitro-card-header {
background: repeating-linear-gradient($warning, $warning 50%, darken($warning,8) 50%, darken($warning,8) 100%);
margin: 0 -2px;
width: calc(100% + 4px);
.nitro-header-stuff {
justify-content: space-between !important;
}
.nitro-card-header-text {
color: darken($warning, 50);
text-shadow: 0px -1px rgba(black, 0.5), 0 1px lighten($warning,15);
}
&::before {
background: darken($warning,8);
box-shadow: 0 1px $warning;
}
}
>:not(:first-child) {
margin:0 4px;
}
.content-area {
background-image: url(@/assets/images/fusion/cards/yellow-bg.png);
color:white;
border-radius: $border-radius-lg;
width: auto !important;
box-shadow: 0 -1px lighten($warning,10), inset 0 0 0 4px rgba(0,0,0,.3), 0 1px darken($warning, 23);
padding:8px;
} }
} }
} }
@ -211,6 +304,10 @@ $nitro-card-tabs-height: 33px;
&.theme-dark { &.theme-dark {
background-color: #1C323F !important; background-color: #1C323F !important;
} }
.btn {
filter: drop-shadow(0px 1px 0 white) drop-shadow(0px -1px 0 rgba(black,.2));
}
} }
@include media-breakpoint-down(lg) { @include media-breakpoint-down(lg) {
@ -238,7 +335,6 @@ $nitro-card-tabs-height: 33px;
&.active { &.active {
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
background: rgba($white, 0.5);
border-bottom: 1px solid rgba($black, 0.2); border-bottom: 1px solid rgba($black, 0.2);
} }

View File

@ -15,7 +15,7 @@ export const NitroCardView: FC<NitroCardViewProps> = props =>
const getClassNames = useMemo(() => const getClassNames = useMemo(() =>
{ {
const newClassNames: string[] = [ 'nitro-card', 'rounded', 'shadow', ]; const newClassNames: string[] = [ 'nitro-card', 'rounded', ];
newClassNames.push(`theme-${ theme || 'primary' }`); newClassNames.push(`theme-${ theme || 'primary' }`);
@ -24,35 +24,35 @@ export const NitroCardView: FC<NitroCardViewProps> = props =>
return newClassNames; return newClassNames;
}, [ theme, classNames ]); }, [ theme, classNames ]);
/* useEffect(() => // useEffect(() =>
{ // {
if(!uniqueKey || !elementRef || !elementRef.current) return; // if(!uniqueKey || !elementRef || !elementRef.current) return;
const localStorage = GetLocalStorage<WindowSaveOptions>(`nitro.windows.${ uniqueKey }`); // const localStorage = GetLocalStorage<WindowSaveOptions>(`nitro.windows.${ uniqueKey }`);
const element = elementRef.current; // const element = elementRef.current;
if(localStorage && localStorage.size) // if(localStorage && localStorage.size)
{ // {
//element.style.width = `${ localStorage.size.width }px`; // //element.style.width = `${ localStorage.size.width }px`;
//element.style.height = `${ localStorage.size.height }px`; // //element.style.height = `${ localStorage.size.height }px`;
} // }
const observer = new ResizeObserver(event => // const observer = new ResizeObserver(event =>
{ // {
const newStorage = { ...GetLocalStorage<Partial<WindowSaveOptions>>(`nitro.windows.${ uniqueKey }`) } as WindowSaveOptions; // const newStorage = { ...GetLocalStorage<Partial<WindowSaveOptions>>(`nitro.windows.${ uniqueKey }`) } as WindowSaveOptions;
newStorage.size = { width: element.offsetWidth, height: element.offsetHeight }; // newStorage.size = { width: element.offsetWidth, height: element.offsetHeight };
SetLocalStorage<WindowSaveOptions>(`nitro.windows.${ uniqueKey }`, newStorage); // SetLocalStorage<WindowSaveOptions>(`nitro.windows.${ uniqueKey }`, newStorage);
}); // });
observer.observe(element); // observer.observe(element);
return () => // return () =>
{ // {
observer.disconnect(); // observer.disconnect();
} // }
}, [ uniqueKey ]); */ // }, [ uniqueKey ]);
return ( return (
<NitroCardContextProvider value={ { theme } }> <NitroCardContextProvider value={ { theme } }>

View File

@ -71,7 +71,7 @@ export const NitroCardAccordionSetView: FC<NitroCardAccordionSetViewProps> = pro
return ( return (
<Column classNames={ getClassNames } gap={ gap } { ...rest }> <Column classNames={ getClassNames } gap={ gap } { ...rest }>
<Flex pointer justifyContent="between" className="nitro-card-accordion-set-header px-2 py-1" onClick={ onClick }> <Flex pointer justifyContent="between" className="nitro-card-accordion-set-header px-2 py-1" onClick={ onClick }>
<Text>{ headerText }</Text> <Text variant="white">{ headerText }</Text>
{ isOpen && <FaCaretUp className="fa-icon" /> } { isOpen && <FaCaretUp className="fa-icon" /> }
{ !isOpen && <FaCaretDown className="fa-icon" /> } { !isOpen && <FaCaretDown className="fa-icon" /> }
</Flex> </Flex>

View File

@ -3,7 +3,7 @@ import { Flex, FlexProps } from '../..';
export const NitroCardTabsView: FC<FlexProps> = props => export const NitroCardTabsView: FC<FlexProps> = props =>
{ {
const { justifyContent = 'center', gap = 1, classNames = [], children = null, ...rest } = props; const { justifyContent = 'start', gap = 0, classNames = [], children = null, ...rest } = props;
const getClassNames = useMemo(() => const getClassNames = useMemo(() =>
{ {

View File

@ -2,10 +2,11 @@ import { MouseEventType, TouchEventType } from '@nitrots/nitro-renderer';
import { CSSProperties, FC, Key, MouseEvent as ReactMouseEvent, ReactNode, TouchEvent as ReactTouchEvent, useCallback, useEffect, useRef, useState } from 'react'; import { CSSProperties, FC, Key, MouseEvent as ReactMouseEvent, ReactNode, TouchEvent as ReactTouchEvent, useCallback, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { Base } from '..'; import { Base } from '..';
import { GetLocalStorage, SetLocalStorage, WindowSaveOptions } from '../../api'; import { GetLocalStorage, WindowSaveOptions } from '../../api';
import { DraggableWindowPosition } from './DraggableWindowPosition'; import { DraggableWindowPosition } from './DraggableWindowPosition';
const CURRENT_WINDOWS: HTMLElement[] = []; const CURRENT_WINDOWS: HTMLElement[] = [];
const POS_MEMORY: Map<Key, { x: number, y: number }> = new Map();
const BOUNDS_THRESHOLD_TOP: number = 0; const BOUNDS_THRESHOLD_TOP: number = 0;
const BOUNDS_THRESHOLD_LEFT: number = 0; const BOUNDS_THRESHOLD_LEFT: number = 0;
@ -138,14 +139,7 @@ export const DraggableWindow: FC<DraggableWindowProps> = props =>
setOffset({ x: offsetX, y: offsetY }); setOffset({ x: offsetX, y: offsetY });
setIsDragging(false); setIsDragging(false);
if(uniqueKey !== null) if(uniqueKey !== null) POS_MEMORY.set(uniqueKey, { x: offsetX, y: offsetY });
{
const newStorage = { ...GetLocalStorage<WindowSaveOptions>(`nitro.windows.${ uniqueKey }`) } as WindowSaveOptions;
newStorage.offset = { x: offsetX, y: offsetY };
SetLocalStorage<WindowSaveOptions>(`nitro.windows.${ uniqueKey }`, newStorage);
}
}, [ dragHandler, delta, offset, uniqueKey ]); }, [ dragHandler, delta, offset, uniqueKey ]);
const onDragMouseUp = useCallback((event: MouseEvent) => const onDragMouseUp = useCallback((event: MouseEvent) =>
@ -194,6 +188,17 @@ export const DraggableWindow: FC<DraggableWindowProps> = props =>
break; break;
} }
if(uniqueKey !== null)
{
const memory = POS_MEMORY.get(uniqueKey);
if(memory)
{
offsetX = memory.x;
offsetY = memory.y;
}
}
setDelta({ x: 0, y: 0 }); setDelta({ x: 0, y: 0 });
setOffset({ x: offsetX, y: offsetY }); setOffset({ x: offsetX, y: offsetY });

View File

@ -1,4 +1,4 @@
import { FC, useMemo } from 'react'; import { FC, ReactNode, useMemo } from 'react';
import { NotificationAlertType } from '../../api'; import { NotificationAlertType } from '../../api';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroCardViewProps } from '../card'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroCardViewProps } from '../card';
@ -7,11 +7,12 @@ export interface LayoutNotificationAlertViewProps extends NitroCardViewProps
title?: string; title?: string;
type?: string; type?: string;
onClose: () => void; onClose: () => void;
options?: ReactNode;
} }
export const LayoutNotificationAlertView: FC<LayoutNotificationAlertViewProps> = props => export const LayoutNotificationAlertView: FC<LayoutNotificationAlertViewProps> = props =>
{ {
const { title = '', onClose = null, classNames = [], children = null,type = NotificationAlertType.DEFAULT, ...rest } = props; const { title = '', onClose = null, classNames = [], children = null,type = NotificationAlertType.DEFAULT, options = null, ...rest } = props;
const getClassNames = useMemo(() => const getClassNames = useMemo(() =>
{ {
@ -30,6 +31,7 @@ export const LayoutNotificationAlertView: FC<LayoutNotificationAlertViewProps> =
<NitroCardContentView grow justifyContent="between" overflow="hidden" className="text-black" gap={ 0 }> <NitroCardContentView grow justifyContent="between" overflow="hidden" className="text-black" gap={ 0 }>
{ children } { children }
</NitroCardContentView> </NitroCardContentView>
{ options }
</NitroCardView> </NitroCardView>
); );
} }

View File

@ -1 +1 @@
export type ColorVariantType = 'primary' | 'success' | 'danger' | 'secondary' | 'link' | 'black' | 'white' | 'dark' | 'warning' | 'muted' | 'light'; export type ColorVariantType = 'primary' | 'success' | 'danger' | 'secondary' | 'link' | 'black' | 'white' | 'dark' | 'warning' | 'muted' | 'light' | 'f-grey';

View File

@ -1,3 +1,5 @@
@import './views/friends-list/myinfo/MyInfo.scss';
.nitro-friends-spritesheet { .nitro-friends-spritesheet {
background: url('@/assets/images/friends/friends-spritesheet.png') transparent no-repeat; background: url('@/assets/images/friends/friends-spritesheet.png') transparent no-repeat;
@ -118,18 +120,35 @@
position: relative; position: relative;
padding-left:38px; padding-left:38px;
text-align: left; text-align: left;
position: relative;
pointer-events: all;
&:not(:last-of-type)::before {
content:'';
width:1px;
background-color: black;
box-shadow: 0px 1px 0 lighten($f-grey, 10%), 0px -1px 0 darken($f-grey-sec, 10);
height: 33px;
position: absolute;
left:100%;
}
&.friend-bar-item-active { &.friend-bar-item-active {
margin-bottom:21px; .friend-bar-item-head {
&.avatar {
top: -50px;
left: -30px;
}
}
} }
.friend-bar-item-head { .friend-bar-item-head {
&.avatar { &.avatar {
top: -30px; top: -40px;
left: -30px; left: -30px;
} }
&.group { &.group {
top: -5px; top: -15px;
left: -5px; left: -5px;
} }

View File

@ -33,7 +33,7 @@ export const FriendBarItemView: FC<{ friend: MessengerFriend }> = props =>
if(!friend) if(!friend)
{ {
return ( return (
<div ref={ elementRef } className="btn btn-primary friend-bar-item friend-bar-search"> <div ref={ elementRef } className="btn btn-f-grey friend-bar-search">
<div className="friend-bar-item-head position-absolute"/> <div className="friend-bar-item-head position-absolute"/>
<div className="text-truncate">{ LocalizeText('friend.bar.find.title') }</div> <div className="text-truncate">{ LocalizeText('friend.bar.find.title') }</div>
</div> </div>
@ -41,19 +41,18 @@ export const FriendBarItemView: FC<{ friend: MessengerFriend }> = props =>
} }
return ( return (
<div ref={ elementRef } className={ 'btn btn-success friend-bar-item ' + (isVisible ? 'friend-bar-item-active' : '') } onClick={ event => setVisible(prevValue => !prevValue) }> <div ref={ elementRef } className={ 'friend-bar-item d-flex align-items-center ' + (isVisible ? 'friend-bar-item-active' : '') } onMouseOver={ () => setVisible(true) } onMouseOut={ () => setVisible(false) }>
<div className={ `friend-bar-item-head position-absolute ${ friend.id > 0 ? 'avatar': 'group' }` }> <div className={ `friend-bar-item-head position-absolute ${ friend.id > 0 ? 'avatar': 'group' }` }>
{ (friend.id > 0) && <LayoutAvatarImageView headOnly={ true } figure={ friend.figure } direction={ 2 } /> } { (friend.id > 0) && <LayoutAvatarImageView figure={ friend.figure } direction={ isVisible ? 2 : 3 } /> }
{ (friend.id <= 0) && <LayoutBadgeImageView isGroup={ true } badgeCode={ friend.figure } /> } { (friend.id <= 0) && <LayoutBadgeImageView isGroup={ true } badgeCode={ friend.figure } /> }
</div> </div>
<div className="text-truncate">{ friend.name }</div> { isVisible ?
{ isVisible && <div className="d-flex justify-content-between w-100 gap-2 pe-1">
<div className="d-flex justify-content-between"> <Base className="nitro-friends-spritesheet icon-friendbar-chat cursor-pointer" onClick={ event => OpenMessengerChat(friend.id) } />
<Base className="nitro-friends-spritesheet icon-friendbar-chat cursor-pointer" onClick={ event => OpenMessengerChat(friend.id) } /> { friend.followingAllowed &&
{ friend.followingAllowed &&
<Base className="nitro-friends-spritesheet icon-friendbar-visit cursor-pointer" onClick={ event => followFriend(friend) } /> } <Base className="nitro-friends-spritesheet icon-friendbar-visit cursor-pointer" onClick={ event => followFriend(friend) } /> }
<Base className="nitro-friends-spritesheet icon-profile cursor-pointer" onClick={ event => GetUserProfile(friend.id) } /> <Base className="nitro-friends-spritesheet icon-profile cursor-pointer" onClick={ event => GetUserProfile(friend.id) } />
</div> } </div> : <div className="text-truncate w-100 pe-1">{ friend.name }</div> }
</div> </div>
); );
} }

View File

@ -1,19 +1,62 @@
import { FC, useRef, useState } from 'react'; import { FC, useCallback, useEffect, useRef, useState } from 'react';
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
import { MessengerFriend } from '../../../../api'; import { MessengerFriend } from '../../../../api';
import { Button, Flex } from '../../../../common'; import { Button, Flex } from '../../../../common';
import { FriendBarItemView } from './FriendBarItemView'; import { FriendBarItemView } from './FriendBarItemView';
const MAX_DISPLAY_COUNT = 3;
export const FriendBarView: FC<{ onlineFriends: MessengerFriend[] }> = props => export const FriendBarView: FC<{ onlineFriends: MessengerFriend[] }> = props =>
{ {
const { onlineFriends = null } = props; const { onlineFriends = null } = props;
const [ indexOffset, setIndexOffset ] = useState(0); const [ indexOffset, setIndexOffset ] = useState(0);
const elementRef = useRef<HTMLDivElement>(); const [ MAX_DISPLAY_COUNT, setMaxDisplayCount ] = useState(1);
const friendWidth = 150;
const friendRef = useRef<HTMLDivElement>();
const calculateDisplayCount = useCallback(() =>
{
let min = 1 + onlineFriends.length;
if(!friendRef.current) return min;
let max = Math.floor((friendRef?.current?.getBoundingClientRect().width - 60) / friendWidth);
if(min >= max) min = max;
setMaxDisplayCount( min );
}, [ friendWidth, onlineFriends ])
useEffect(() =>
{
if(!friendRef.current) return;
const resizeObserver = new ResizeObserver(() =>
{
calculateDisplayCount();
resizeObserver.disconnect();
});
resizeObserver.observe(friendRef.current);
window.addEventListener('resize', () =>
{
calculateDisplayCount()
});
return () =>
{
resizeObserver.disconnect();
window.removeEventListener('resize', () =>
{
calculateDisplayCount()
});
}
}, [ calculateDisplayCount, friendRef ]);
return ( return (
<Flex innerRef={ elementRef } alignItems="center" className="friend-bar"> <Flex innerRef={ friendRef } className="w-100 justify-content-center align-items-center friend-bar" gap={ 1 }>
<Button variant="black" className="friend-bar-button" disabled={ (indexOffset <= 0) } onClick={ event => setIndexOffset(indexOffset - 1) }> <Button variant="black" className="friend-bar-button" disabled={ (indexOffset <= 0) } onClick={ event => setIndexOffset(indexOffset - 1) }>
<FaChevronLeft className="fa-icon" /> <FaChevronLeft className="fa-icon" />
</Button> </Button>

View File

@ -1,7 +1,7 @@
import { HabboSearchComposer, HabboSearchResultData, HabboSearchResultEvent } from '@nitrots/nitro-renderer'; import { HabboSearchComposer, HabboSearchResultData, HabboSearchResultEvent } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { LocalizeText, OpenMessengerChat, SendMessageComposer } from '../../../../api'; import { LocalizeText, OpenMessengerChat, SendMessageComposer } from '../../../../api';
import { Base, Column, Flex, NitroCardAccordionItemView, NitroCardAccordionSetView, NitroCardAccordionSetViewProps, Text, UserProfileIconView } from '../../../../common'; import { Base, Column, Flex, NitroCardAccordionItemView, NitroCardAccordionSetViewProps, Text, UserProfileIconView } from '../../../../common';
import { useFriends, useMessageEvent } from '../../../../hooks'; import { useFriends, useMessageEvent } from '../../../../hooks';
interface FriendsSearchViewProps extends NitroCardAccordionSetViewProps interface FriendsSearchViewProps extends NitroCardAccordionSetViewProps
@ -39,19 +39,18 @@ export const FriendsSearchView: FC<FriendsSearchViewProps> = props =>
return () => clearTimeout(timeout); return () => clearTimeout(timeout);
}, [ searchValue ]); }, [ searchValue ]);
return ( return <>
<NitroCardAccordionSetView { ...rest }> <Text bold variant="white">{ LocalizeText('friendlist.tip.tab.3') }</Text>
<input type="text" className="search-input form-control form-control-sm w-100 rounded-0" placeholder={ LocalizeText('generic.search') } value={ searchValue } maxLength={ 50 } onChange={ event => setSearchValue(event.target.value) } /> <input type="text" className="fi fi-white" placeholder={ LocalizeText('generic.search') } value={ searchValue } maxLength={ 50 } onChange={ event => setSearchValue(event.target.value) } />
<Column> <Column gap={ 1 } fullHeight overflow="hidden">
{ friendResults && { friendResults &&
<> <>
{ (friendResults.length === 0) && { (friendResults.length === 0) &&
<Text bold small className="px-2 py-1">{ LocalizeText('friendlist.search.nofriendsfound') }</Text> } <Text bold small variant="white">{ LocalizeText('friendlist.search.nofriendsfound') }</Text> }
{ (friendResults.length > 0) && { (friendResults.length > 0) &&
<Column gap={ 0 }> <Column gap={ 0 } fullHeight overflow="hidden">
<Text bold small className="px-2 py-1">{ LocalizeText('friendlist.search.friendscaption', [ 'cnt' ], [ friendResults.length.toString() ]) }</Text> <Text bold small variant="white">{ LocalizeText('friendlist.search.friendscaption', [ 'cnt' ], [ friendResults.length.toString() ]) }</Text>
<hr className="mx-2 mt-0 mb-1 text-black" /> <Column gap={ 0 } fullHeight className="overflow-y-scroll">
<Column gap={ 0 }>
{ friendResults.map(result => { friendResults.map(result =>
{ {
return ( return (
@ -70,15 +69,14 @@ export const FriendsSearchView: FC<FriendsSearchViewProps> = props =>
</Column> </Column>
</Column> } </Column> }
</> } </> }
{ otherResults && { otherResults &&
<> <>
{ (otherResults.length === 0) && { (otherResults.length === 0) &&
<Text bold small className="px-2 py-1">{ LocalizeText('friendlist.search.noothersfound') }</Text> } <Text bold small variant="white">{ LocalizeText('friendlist.search.noothersfound') }</Text> }
{ (otherResults.length > 0) && { (otherResults.length > 0) &&
<Column gap={ 0 }> <Column gap={ 0 } fullHeight overflow="hidden">
<Text bold small className="px-2 py-1">{ LocalizeText('friendlist.search.otherscaption', [ 'cnt' ], [ otherResults.length.toString() ]) }</Text> <Text bold small variant="white">{ LocalizeText('friendlist.search.otherscaption', [ 'cnt' ], [ otherResults.length.toString() ]) }</Text>
<hr className="mx-2 mt-0 mb-1 text-black" /> <Column gap={ 0 } fullHeight className="overflow-y-scroll">
<Column gap={ 0 }>
{ otherResults.map(result => { otherResults.map(result =>
{ {
return ( return (
@ -97,7 +95,6 @@ export const FriendsSearchView: FC<FriendsSearchViewProps> = props =>
</Column> </Column>
</Column> } </Column> }
</> } </> }
</Column> </Column>
</NitroCardAccordionSetView> </>
);
} }

View File

@ -1,13 +1,13 @@
import { ILinkEventTracker, RemoveFriendComposer, SendRoomInviteComposer } from '@nitrots/nitro-renderer'; import { ILinkEventTracker, RemoveFriendComposer, SendRoomInviteComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { AddEventLinkTracker, LocalizeText, MessengerFriend, RemoveLinkEventTracker, SendMessageComposer } from '../../../../api'; import { AddEventLinkTracker, LocalizeText, MessengerFriend, RemoveLinkEventTracker, SendMessageComposer } from '../../../../api';
import { Button, Flex, NitroCardAccordionSetView, NitroCardAccordionView, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; import { Button, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
import { useFriends } from '../../../../hooks'; import { useFriends } from '../../../../hooks';
import { FriendsListGroupView } from './friends-list-group/FriendsListGroupView';
import { FriendsListRequestView } from './friends-list-request/FriendsListRequestView';
import { FriendsRemoveConfirmationView } from './FriendsListRemoveConfirmationView'; import { FriendsRemoveConfirmationView } from './FriendsListRemoveConfirmationView';
import { FriendsRoomInviteView } from './FriendsListRoomInviteView'; import { FriendsRoomInviteView } from './FriendsListRoomInviteView';
import { FriendsSearchView } from './FriendsListSearchView'; import { FriendsSearchView } from './FriendsListSearchView';
import { FriendsListGroupView } from './friends-list-group/FriendsListGroupView';
import { MyInfoView } from './myinfo/MyInfoView';
export const FriendsListView: FC<{}> = props => export const FriendsListView: FC<{}> = props =>
{ {
@ -17,6 +17,8 @@ export const FriendsListView: FC<{}> = props =>
const [ showRemoveFriendsConfirmation, setShowRemoveFriendsConfirmation ] = useState<boolean>(false); const [ showRemoveFriendsConfirmation, setShowRemoveFriendsConfirmation ] = useState<boolean>(false);
const { onlineFriends = [], offlineFriends = [], requests = [], requestFriend = null } = useFriends(); const { onlineFriends = [], offlineFriends = [], requests = [], requestFriend = null } = useFriends();
const [ tab, setTab ] = useState<number>(0);
const removeFriendsText = useMemo(() => const removeFriendsText = useMemo(() =>
{ {
if(!selectedFriendsIds || !selectedFriendsIds.length) return ''; if(!selectedFriendsIds || !selectedFriendsIds.length) return '';
@ -83,6 +85,30 @@ export const FriendsListView: FC<{}> = props =>
setShowRemoveFriendsConfirmation(false); setShowRemoveFriendsConfirmation(false);
} }
const getView = () =>
{
switch(tab)
{
case 0: return <MyInfoView />
case 1: return <>
<Text variant="white" bold>{ LocalizeText('friendlist.friends') + ` (${ onlineFriends.length })` }</Text>
<FriendsListGroupView list={ onlineFriends } selectedFriendsIds={ selectedFriendsIds } selectFriend={ selectFriend } />
{ selectedFriendsIds && selectedFriendsIds.length > 0 &&
<Flex gap={ 1 } className="p-1">
<Button outline fullWidth variant="white" onClick={ () => setShowRoomInvite(true) }>{ LocalizeText('friendlist.tip.invite') }</Button>
<Button outline fullWidth variant="white" onClick={ event => setShowRemoveFriendsConfirmation(true) }>{ LocalizeText('generic.delete') }</Button>
</Flex> }
</>;
case 2: return <>
<Text variant="white" bold>{ LocalizeText('friendlist.friends.offlinecaption')+ ` (${ offlineFriends.length })` }</Text>
<FriendsListGroupView list={ offlineFriends } selectedFriendsIds={ selectedFriendsIds } selectFriend={ selectFriend } />
</>
case 3: return <FriendsSearchView headerText={ LocalizeText('people.search.title') } />;
default:
return null
}
}
useEffect(() => useEffect(() =>
{ {
const linkTracker: ILinkEventTracker = { const linkTracker: ILinkEventTracker = {
@ -121,10 +147,11 @@ export const FriendsListView: FC<{}> = props =>
return ( return (
<> <>
<NitroCardView className="nitro-friends" uniqueKey="nitro-friends" theme="primary-slim"> <NitroCardView className="nitro-friends" uniqueKey="nitro-friends" theme="yellow-slim">
<NitroCardHeaderView headerText={ LocalizeText('friendlist.friends') } onCloseClick={ event => setIsVisible(false) } /> <NitroCardHeaderView headerText={ LocalizeText('friendlist.friends') } onCloseClick={ event => setIsVisible(false) } />
<NitroCardContentView overflow="hidden" gap={ 1 } className="text-black p-0"> <NitroCardContentView overflow="hidden" gap={ 1 } className="mb-2">
<NitroCardAccordionView fullHeight overflow="hidden"> { getView() }
{ /* <NitroCardAccordionView fullHeight overflow="hidden">
<NitroCardAccordionSetView headerText={ LocalizeText('friendlist.friends') + ` (${ onlineFriends.length })` } isExpanded={ true }> <NitroCardAccordionSetView headerText={ LocalizeText('friendlist.friends') + ` (${ onlineFriends.length })` } isExpanded={ true }>
<FriendsListGroupView list={ onlineFriends } selectedFriendsIds={ selectedFriendsIds } selectFriend={ selectFriend } /> <FriendsListGroupView list={ onlineFriends } selectedFriendsIds={ selectedFriendsIds } selectFriend={ selectFriend } />
</NitroCardAccordionSetView> </NitroCardAccordionSetView>
@ -138,8 +165,25 @@ export const FriendsListView: FC<{}> = props =>
<Flex gap={ 1 } className="p-1"> <Flex gap={ 1 } className="p-1">
<Button fullWidth onClick={ () => setShowRoomInvite(true) }>{ LocalizeText('friendlist.tip.invite') }</Button> <Button fullWidth onClick={ () => setShowRoomInvite(true) }>{ LocalizeText('friendlist.tip.invite') }</Button>
<Button fullWidth variant="danger" onClick={ event => setShowRemoveFriendsConfirmation(true) }>{ LocalizeText('generic.delete') }</Button> <Button fullWidth variant="danger" onClick={ event => setShowRemoveFriendsConfirmation(true) }>{ LocalizeText('generic.delete') }</Button>
</Flex> } </Flex> } */ }
</NitroCardContentView> </NitroCardContentView>
<Flex gap={ 1 } className="mb-1">
<Button variant="warning" fullWidth className={ tab == 0 ? 'inset' : '' } onClick={ () => setTab(0) }>
<i className="icon disk-creator" />
</Button>
<Button variant="warning" fullWidth className={ tab == 1 ? 'inset' : '' } onClick={ () => setTab(1) }>
<i className="icon icon-online-friends" />
</Button>
<Button variant="warning" fullWidth className={ tab == 2 ? 'inset' : '' } onClick={ () => setTab(2) }>
<i className="icon icon-offline-friends" />
</Button>
<Button variant="warning" fullWidth className={ tab == 3 ? 'inset' : '' } onClick={ () => setTab(3) }>
<i className="icon icon-search" />
</Button>
<Button variant="warning" fullWidth className={ tab == 4 ? 'inset' : '' } onClick={ () => setTab(4) }>
<i className="nitro-friends-spritesheet icon-chat" />
</Button>
</Flex>
</NitroCardView> </NitroCardView>
{ showRoomInvite && { showRoomInvite &&
<FriendsRoomInviteView selectedFriendsIds={ selectedFriendsIds } onCloseClick={ () => setShowRoomInvite(false) } sendRoomInvite={ sendRoomInvite } /> } <FriendsRoomInviteView selectedFriendsIds={ selectedFriendsIds } onCloseClick={ () => setShowRoomInvite(false) } sendRoomInvite={ sendRoomInvite } /> }

View File

@ -52,7 +52,34 @@ export const FriendsListGroupItemView: FC<{ friend: MessengerFriend, selected: b
} }
} }
if(!friend) return null; if (!friend) return null;
return <Flex fullWidth justifyContent="between" onClick={ event => selectFriend(friend.id) } className={ selected ? 'bg-primary text-white' : '' }>
<Flex alignItems="center" gap={ 1 }>
<Base onClick={ event => event.stopPropagation() }>
<UserProfileIconView userId={ friend.id } />
</Base>
<div>{ friend.name }</div>
</Flex>
<Flex alignItems="center" gap={ 1 }>
{ !isRelationshipOpen &&
<>
{ friend.followingAllowed &&
<Base pointer onClick={ clickFollowFriend } className="nitro-friends-spritesheet icon-follow" title={ LocalizeText('friendlist.tip.follow') } /> }
{ friend.online &&
<Base pointer className="nitro-friends-spritesheet icon-chat" onClick={ openMessengerChat } title={ LocalizeText('friendlist.tip.im') } /> }
{ (friend.id > 0) &&
<Base className={ `nitro-friends-spritesheet icon-${ getCurrentRelationshipName() } cursor-pointer` } onClick={ openRelationship } title={ LocalizeText('infostand.link.relationship') } /> }
</> }
{ isRelationshipOpen &&
<>
<Base pointer className="nitro-friends-spritesheet icon-heart" onClick={ event => clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_HEART) } />
<Base pointer className="nitro-friends-spritesheet icon-smile" onClick={ event => clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_SMILE) } />
<Base pointer className="nitro-friends-spritesheet icon-bobba" onClick={ event => clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_BOBBA) } />
<Base pointer className="nitro-friends-spritesheet icon-none" onClick={ event => clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_NONE) } />
</> }
</Flex>
</Flex>
return ( return (
<NitroCardAccordionItemView justifyContent="between" className={ `px-2 py-1 ${ selected && 'bg-primary text-white' }` } onClick={ event => selectFriend(friend.id) }> <NitroCardAccordionItemView justifyContent="between" className={ `px-2 py-1 ${ selected && 'bg-primary text-white' }` } onClick={ event => selectFriend(friend.id) }>

View File

@ -1,5 +1,6 @@
import { FC } from 'react'; import { FC } from 'react';
import { MessengerFriend } from '../../../../../api'; import { MessengerFriend } from '../../../../../api';
import { Column } from '../../../../../common';
import { FriendsListGroupItemView } from './FriendsListGroupItemView'; import { FriendsListGroupItemView } from './FriendsListGroupItemView';
interface FriendsListGroupViewProps interface FriendsListGroupViewProps
@ -16,8 +17,8 @@ export const FriendsListGroupView: FC<FriendsListGroupViewProps> = props =>
if(!list || !list.length) return null; if(!list || !list.length) return null;
return ( return (
<> <Column fullHeight className="overflow-y-scroll">
{ list.map((item, index) => <FriendsListGroupItemView key={ index } friend={ item } selected={ selectedFriendsIds && (selectedFriendsIds.indexOf(item.id) >= 0) } selectFriend={ selectFriend } />) } { list.map((item, index) => <FriendsListGroupItemView key={ index } friend={ item } selected={ selectedFriendsIds && (selectedFriendsIds.indexOf(item.id) >= 0) } selectFriend={ selectFriend } />) }
</> </Column>
); );
} }

View File

@ -0,0 +1,16 @@
.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;
}
}
}

View File

@ -0,0 +1,40 @@
import { KeyboardEvent } from 'react';
import { Base, Column, Flex, LayoutAvatarImageView, Text } from '../../../../../common';
import { useFriends, useMessenger, useSessionInfo } from '../../../../../hooks';
export const MyInfoView = () =>
{
const { userFigure = null, userName = null, motto,setMotto, saveMotto } = useSessionInfo();
const { unreadThreads = 0 } = useMessenger();
const { requests = [] } = useFriends();
const onMottoKeyDown = (event: KeyboardEvent<HTMLInputElement>) =>
{
event.stopPropagation();
switch(event.key)
{
case 'Enter':
saveMotto((event.target as HTMLInputElement).value);
return;
}
}
return <Column fullHeight fullWidth className="friends-myinfo">
<Flex fullHeight fullWidth center gap={ 1 } className="px-2">
<Base className="myinfo-avatar">
<LayoutAvatarImageView figure={ userFigure } direction={ 3 } position="absolute" headOnly />
</Base>
<Column>
<Text variant="white" bold>{ userName }</Text>
<input className="fi fi-white" value={ motto } onChange={ event => setMotto(event.target.value) } onKeyDown={ onMottoKeyDown } />
</Column>
</Flex>
<Flex fullHeight fullWidth center>
<Column>
<Text variant="white" bold underline>{ unreadThreads } Unread Messages</Text>
<Text variant="white" bold underline>{ requests.length } Friend Requests</Text>
</Column>
</Flex>
</Column>
}

View File

@ -14,4 +14,27 @@
padding: 1px 2px; padding: 1px 2px;
z-index: 5; z-index: 5;
} }
.tab-icon {
width:18px;
height:18px;
background-position: center;
background-repeat: no-repeat;
&.tab-inventory-furni {
background-image: url(@/assets/images/fusion/inventory/furni.png);
}
&.tab-inventory-bots {
background-image: url(@/assets/images/fusion/inventory/bots.png);
}
&.tab-inventory-furni-tab-pets {
background-image: url(@/assets/images/fusion/inventory/pets.png);
}
&.tab-inventory-badges {
background-image: url(@/assets/images/fusion/inventory/ach.png);
}
}
} }

View File

@ -1,7 +1,7 @@
import { BadgePointLimitsEvent, ILinkEventTracker, IRoomSession, RoomEngineObjectEvent, RoomEngineObjectPlacedEvent, RoomPreviewer, RoomSessionEvent } from '@nitrots/nitro-renderer'; import { BadgePointLimitsEvent, ILinkEventTracker, IRoomSession, RoomEngineObjectEvent, RoomEngineObjectPlacedEvent, RoomPreviewer, RoomSessionEvent } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { AddEventLinkTracker, GetLocalization, GetRoomEngine, isObjectMoverRequested, LocalizeText, RemoveLinkEventTracker, setObjectMoverRequested, UnseenItemCategory } from '../../api'; import { AddEventLinkTracker, GetLocalization, GetRoomEngine, LocalizeText, RemoveLinkEventTracker, UnseenItemCategory, isObjectMoverRequested, setObjectMoverRequested } from '../../api';
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common'; import { Base, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common';
import { useInventoryTrade, useInventoryUnseenTracker, useMessageEvent, useRoomEngineEvent, useRoomSessionManagerEvent } from '../../hooks'; import { useInventoryTrade, useInventoryUnseenTracker, useMessageEvent, useRoomEngineEvent, useRoomSessionManagerEvent } from '../../hooks';
import { InventoryBadgeView } from './views/badge/InventoryBadgeView'; import { InventoryBadgeView } from './views/badge/InventoryBadgeView';
import { InventoryBotView } from './views/bot/InventoryBotView'; import { InventoryBotView } from './views/bot/InventoryBotView';
@ -127,7 +127,7 @@ export const InventoryView: FC<{}> = props =>
{ {
return ( return (
<NitroCardTabsItemView key={ index } isActive={ (currentTab === name) } onClick={ event => setCurrentTab(name) } count={ getCount(UNSEEN_CATEGORIES[index]) }> <NitroCardTabsItemView key={ index } isActive={ (currentTab === name) } onClick={ event => setCurrentTab(name) } count={ getCount(UNSEEN_CATEGORIES[index]) }>
{ LocalizeText(name) } <Base className={ `tab-icon tab-${ name.replaceAll('.','-') }` }/>
</NitroCardTabsItemView> </NitroCardTabsItemView>
); );
}) } }) }

View File

@ -21,8 +21,14 @@ export const NotificationDefaultAlertView: FC<NotificationDefaultAlertViewProps>
const hasFrank = (item.alertType === NotificationAlertType.DEFAULT); const hasFrank = (item.alertType === NotificationAlertType.DEFAULT);
const options = <Column alignItems="center" center gap={ 0 } className="my-1">
{ !item.clickUrl &&
<Button onClick={ onClose } variant="success">{ LocalizeText('generic.close') }</Button> }
{ item.clickUrl && (item.clickUrl.length > 0) && <Button variant="success" onClick={ visitUrl }>{ LocalizeText(item.clickUrlText) }</Button> }
</Column>
return ( return (
<LayoutNotificationAlertView title={ title } onClose={ onClose } { ...rest } type={ hasFrank ? NotificationAlertType.DEFAULT : item.alertType }> <LayoutNotificationAlertView title={ title } onClose={ onClose } { ...rest } type={ hasFrank ? NotificationAlertType.DEFAULT : item.alertType } options={ options }>
<Flex fullHeight overflow="auto" gap={ hasFrank || (item.imageUrl && !imageFailed) ? 2 : 0 }> <Flex fullHeight overflow="auto" gap={ hasFrank || (item.imageUrl && !imageFailed) ? 2 : 0 }>
{ hasFrank && !item.imageUrl && <Base className="notification-frank flex-shrink-0" /> } { hasFrank && !item.imageUrl && <Base className="notification-frank flex-shrink-0" /> }
{ item.imageUrl && !imageFailed && <img src={ item.imageUrl } alt={ item.title } onError={ () => { item.imageUrl && !imageFailed && <img src={ item.imageUrl } alt={ item.title } onError={ () =>
@ -36,20 +42,8 @@ export const NotificationDefaultAlertView: FC<NotificationDefaultAlertViewProps>
return <Base key={ index } dangerouslySetInnerHTML={ { __html: htmlText } } />; return <Base key={ index } dangerouslySetInnerHTML={ { __html: htmlText } } />;
}) } }) }
{ item.clickUrl && (item.clickUrl.length > 0) && (item.imageUrl && !imageFailed) && <>
<hr className="my-2 w-100" />
<Button onClick={ visitUrl } className="align-self-center px-3">{ LocalizeText(item.clickUrlText) }</Button>
</> }
</Base> </Base>
</Flex> </Flex>
{ (!item.imageUrl || (item.imageUrl && imageFailed)) && <>
<Column alignItems="center" center gap={ 0 }>
<hr className="my-2 w-100" />
{ !item.clickUrl &&
<Button onClick={ onClose }>{ LocalizeText('generic.close') }</Button> }
{ item.clickUrl && (item.clickUrl.length > 0) && <Button onClick={ visitUrl }>{ LocalizeText(item.clickUrlText) }</Button> }
</Column>
</> }
</LayoutNotificationAlertView> </LayoutNotificationAlertView>
); );

View File

@ -1,6 +1,13 @@
.nitro-purse-container { .nitro-purse-container {
font-size: $font-size-sm; pointer-events: none;
pointer-events: all; position: absolute;
left: 0;
right: 0;
margin: auto;
display: flex;
justify-content: center;
align-items: center;
z-index: 0;
.nitro-purse { .nitro-purse {
background-color: rgba($dark, 0.95); background-color: rgba($dark, 0.95);

View File

@ -1,10 +1,9 @@
import { FriendlyTime, HabboClubLevelEnum } from '@nitrots/nitro-renderer'; import { FriendlyTime, HabboClubLevelEnum } from '@nitrots/nitro-renderer';
import { FC, useMemo } from 'react'; import { FC, useMemo } from 'react';
import { CreateLinkEvent, GetConfiguration, LocalizeText } from '../../api'; import { CreateLinkEvent, GetConfiguration, LocalizeText } from '../../api';
import { Column, Flex, Grid, LayoutCurrencyIcon, Text } from '../../common'; import { Button, Flex, LayoutCurrencyIcon, Text } from '../../common';
import { usePurse } from '../../hooks'; import { usePurse } from '../../hooks';
import { CurrencyView } from './views/CurrencyView'; import { CurrencyView } from './views/CurrencyView';
import { SeasonalView } from './views/SeasonalView';
export const PurseView: FC<{}> = props => export const PurseView: FC<{}> = props =>
{ {
@ -48,10 +47,9 @@ export const PurseView: FC<{}> = props =>
for(const type of types) for(const type of types)
{ {
if((limit > -1) && (count === limit)) break; if ((limit > -1) && (count === limit)) break;
if(seasonal) elements.push(<SeasonalView key={ type } type={ type } amount={ purse.activityPoints.get(type) } />); elements.push(<CurrencyView key={ type } type={ type } amount={ purse.activityPoints.get(type) } short={ currencyDisplayNumberShort } />)
else elements.push(<CurrencyView key={ type } type={ type } amount={ purse.activityPoints.get(type) } short={ currencyDisplayNumberShort } />);
count++; count++;
} }
@ -59,32 +57,14 @@ export const PurseView: FC<{}> = props =>
return elements; return elements;
} }
if(!purse) return null; if (!purse) return null;
return ( return <Flex className="nitro-purse-container" gap={ 2 }>
<Column alignItems="end" className="nitro-purse-container" gap={ 1 }> <CurrencyView type={ -1 } amount={ purse.credits } short={ currencyDisplayNumberShort } />
<Flex className="nitro-purse rounded-bottom p-1"> { getCurrencyElements(0, -1) }
<Grid fullWidth gap={ 1 }> { !hcDisabled && <Button className="gap-2 d-flex inset" variant="f-grey" onClick={ event => CreateLinkEvent('habboUI/open/hccenter') }>
<Column justifyContent="center" size={ hcDisabled ? 10 : 6 } gap={ 0 }> <Text truncate bold textEnd variant="white" grow>{ getClubText }</Text>
<CurrencyView type={ -1 } amount={ purse.credits } short={ currencyDisplayNumberShort } /> <LayoutCurrencyIcon type="hc" />
{ getCurrencyElements(0, 2) } </Button> }
</Column> </Flex>;
{ !hcDisabled &&
<Column center pointer size={ 4 } gap={ 1 } className="nitro-purse-subscription rounded" onClick={ event => CreateLinkEvent('habboUI/open/hccenter') }>
<LayoutCurrencyIcon type="hc" />
<Text variant="white">{ getClubText }</Text>
</Column> }
<Column justifyContent="center" size={ 2 } gap={ 0 }>
<Flex center pointer fullHeight className="nitro-purse-button p-1 rounded" onClick={ event => CreateLinkEvent('help/show') }>
<i className="icon icon-help"/>
</Flex>
<Flex center pointer fullHeight className="nitro-purse-button p-1 rounded" onClick={ event => CreateLinkEvent('user-settings/toggle') } >
<i className="icon icon-cog"/>
</Flex>
</Column>
</Grid>
</Flex>
{ getCurrencyElements(2, -1, true) }
</Column>
);
} }

View File

@ -1,7 +1,7 @@
import { FC, useMemo } from 'react'; import { FC, useMemo } from 'react';
import { OverlayTrigger, Tooltip } from 'react-bootstrap'; import { OverlayTrigger, Tooltip } from 'react-bootstrap';
import { LocalizeFormattedNumber, LocalizeShortNumber } from '../../../api'; import { CreateLinkEvent, LocalizeFormattedNumber, LocalizeShortNumber } from '../../../api';
import { Flex, LayoutCurrencyIcon, Text } from '../../../common'; import { Button, LayoutCurrencyIcon, Text } from '../../../common';
interface CurrencyViewProps interface CurrencyViewProps
{ {
@ -16,11 +16,10 @@ export const CurrencyView: FC<CurrencyViewProps> = props =>
const element = useMemo(() => const element = useMemo(() =>
{ {
return ( return <Button className="gap-2 d-flex inset" variant="f-grey" onClick={ () => CreateLinkEvent('catalog/open/currency-' + type) }>
<Flex justifyContent="end" pointer gap={ 1 } className="nitro-purse-button rounded"> <Text truncate bold textEnd variant="white" grow>{ short ? LocalizeShortNumber(amount) : LocalizeFormattedNumber(amount) }</Text>
<Text truncate textEnd variant="white" grow>{ short ? LocalizeShortNumber(amount) : LocalizeFormattedNumber(amount) }</Text> <LayoutCurrencyIcon type={ type } />
<LayoutCurrencyIcon type={ type } /> </Button>
</Flex>);
}, [ amount, short, type ]); }, [ amount, short, type ]);
if(!short) return element; if(!short) return element;

View File

@ -3,7 +3,6 @@ import { Column } from '../../common';
import { OfferView } from '../catalog/views/targeted-offer/OfferView'; import { OfferView } from '../catalog/views/targeted-offer/OfferView';
import { GroupRoomInformationView } from '../groups/views/GroupRoomInformationView'; import { GroupRoomInformationView } from '../groups/views/GroupRoomInformationView';
import { NotificationCenterView } from '../notification-center/NotificationCenterView'; import { NotificationCenterView } from '../notification-center/NotificationCenterView';
import { PurseView } from '../purse/PurseView';
import { MysteryBoxExtensionView } from '../room/widgets/mysterybox/MysteryBoxExtensionView'; import { MysteryBoxExtensionView } from '../room/widgets/mysterybox/MysteryBoxExtensionView';
import { RoomPromotesWidgetView } from '../room/widgets/room-promotes/RoomPromotesWidgetView'; import { RoomPromotesWidgetView } from '../room/widgets/room-promotes/RoomPromotesWidgetView';
@ -12,7 +11,6 @@ export const RightSideView: FC<{}> = props =>
return ( return (
<div className="nitro-right-side"> <div className="nitro-right-side">
<Column position="relative" gap={ 1 }> <Column position="relative" gap={ 1 }>
<PurseView />
<GroupRoomInformationView /> <GroupRoomInformationView />
<MysteryBoxExtensionView /> <MysteryBoxExtensionView />
<OfferView/> <OfferView/>

View File

@ -34,7 +34,7 @@ export const RoomView: FC<{}> = props =>
}, []); }, []);
return ( return (
<Base fit innerRef={ elementRef } className={ (!roomSession && 'd-none') }> <Base fit innerRef={ elementRef } className={ (!roomSession && 'd-none') + ' nitro-room' }>
{ roomSession && { roomSession &&
<> <>
<RoomWidgetsView /> <RoomWidgetsView />

View File

@ -3,7 +3,7 @@ import { Dispatch, FC, FocusEvent, KeyboardEvent, SetStateAction, useEffect, use
import { FaPencilAlt, FaTimes } from 'react-icons/fa'; import { FaPencilAlt, FaTimes } from 'react-icons/fa';
import { AvatarInfoUser, CloneObject, GetConfiguration, GetGroupInformation, GetSessionDataManager, GetUserProfile, LocalizeText, SendMessageComposer } from '../../../../../api'; import { AvatarInfoUser, CloneObject, GetConfiguration, GetGroupInformation, GetSessionDataManager, GetUserProfile, LocalizeText, SendMessageComposer } from '../../../../../api';
import { Column, Flex, LayoutAvatarImageView, LayoutBadgeImageView, Text, UserProfileIconView } from '../../../../../common'; import { Column, Flex, LayoutAvatarImageView, LayoutBadgeImageView, Text, UserProfileIconView } from '../../../../../common';
import { useMessageEvent, useRoom, useRoomSessionManagerEvent } from '../../../../../hooks'; import { useMessageEvent, useRoom, useRoomSessionManagerEvent, useSessionInfo } from '../../../../../hooks';
import { InfoStandWidgetUserRelationshipsView } from './InfoStandWidgetUserRelationshipsView'; import { InfoStandWidgetUserRelationshipsView } from './InfoStandWidgetUserRelationshipsView';
import { InfoStandWidgetUserTagsView } from './InfoStandWidgetUserTagsView'; import { InfoStandWidgetUserTagsView } from './InfoStandWidgetUserTagsView';
@ -21,15 +21,7 @@ export const InfoStandWidgetUserView: FC<InfoStandWidgetUserViewProps> = props =
const [ isEditingMotto, setIsEditingMotto ] = useState(false); const [ isEditingMotto, setIsEditingMotto ] = useState(false);
const [ relationships, setRelationships ] = useState<RelationshipStatusInfoMessageParser>(null); const [ relationships, setRelationships ] = useState<RelationshipStatusInfoMessageParser>(null);
const { roomSession = null } = useRoom(); const { roomSession = null } = useRoom();
const { saveMotto } = useSessionInfo();
const saveMotto = (motto: string) =>
{
if(!isEditingMotto || (motto.length > GetConfiguration<number>('motto.max.length', 38))) return;
roomSession.sendMottoMessage(motto);
setIsEditingMotto(false);
}
const onMottoBlur = (event: FocusEvent<HTMLInputElement>) => saveMotto(event.target.value); const onMottoBlur = (event: FocusEvent<HTMLInputElement>) => saveMotto(event.target.value);

View File

@ -10,6 +10,12 @@
padding-right: 10px; padding-right: 10px;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
position: absolute;
bottom:55px;
left:0;
right:0;
max-width: 500px;
margin:auto;
@include media-breakpoint-down(sm) { @include media-breakpoint-down(sm) {
display: flex; display: flex;

View File

@ -1,7 +1,7 @@
import { GetGuestRoomResultEvent, NavigatorSearchComposer, RateFlatMessageComposer } from '@nitrots/nitro-renderer'; import { GetGuestRoomResultEvent, NavigatorSearchComposer, RateFlatMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { CreateLinkEvent, GetRoomEngine, LocalizeText, SendMessageComposer } from '../../../../api'; import { CreateLinkEvent, GetRoomEngine, LocalizeText, SendMessageComposer } from '../../../../api';
import { Base, classNames, Column, Flex, Text, TransitionAnimation, TransitionAnimationTypes } from '../../../../common'; import { Base, Column, Flex, Text, TransitionAnimation, TransitionAnimationTypes, classNames } from '../../../../common';
import { useMessageEvent, useNavigator, useRoom } from '../../../../hooks'; import { useMessageEvent, useNavigator, useRoom } from '../../../../hooks';
export const RoomToolsWidgetView: FC<{}> = props => export const RoomToolsWidgetView: FC<{}> = props =>
@ -76,6 +76,7 @@ export const RoomToolsWidgetView: FC<{}> = props =>
<Base pointer title={ LocalizeText('room.settings.button.text') } className="icon icon-cog" onClick={ () => handleToolClick('settings') } /> <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.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" /> <Base pointer title={ LocalizeText('room.chathistory.button.text') } onClick={ () => handleToolClick('chat_history') } className="icon icon-chat-history" />
<Base pointer title={ LocalizeText('room.chathistory.button.text') } onClick={ event => CreateLinkEvent('camera/toggle') } className="icon icon-camera-small" />
{ navigatorData.canRate && { navigatorData.canRate &&
<Base pointer title={ LocalizeText('room.like.button.text') } onClick={ () => handleToolClick('like_room') } className="icon icon-like-room" /> } <Base pointer title={ LocalizeText('room.like.button.text') } onClick={ () => handleToolClick('like_room') } className="icon icon-like-room" /> }
</Column> </Column>

View File

@ -1,17 +1,54 @@
.nitro-toolbar { .nitro-toolbar,
.nitro-friendbar {
position: absolute; position: absolute;
bottom: 0;
left: 0; left: 0;
width: 100%; right:0;
height: $toolbar-height; width: 100%;
z-index: $toolbar-zindex; z-index: $toolbar-zindex;
background: repeating-linear-gradient($f-grey, $f-grey 50%, $f-grey-sec 50%, $f-grey-sec 100%);
border-bottom:1px solid black;
border-top:1px solid black;
box-shadow: inset 0 1px lighten($f-grey, 10%), inset 0 -1px lighten($f-grey-sec, 10%);
pointer-events: all; pointer-events: all;
background: rgba($dark, 0.95);
box-shadow: inset 0px 5px lighten(rgba($dark, 0.6), 2.5),
inset 0 -4px darken(rgba($dark, 0.6), 4); &::before {
content:'';
position: absolute;
height:1px;
top:0;
bottom:0;
left: 0;
right:0;
margin:auto;
background: $f-grey-sec;
box-shadow: 0 1px $f-grey;
z-index: 1;
}
.tb-splitter {
width:1px;
background-color: black;
box-shadow: 0px 1px 0 lighten($f-grey, 10%), 0px -1px 0 darken($f-grey-sec, 10);
height: 33px;
}
.btn {
filter: drop-shadow(0px 1px 0 lighten($f-grey, 10%)) drop-shadow(0px -1px 0 darken($f-grey-sec, 10));
&:not(:disabled) {
pointer-events: all;
}
}
> * {
z-index: 2;
}
.navigation-item { .navigation-item {
position: relative; position: relative;
z-index: 40;
filter: drop-shadow(0px 1px 0 lighten($f-grey, 10%)) drop-shadow(0px -1px 0 darken($f-grey-sec, 10));
&.item-avatar { &.item-avatar {
width: 50px; width: 50px;
@ -20,7 +57,7 @@
.avatar-image { .avatar-image {
margin-left: -5px; margin-left: -5px;
margin-top: 25px; margin-top: 40px;
} }
} }
@ -37,14 +74,15 @@
filter: none; filter: none;
} }
} }
}
#toolbar-chat-input-container { .nitro-friendbar{
bottom:0;
}
@include media-breakpoint-down(sm) {
width: 0px; .nitro-toolbar {
height: 0px; top:0;
}
}
} }
.nitro-toolbar-me { .nitro-toolbar-me {

View File

@ -3,6 +3,7 @@ import { FC, useState } from 'react';
import { CreateLinkEvent, GetConfiguration, GetSessionDataManager, MessengerIconState, OpenMessengerChat, VisitDesktop } from '../../api'; import { CreateLinkEvent, GetConfiguration, GetSessionDataManager, MessengerIconState, OpenMessengerChat, VisitDesktop } from '../../api';
import { Base, Flex, LayoutAvatarImageView, LayoutItemCountView, TransitionAnimation, TransitionAnimationTypes } from '../../common'; import { Base, Flex, LayoutAvatarImageView, LayoutItemCountView, TransitionAnimation, TransitionAnimationTypes } from '../../common';
import { useAchievements, useFriends, useInventoryUnseenTracker, useMessageEvent, useMessenger, useRoomEngineEvent, useSessionInfo } from '../../hooks'; import { useAchievements, useFriends, useInventoryUnseenTracker, useMessageEvent, useMessenger, useRoomEngineEvent, useSessionInfo } from '../../hooks';
import { PurseView } from '../purse/PurseView';
import { ToolbarMeView } from './ToolbarMeView'; import { ToolbarMeView } from './ToolbarMeView';
export const ToolbarView: FC<{ isInRoom: boolean }> = props => export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
@ -10,7 +11,7 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
const { isInRoom } = props; const { isInRoom } = props;
const [ isMeExpanded, setMeExpanded ] = useState(false); const [ isMeExpanded, setMeExpanded ] = useState(false);
const [ useGuideTool, setUseGuideTool ] = useState(false); const [ useGuideTool, setUseGuideTool ] = useState(false);
const { userFigure = null } = useSessionInfo(); const { userFigure = null, userName = null } = useSessionInfo();
const { getFullCount = 0 } = useInventoryUnseenTracker(); const { getFullCount = 0 } = useInventoryUnseenTracker();
const { getTotalUnseen = 0 } = useAchievements(); const { getTotalUnseen = 0 } = useAchievements();
const { requests = [] } = useFriends(); const { requests = [] } = useFriends();
@ -68,43 +69,53 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
<TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ isMeExpanded } timeout={ 300 }> <TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ isMeExpanded } timeout={ 300 }>
<ToolbarMeView useGuideTool={ useGuideTool } unseenAchievementCount={ getTotalUnseen } setMeExpanded={ setMeExpanded } /> <ToolbarMeView useGuideTool={ useGuideTool } unseenAchievementCount={ getTotalUnseen } setMeExpanded={ setMeExpanded } />
</TransitionAnimation> </TransitionAnimation>
<Flex alignItems="center" justifyContent="between" gap={ 2 } className="nitro-toolbar py-1 px-3"> <Flex alignItems="center" justifyContent="between" gap={ 2 } className="nitro-toolbar px-4" overflow="hidden">
<Flex gap={ 2 } alignItems="center"> <Flex gap={ 3 } alignItems="center" fullHeight>
<Flex alignItems="center" gap={ 2 }> { isInRoom &&
<Base pointer className="navigation-item icon icon-habbo" onClick={ event => VisitDesktop() } /> }
{ !isInRoom &&
<Base pointer className="navigation-item icon icon-house" onClick={ event => CreateLinkEvent('navigator/goto/home') } /> }
<Base pointer className="navigation-item icon icon-rooms" onClick={ event => CreateLinkEvent('navigator/toggle') } />
{ GetConfiguration('game.center.enabled') && <Base pointer className="navigation-item icon icon-game" onClick={ event => CreateLinkEvent('games/toggle') } /> }
<Base pointer className="navigation-item icon icon-catalog" onClick={ event => CreateLinkEvent('catalog/toggle') } />
<Base pointer className="navigation-item icon icon-inventory" onClick={ event => CreateLinkEvent('inventory/toggle') }>
{ (getFullCount > 0) &&
<LayoutItemCountView count={ getFullCount } /> }
</Base>
<Base className="tb-splitter"/>
</Flex>
<PurseView />
<Flex gap={ 3 } alignItems="center" fullHeight>
<Base className="tb-splitter" />
{ isMod &&
<Base pointer className="navigation-item icon icon-modtools" onClick={ event => CreateLinkEvent('mod-tools/toggle') } /> }
<Flex center pointer fullHeight className="navigation-item" onClick={ event => CreateLinkEvent('help/show') }>
<i className="icon icon-help"/>
</Flex>
<Flex center pointer fullHeight className="navigation-item" onClick={ event => CreateLinkEvent('user-settings/toggle') } >
<i className="icon icon-cog"/>
</Flex>
<Flex gap={ 0 } alignItems="center">
<Flex center pointer className={ 'navigation-item item-avatar ' + (isMeExpanded ? 'active ' : '') } onClick={ event => setMeExpanded(!isMeExpanded) }> <Flex center pointer className={ 'navigation-item item-avatar ' + (isMeExpanded ? 'active ' : '') } onClick={ event => setMeExpanded(!isMeExpanded) }>
<LayoutAvatarImageView figure={ userFigure } direction={ 2 } position="absolute" /> <LayoutAvatarImageView figure={ userFigure } direction={ 3 } position="absolute" headOnly />
{ (getTotalUnseen > 0) && { (getTotalUnseen > 0) &&
<LayoutItemCountView count={ getTotalUnseen } /> } <LayoutItemCountView count={ getTotalUnseen } /> }
</Flex> </Flex>
{ isInRoom && { userName }
<Base pointer className="navigation-item icon icon-habbo" onClick={ event => VisitDesktop() } /> }
{ !isInRoom &&
<Base pointer className="navigation-item icon icon-house" onClick={ event => CreateLinkEvent('navigator/goto/home') } /> }
<Base pointer className="navigation-item icon icon-rooms" onClick={ event => CreateLinkEvent('navigator/toggle') } />
{ GetConfiguration('game.center.enabled') && <Base pointer className="navigation-item icon icon-game" onClick={ event => CreateLinkEvent('games/toggle') } /> }
<Base pointer className="navigation-item icon icon-catalog" onClick={ event => CreateLinkEvent('catalog/toggle') } />
<Base pointer className="navigation-item icon icon-inventory" onClick={ event => CreateLinkEvent('inventory/toggle') }>
{ (getFullCount > 0) &&
<LayoutItemCountView count={ getFullCount } /> }
</Base>
{ isInRoom &&
<Base pointer className="navigation-item icon icon-camera" onClick={ event => CreateLinkEvent('camera/toggle') } /> }
{ isMod &&
<Base pointer className="navigation-item icon icon-modtools" onClick={ event => CreateLinkEvent('mod-tools/toggle') } /> }
</Flex> </Flex>
<Flex alignItems="center" id="toolbar-chat-input-container" />
</Flex> </Flex>
<Flex alignItems="center" gap={ 2 }> </Flex>
<Flex gap={ 2 }> <Flex alignItems="center" id="toolbar-chat-input-container" />
<Base pointer className="navigation-item icon icon-friendall" onClick={ event => CreateLinkEvent('friends/toggle') }> <Flex alignItems="center" gap={ 3 } className="nitro-friendbar px-4 py-1">
{ (requests.length > 0) && <Flex gap={ 2 }>
<Base pointer className="navigation-item icon icon-friendall" onClick={ event => CreateLinkEvent('friends/toggle') }>
{ (requests.length > 0) &&
<LayoutItemCountView count={ requests.length } /> } <LayoutItemCountView count={ requests.length } /> }
</Base> </Base>
{ ((iconState === MessengerIconState.SHOW) || (iconState === MessengerIconState.UNREAD)) && { ((iconState === MessengerIconState.SHOW) || (iconState === MessengerIconState.UNREAD)) &&
<Base pointer className={ `navigation-item icon icon-message ${ (iconState === MessengerIconState.UNREAD) && 'is-unseen' }` } onClick={ event => OpenMessengerChat() } /> } <Base pointer className={ `navigation-item icon icon-message ${ (iconState === MessengerIconState.UNREAD) && 'is-unseen' }` } onClick={ event => OpenMessengerChat() } /> }
</Flex>
<Base id="toolbar-friend-bar-container" className="d-none d-lg-block" />
</Flex> </Flex>
<Base id="toolbar-friend-bar-container" fullWidth position="absolute" className="pe-none" />
</Flex> </Flex>
</> </>
); );

View File

@ -17,6 +17,17 @@ const useMessengerState = () =>
const visibleThreads = useMemo(() => messageThreads.filter(thread => (hiddenThreadIds.indexOf(thread.threadId) === -1)), [ messageThreads, hiddenThreadIds ]); const visibleThreads = useMemo(() => messageThreads.filter(thread => (hiddenThreadIds.indexOf(thread.threadId) === -1)), [ messageThreads, hiddenThreadIds ]);
const activeThread = useMemo(() => ((activeThreadId > 0) && visibleThreads.find(thread => (thread.threadId === activeThreadId) || null)), [ activeThreadId, visibleThreads ]); const activeThread = useMemo(() => ((activeThreadId > 0) && visibleThreads.find(thread => (thread.threadId === activeThreadId) || null)), [ activeThreadId, visibleThreads ]);
const unreadThreads = useMemo(() =>
{
let count = 0;
messageThreads.forEach(t =>
{
count += t.unreadCount
})
return count
}, [ messageThreads ] )
const getMessageThread = (userId: number) => const getMessageThread = (userId: number) =>
{ {
@ -181,7 +192,7 @@ const useMessengerState = () =>
}); });
}, [ visibleThreads ]); }, [ visibleThreads ]);
return { messageThreads, activeThread, iconState, visibleThreads, getMessageThread, setActiveThreadId, closeThread, sendMessage }; return { messageThreads, activeThread, iconState, visibleThreads, getMessageThread, setActiveThreadId, closeThread, sendMessage, unreadThreads };
} }
export const useMessenger = () => useBetween(useMessengerState); export const useMessenger = () => useBetween(useMessengerState);

View File

@ -27,6 +27,8 @@ const useRoomState = () =>
{ {
roomBackground.tint = newColor; roomBackground.tint = newColor;
} }
console.log(hue, saturation, lightness);
} }
const updateRoomFilter = (color: number) => const updateRoomFilter = (color: number) =>
@ -56,7 +58,7 @@ const useRoomState = () =>
if(RoomId.isRoomPreviewerId(event.roomId)) return; if(RoomId.isRoomPreviewerId(event.roomId)) return;
if(event.enable) updateRoomBackgroundColor(event.hue, event.saturation, event.lightness, true); if(event.enable) updateRoomBackgroundColor(event.hue, event.saturation, event.lightness, true);
else updateRoomBackgroundColor(0, 0, 0, true); else updateRoomBackgroundColor(146, 206, 21, true);
}); });
useRoomEngineEvent<RoomBackgroundColorEvent>(RoomBackgroundColorEvent.ROOM_COLOR, event => useRoomEngineEvent<RoomBackgroundColorEvent>(RoomBackgroundColorEvent.ROOM_COLOR, event =>
@ -91,6 +93,26 @@ const useRoomState = () =>
case RoomEngineEvent.INITIALIZED: case RoomEngineEvent.INITIALIZED:
SetActiveRoomId(event.roomId); SetActiveRoomId(event.roomId);
setRoomSession(session); setRoomSession(session);
// const room = GetRoomEngine().createTextureFromRoom(roomSession.roomId, 1);
// const canvas = GetRoomEngine().getRoomInstanceRenderingCanvas(roomSession.roomId, 1);
// const texture = new NitroSprite(room);
// console.log(TextureUtils.generateImageUrl(texture))
// if (!canvas) return;
// const master = (canvas.master as NitroContainer);
// texture.width = room.width;
// texture.height = room.height
// texture.x = 30;
// texture.y = 30;
// master.addChildAt(texture, 1);
return; return;
case RoomEngineEvent.DISPOSED: case RoomEngineEvent.DISPOSED:
setRoomSession(null); setRoomSession(null);
@ -222,10 +244,26 @@ const useRoomState = () =>
const filter = new AdjustmentFilter(); const filter = new AdjustmentFilter();
const master = (canvas.master as NitroContainer); const master = (canvas.master as NitroContainer);
background.tint = 0; background.tint = ColorConverter.hslToRGB(((((146 & 0xFF) << 16) + ((206 & 0xFF) << 8)) + (21 & 0xFF)));
background.width = width; background.width = width;
background.height = height; background.height = height;
// const room = GetRoomEngine().createTextureFromRoom(roomSession.roomId, 1);
// const texture = new NitroSprite(room);
// texture.width = room.width;
// texture.height = room.height
// texture.x = room.frame.x
// texture.y = room.frame.y
// texture.tint = 0
// texture.alpha = 0.5
// console.log(TextureUtils.generateImageUrl(texture))
//master.addChildAt(texture,0)
master.addChildAt(background, 0); master.addChildAt(background, 0);
master.filters = [ filter ]; master.filters = [ filter ];

View File

@ -1,7 +1,7 @@
import { FigureUpdateEvent, RoomUnitChatStyleComposer, UserInfoDataParser, UserInfoEvent, UserSettingsEvent } from '@nitrots/nitro-renderer'; import { FigureUpdateEvent, RoomUnitChatStyleComposer, UserInfoDataParser, UserInfoEvent, UserMottoComposer, UserSettingsEvent } from '@nitrots/nitro-renderer';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useBetween } from 'use-between'; import { useBetween } from 'use-between';
import { GetLocalStorage, GetSessionDataManager, SendMessageComposer } from '../../api'; import { GetConfiguration, GetLocalStorage, GetSessionDataManager, SendMessageComposer } from '../../api';
import { useMessageEvent } from '../events'; import { useMessageEvent } from '../events';
import { useLocalStorage } from '../useLocalStorage'; import { useLocalStorage } from '../useLocalStorage';
@ -9,6 +9,8 @@ const useSessionInfoState = () =>
{ {
const [ userInfo, setUserInfo ] = useState<UserInfoDataParser>(null); const [ userInfo, setUserInfo ] = useState<UserInfoDataParser>(null);
const [ userFigure, setUserFigure ] = useState<string>(null); const [ userFigure, setUserFigure ] = useState<string>(null);
const [ userName, setUserName ] = useState<string>(null);
const [ motto, setMotto ] = useState<string>('');
const [ chatStyleId, setChatStyleId ] = useState<number>(0); const [ chatStyleId, setChatStyleId ] = useState<number>(0);
const [ userRespectRemaining, setUserRespectRemaining ] = useState<number>(0); const [ userRespectRemaining, setUserRespectRemaining ] = useState<number>(0);
const [ petRespectRemaining, setPetRespectRemaining ] = useState<number>(0); const [ petRespectRemaining, setPetRespectRemaining ] = useState<number>(0);
@ -35,11 +37,20 @@ const useSessionInfoState = () =>
setPetRespectRemaining(GetSessionDataManager().respectsPetLeft); setPetRespectRemaining(GetSessionDataManager().respectsPetLeft);
} }
const saveMotto = (motto: string) =>
{
if (motto.length > GetConfiguration<number>('motto.max.length', 38)) return;
setMotto(motto);
SendMessageComposer(new UserMottoComposer(motto))
}
useMessageEvent<UserInfoEvent>(UserInfoEvent, event => useMessageEvent<UserInfoEvent>(UserInfoEvent, event =>
{ {
const parser = event.getParser(); const parser = event.getParser();
setUserInfo(parser.userInfo); setUserInfo(parser.userInfo);
setUserName(parser.userInfo.username);
setMotto(parser.userInfo.motto);
setUserFigure(parser.userInfo.figure); setUserFigure(parser.userInfo.figure);
setUserRespectRemaining(parser.userInfo.respectsRemaining); setUserRespectRemaining(parser.userInfo.respectsRemaining);
setPetRespectRemaining(parser.userInfo.respectsPetRemaining); setPetRespectRemaining(parser.userInfo.respectsPetRemaining);
@ -87,7 +98,7 @@ const useSessionInfoState = () =>
} }
}, [ setScreenSize ]); }, [ setScreenSize ]);
return { userInfo, userFigure, chatStyleId, userRespectRemaining, petRespectRemaining, respectUser, respectPet, updateChatStyleId }; return { userInfo, userFigure, userName,motto, chatStyleId, userRespectRemaining, petRespectRemaining, respectUser, respectPet, updateChatStyleId, setMotto, saveMotto };
} }
export const useSessionInfo = () => useBetween(useSessionInfoState); export const useSessionInfo = () => useBetween(useSessionInfoState);