mirror of
https://github.com/billsonnn/nitro-react.git
synced 2024-11-23 14:40:50 +01:00
Merge branch 'dev' into @update/groups
This commit is contained in:
commit
54312a412e
@ -8,6 +8,7 @@
|
|||||||
"habbopages.url": "https://website.com/habbopages/",
|
"habbopages.url": "https://website.com/habbopages/",
|
||||||
"chat.viewer.height.percentage": 0.40,
|
"chat.viewer.height.percentage": 0.40,
|
||||||
"widget.dimmer.colorwheel": false,
|
"widget.dimmer.colorwheel": false,
|
||||||
|
"avatar.wardrobe.max.slots": 10,
|
||||||
"hotelview": {
|
"hotelview": {
|
||||||
"widgets": {
|
"widgets": {
|
||||||
"slot.1.widget": "promoarticle",
|
"slot.1.widget": "promoarticle",
|
||||||
|
@ -70,6 +70,7 @@ $camera-checkout-width: 350px;
|
|||||||
$room-info-width: 325px;
|
$room-info-width: 325px;
|
||||||
|
|
||||||
$nitro-group-creator-width: 383px;
|
$nitro-group-creator-width: 383px;
|
||||||
|
$nitro-mod-tools-width: 175px;
|
||||||
|
|
||||||
.nitro-app {
|
.nitro-app {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
14
src/App.tsx
14
src/App.tsx
@ -1,13 +1,14 @@
|
|||||||
import { ConfigurationEvent, HabboWebTools, LegacyExternalInterface, Nitro, NitroCommunicationDemoEvent, NitroEvent, NitroLocalizationEvent, NitroVersion, RoomEngineEvent, WebGL } from '@nitrots/nitro-renderer';
|
import { ConfigurationEvent, HabboWebTools, LegacyExternalInterface, Nitro, NitroCommunicationDemoEvent, NitroEvent, NitroLocalizationEvent, NitroVersion, RoomEngineEvent, WebGL } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useState } from 'react';
|
import { FC, useCallback, useState } from 'react';
|
||||||
import { GetCommunication, GetConfiguration, GetNitroInstance } from './api';
|
import { GetCommunication, GetConfiguration, GetNitroInstance } from './api';
|
||||||
|
import { Base } from './common';
|
||||||
|
import { LoadingView } from './components/loading/LoadingView';
|
||||||
|
import { MainView } from './components/main/MainView';
|
||||||
import { useConfigurationEvent } from './hooks/events/core/configuration/configuration-event';
|
import { useConfigurationEvent } from './hooks/events/core/configuration/configuration-event';
|
||||||
import { useLocalizationEvent } from './hooks/events/nitro/localization/localization-event';
|
import { useLocalizationEvent } from './hooks/events/nitro/localization/localization-event';
|
||||||
import { dispatchMainEvent, useMainEvent } from './hooks/events/nitro/main-event';
|
import { dispatchMainEvent, useMainEvent } from './hooks/events/nitro/main-event';
|
||||||
import { useRoomEngineEvent } from './hooks/events/nitro/room/room-engine-event';
|
import { useRoomEngineEvent } from './hooks/events/nitro/room/room-engine-event';
|
||||||
import { TransitionAnimation, TransitionAnimationTypes } from './layout';
|
import { TransitionAnimation, TransitionAnimationTypes } from './layout';
|
||||||
import { LoadingView } from './views/loading/LoadingView';
|
|
||||||
import { MainView } from './views/main/MainView';
|
|
||||||
|
|
||||||
export const App: FC<{}> = props =>
|
export const App: FC<{}> = props =>
|
||||||
{
|
{
|
||||||
@ -127,12 +128,13 @@ export const App: FC<{}> = props =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="nitro-app overflow-hidden">
|
<Base fit overflow="hidden">
|
||||||
{ (!isReady || isError) && <LoadingView isError={ isError } message={ message } /> }
|
{ (!isReady || isError) &&
|
||||||
|
<LoadingView isError={ isError } message={ message } /> }
|
||||||
<TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ (isReady && !isError) }>
|
<TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ (isReady && !isError) }>
|
||||||
<MainView />
|
<MainView />
|
||||||
</TransitionAnimation>
|
</TransitionAnimation>
|
||||||
<div id="draggable-windows-container" />
|
<Base id="draggable-windows-container" />
|
||||||
</div>
|
</Base>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,11 @@
|
|||||||
font-weight: $font-weight-normal;
|
font-weight: $font-weight-normal;
|
||||||
color: $btn-link-color;
|
color: $btn-link-color;
|
||||||
text-decoration: $link-decoration;
|
text-decoration: $link-decoration;
|
||||||
|
box-shadow: none !important;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: $btn-link-color !important;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $btn-link-hover-color;
|
color: $btn-link-hover-color;
|
||||||
|
@ -732,7 +732,7 @@ $table-cell-padding-x-sm: .25rem !default;
|
|||||||
|
|
||||||
$table-cell-vertical-align: top !default;
|
$table-cell-vertical-align: top !default;
|
||||||
|
|
||||||
$table-color: $body-color !default;
|
$table-color: $black !default;
|
||||||
$table-bg: transparent !default;
|
$table-bg: transparent !default;
|
||||||
$table-accent-bg: transparent !default;
|
$table-accent-bg: transparent !default;
|
||||||
|
|
||||||
|
@ -90,3 +90,10 @@ ul {
|
|||||||
.flex-basis-max-content {
|
.flex-basis-max-content {
|
||||||
flex-basis: max-content;
|
flex-basis: max-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.striped-children {
|
||||||
|
|
||||||
|
> :nth-child(1) {
|
||||||
|
background-color: $table-striped-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { CSSProperties, DetailedHTMLProps, FC, HTMLAttributes, LegacyRef, useMemo } from 'react';
|
import { CSSProperties, DetailedHTMLProps, FC, HTMLAttributes, LegacyRef, useMemo } from 'react';
|
||||||
import { ColorVariantType, OverflowType, PositionType } from './types';
|
import { ColorVariantType, FloatType, OverflowType, PositionType } from './types';
|
||||||
|
|
||||||
export interface BaseProps<T = HTMLElement> extends DetailedHTMLProps<HTMLAttributes<T>, T>
|
export interface BaseProps<T = HTMLElement> extends DetailedHTMLProps<HTMLAttributes<T>, T>
|
||||||
{
|
{
|
||||||
@ -11,6 +11,7 @@ export interface BaseProps<T = HTMLElement> extends DetailedHTMLProps<HTMLAttrib
|
|||||||
fullHeight?: boolean;
|
fullHeight?: boolean;
|
||||||
overflow?: OverflowType;
|
overflow?: OverflowType;
|
||||||
position?: PositionType;
|
position?: PositionType;
|
||||||
|
float?: FloatType;
|
||||||
pointer?: boolean;
|
pointer?: boolean;
|
||||||
textColor?: ColorVariantType;
|
textColor?: ColorVariantType;
|
||||||
classNames?: string[];
|
classNames?: string[];
|
||||||
@ -18,7 +19,7 @@ export interface BaseProps<T = HTMLElement> extends DetailedHTMLProps<HTMLAttrib
|
|||||||
|
|
||||||
export const Base: FC<BaseProps<HTMLDivElement>> = props =>
|
export const Base: FC<BaseProps<HTMLDivElement>> = props =>
|
||||||
{
|
{
|
||||||
const { ref = null, innerRef = null, fit = false, grow = false, shrink = false, fullWidth = false, fullHeight = false, overflow = null, position = null, pointer = false, textColor = null, classNames = [], className = '', style = {}, ...rest } = props;
|
const { ref = null, innerRef = null, fit = false, grow = false, shrink = false, fullWidth = false, fullHeight = false, overflow = null, position = null, float = null, pointer = false, textColor = null, classNames = [], className = '', style = {}, ...rest } = props;
|
||||||
|
|
||||||
const getClassNames = useMemo(() =>
|
const getClassNames = useMemo(() =>
|
||||||
{
|
{
|
||||||
@ -36,6 +37,8 @@ export const Base: FC<BaseProps<HTMLDivElement>> = props =>
|
|||||||
|
|
||||||
if(position) newClassNames.push('position-' + position);
|
if(position) newClassNames.push('position-' + position);
|
||||||
|
|
||||||
|
if(float) newClassNames.push('float-' + float);
|
||||||
|
|
||||||
if(pointer) newClassNames.push('cursor-pointer');
|
if(pointer) newClassNames.push('cursor-pointer');
|
||||||
|
|
||||||
if(textColor) newClassNames.push('text-' + textColor);
|
if(textColor) newClassNames.push('text-' + textColor);
|
||||||
@ -43,7 +46,7 @@ export const Base: FC<BaseProps<HTMLDivElement>> = props =>
|
|||||||
if(classNames.length) newClassNames.push(...classNames);
|
if(classNames.length) newClassNames.push(...classNames);
|
||||||
|
|
||||||
return newClassNames;
|
return newClassNames;
|
||||||
}, [ fit, grow, shrink, fullWidth, fullHeight, overflow, position, pointer, textColor, classNames ]);
|
}, [ fit, grow, shrink, fullWidth, fullHeight, overflow, position, float, pointer, textColor, classNames ]);
|
||||||
|
|
||||||
const getClassName = useMemo(() =>
|
const getClassName = useMemo(() =>
|
||||||
{
|
{
|
||||||
@ -51,7 +54,7 @@ export const Base: FC<BaseProps<HTMLDivElement>> = props =>
|
|||||||
|
|
||||||
if(className.length) newClassName += (' ' + className);
|
if(className.length) newClassName += (' ' + className);
|
||||||
|
|
||||||
return newClassName;
|
return newClassName.trim();
|
||||||
}, [ getClassNames, className ]);
|
}, [ getClassNames, className ]);
|
||||||
|
|
||||||
const getStyle = useMemo(() =>
|
const getStyle = useMemo(() =>
|
||||||
|
@ -2,7 +2,7 @@ import { FC, useMemo } from 'react';
|
|||||||
import { CSSProperties } from 'styled-components';
|
import { CSSProperties } from 'styled-components';
|
||||||
import { Base, BaseProps } from './Base';
|
import { Base, BaseProps } from './Base';
|
||||||
import { GridContextProvider } from './GridContext';
|
import { GridContextProvider } from './GridContext';
|
||||||
import { SpacingType } from './types';
|
import { AlignItemType, AlignSelfType, JustifyContentType, SpacingType } from './types';
|
||||||
|
|
||||||
export interface GridProps extends BaseProps<HTMLDivElement>
|
export interface GridProps extends BaseProps<HTMLDivElement>
|
||||||
{
|
{
|
||||||
@ -10,11 +10,15 @@ export interface GridProps extends BaseProps<HTMLDivElement>
|
|||||||
gap?: SpacingType;
|
gap?: SpacingType;
|
||||||
maxContent?: boolean;
|
maxContent?: boolean;
|
||||||
columnCount?: number;
|
columnCount?: number;
|
||||||
|
center?: boolean;
|
||||||
|
alignSelf?: AlignSelfType;
|
||||||
|
alignItems?: AlignItemType;
|
||||||
|
justifyContent?: JustifyContentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Grid: FC<GridProps> = props =>
|
export const Grid: FC<GridProps> = props =>
|
||||||
{
|
{
|
||||||
const { inline = false, gap = 2, maxContent = false, columnCount = 0, fullHeight = true, classNames = [], style = {}, ...rest } = props;
|
const { inline = false, gap = 2, maxContent = false, columnCount = 0, center = false, alignSelf = null, alignItems = null, justifyContent = null, fullHeight = true, classNames = [], style = {}, ...rest } = props;
|
||||||
|
|
||||||
const getClassNames = useMemo(() =>
|
const getClassNames = useMemo(() =>
|
||||||
{
|
{
|
||||||
@ -28,10 +32,18 @@ export const Grid: FC<GridProps> = props =>
|
|||||||
|
|
||||||
if(maxContent) newClassNames.push('flex-basis-max-content');
|
if(maxContent) newClassNames.push('flex-basis-max-content');
|
||||||
|
|
||||||
|
if(alignSelf) newClassNames.push('align-self-' + alignSelf);
|
||||||
|
|
||||||
|
if(alignItems) newClassNames.push('align-items-' + alignItems);
|
||||||
|
|
||||||
|
if(justifyContent) newClassNames.push('justify-content-' + justifyContent);
|
||||||
|
|
||||||
|
if(!alignItems && !justifyContent && center) newClassNames.push('align-items-center', 'justify-content-center');
|
||||||
|
|
||||||
if(classNames.length) newClassNames.push(...classNames);
|
if(classNames.length) newClassNames.push(...classNames);
|
||||||
|
|
||||||
return newClassNames;
|
return newClassNames;
|
||||||
}, [ inline, gap, maxContent, classNames ]);
|
}, [ inline, gap, maxContent, alignSelf, alignItems, justifyContent, center, classNames ]);
|
||||||
|
|
||||||
const getStyle = useMemo(() =>
|
const getStyle = useMemo(() =>
|
||||||
{
|
{
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { FC, useMemo } from 'react';
|
import { FC, useMemo } from 'react';
|
||||||
import { Base, BaseProps } from './Base';
|
import { Base, BaseProps } from './Base';
|
||||||
import { ColorVariantType, FontSizeType, FontWeightType } from './types';
|
import { ColorVariantType, FontSizeType, FontWeightType, TextAlignType } from './types';
|
||||||
|
|
||||||
export interface TextProps extends BaseProps<HTMLDivElement>
|
export interface TextProps extends BaseProps<HTMLDivElement>
|
||||||
{
|
{
|
||||||
variant?: ColorVariantType;
|
variant?: ColorVariantType;
|
||||||
fontWeight?: FontWeightType;
|
fontWeight?: FontWeightType;
|
||||||
fontSize?: FontSizeType;
|
fontSize?: FontSizeType;
|
||||||
|
align?: TextAlignType;
|
||||||
bold?: boolean;
|
bold?: boolean;
|
||||||
underline?: boolean;
|
underline?: boolean;
|
||||||
italics?: boolean;
|
italics?: boolean;
|
||||||
@ -14,11 +15,14 @@ export interface TextProps extends BaseProps<HTMLDivElement>
|
|||||||
center?: boolean;
|
center?: boolean;
|
||||||
textEnd?: boolean;
|
textEnd?: boolean;
|
||||||
small?: boolean;
|
small?: boolean;
|
||||||
|
wrap?: boolean;
|
||||||
|
noWrap?: boolean;
|
||||||
|
textBreak?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Text: FC<TextProps> = props =>
|
export const Text: FC<TextProps> = props =>
|
||||||
{
|
{
|
||||||
const { variant = 'black', fontWeight = null, fontSize = 0, bold = false, underline = false, italics = false, truncate = false, center = false, textEnd = false, small = false, ...rest } = props;
|
const { variant = 'black', fontWeight = null, fontSize = 0, align = null, bold = false, underline = false, italics = false, truncate = false, center = false, textEnd = false, small = false, wrap = false, noWrap = false, textBreak = false, ...rest } = props;
|
||||||
|
|
||||||
const getClassNames = useMemo(() =>
|
const getClassNames = useMemo(() =>
|
||||||
{
|
{
|
||||||
@ -32,6 +36,8 @@ export const Text: FC<TextProps> = props =>
|
|||||||
|
|
||||||
if(fontSize) newClassNames.push('fs-' + fontSize);
|
if(fontSize) newClassNames.push('fs-' + fontSize);
|
||||||
|
|
||||||
|
if(align) newClassNames.push('text-' + align);
|
||||||
|
|
||||||
if(underline) newClassNames.push('text-decoration-underline');
|
if(underline) newClassNames.push('text-decoration-underline');
|
||||||
|
|
||||||
if(italics) newClassNames.push('fst-italic');
|
if(italics) newClassNames.push('fst-italic');
|
||||||
@ -44,8 +50,14 @@ export const Text: FC<TextProps> = props =>
|
|||||||
|
|
||||||
if(small) newClassNames.push('small');
|
if(small) newClassNames.push('small');
|
||||||
|
|
||||||
|
if(wrap) newClassNames.push('text-wrap');
|
||||||
|
|
||||||
|
if(noWrap) newClassNames.push('text-nowrap');
|
||||||
|
|
||||||
|
if(textBreak) newClassNames.push('text-break');
|
||||||
|
|
||||||
return newClassNames;
|
return newClassNames;
|
||||||
}, [ variant, fontWeight, fontSize, bold, underline, italics, truncate, center, textEnd, small ]);
|
}, [ variant, fontWeight, fontSize, align, bold, underline, italics, truncate, center, textEnd, small, wrap, noWrap, textBreak ]);
|
||||||
|
|
||||||
return <Base classNames={ getClassNames } { ...rest } />;
|
return <Base classNames={ getClassNames } { ...rest } />;
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
border-color: $grid-active-border-color !important;
|
border-color: $grid-active-border-color !important;
|
||||||
background-color: $grid-active-bg-color !important;
|
background-color: $grid-active-bg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
@ -25,4 +25,19 @@
|
|||||||
.avatar-image {
|
.avatar-image {
|
||||||
background-position-y: -35px;
|
background-position-y: -35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.has-highlight {
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
z-index: 2;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 50%;
|
||||||
|
background-color: $white;
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,12 +14,13 @@ export interface LayoutGridItemProps extends ColumnProps
|
|||||||
itemUniqueSoldout?: boolean;
|
itemUniqueSoldout?: boolean;
|
||||||
itemUniqueNumber?: number;
|
itemUniqueNumber?: number;
|
||||||
itemUnseen?: boolean;
|
itemUnseen?: boolean;
|
||||||
|
itemHighlight?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LayoutGridItem: FC<LayoutGridItemProps> = props =>
|
export const LayoutGridItem: FC<LayoutGridItemProps> = props =>
|
||||||
{
|
{
|
||||||
const { itemImage = undefined, itemColor = undefined, itemActive = false, itemCount = 1, itemCountMinimum = 1, itemUniqueSoldout = false, itemUniqueNumber = -2, itemUnseen = false, disabled = false, center = true, column = true, style = {}, classNames = [], position = 'relative', overflow = 'hidden', children = null, ...rest } = props;
|
const { itemImage = undefined, itemColor = undefined, itemActive = false, itemCount = 1, itemCountMinimum = 1, itemUniqueSoldout = false, itemUniqueNumber = -2, itemUnseen = false, itemHighlight = false, disabled = false, center = true, column = true, style = {}, classNames = [], position = 'relative', overflow = 'hidden', children = null, ...rest } = props;
|
||||||
|
|
||||||
const getClassNames = useMemo(() =>
|
const getClassNames = useMemo(() =>
|
||||||
{
|
{
|
||||||
@ -33,6 +34,8 @@ export const LayoutGridItem: FC<LayoutGridItemProps> = props =>
|
|||||||
|
|
||||||
if(itemUnseen) newClassNames.push('unseen');
|
if(itemUnseen) newClassNames.push('unseen');
|
||||||
|
|
||||||
|
if(itemHighlight) newClassNames.push('has-highlight');
|
||||||
|
|
||||||
if(disabled) newClassNames.push('disabled')
|
if(disabled) newClassNames.push('disabled')
|
||||||
|
|
||||||
if(itemImage === null) newClassNames.push('icon', 'loading-icon');
|
if(itemImage === null) newClassNames.push('icon', 'loading-icon');
|
||||||
@ -40,7 +43,7 @@ export const LayoutGridItem: FC<LayoutGridItemProps> = props =>
|
|||||||
if(classNames.length) newClassNames.push(...classNames);
|
if(classNames.length) newClassNames.push(...classNames);
|
||||||
|
|
||||||
return newClassNames;
|
return newClassNames;
|
||||||
}, [ itemActive, itemUniqueSoldout, itemUniqueNumber, itemUnseen, disabled, itemImage, classNames ]);
|
}, [ itemActive, itemUniqueSoldout, itemUniqueNumber, itemUnseen, itemHighlight, disabled, itemImage, classNames ]);
|
||||||
|
|
||||||
const getStyle = useMemo(() =>
|
const getStyle = useMemo(() =>
|
||||||
{
|
{
|
||||||
|
1
src/common/types/FloatType.ts
Normal file
1
src/common/types/FloatType.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export type FloatType = 'start' | 'end' | 'none';
|
@ -1 +1 @@
|
|||||||
export type FontSizeType = 1 | 2 | 3 | 4 | 5;
|
export type FontSizeType = 1 | 2 | 3 | 4 | 5 | 6;
|
||||||
|
1
src/common/types/TextAlignType.ts
Normal file
1
src/common/types/TextAlignType.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export type TextAlignType = 'start' | 'center' | 'end';
|
@ -3,9 +3,11 @@ export * from './AlignSelfType';
|
|||||||
export * from './ButtonSizeType';
|
export * from './ButtonSizeType';
|
||||||
export * from './ColorVariantType';
|
export * from './ColorVariantType';
|
||||||
export * from './ColumnSizesType';
|
export * from './ColumnSizesType';
|
||||||
|
export * from './FloatType';
|
||||||
export * from './FontSizeType';
|
export * from './FontSizeType';
|
||||||
export * from './FontWeightType';
|
export * from './FontWeightType';
|
||||||
export * from './JustifyContentType';
|
export * from './JustifyContentType';
|
||||||
export * from './OverflowType';
|
export * from './OverflowType';
|
||||||
export * from './PositionType';
|
export * from './PositionType';
|
||||||
export * from './SpacingType';
|
export * from './SpacingType';
|
||||||
|
export * from './TextAlignType';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { AvatarEditorFigureCategory, FigureSetIdsMessageEvent, GetWardrobeMessageComposer, IAvatarFigureContainer, UserFigureComposer, UserWardrobePageEvent } from '@nitrots/nitro-renderer';
|
import { AvatarEditorFigureCategory, FigureSetIdsMessageEvent, GetWardrobeMessageComposer, IAvatarFigureContainer, UserFigureComposer, UserWardrobePageEvent } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useEffect, useState } from 'react';
|
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { GetAvatarRenderManager, GetClubMemberLevel, GetSessionDataManager, LocalizeText } from '../../api';
|
import { GetAvatarRenderManager, GetClubMemberLevel, GetConfiguration, GetSessionDataManager, LocalizeText } from '../../api';
|
||||||
import { Button } from '../../common/Button';
|
import { Button } from '../../common/Button';
|
||||||
import { ButtonGroup } from '../../common/ButtonGroup';
|
import { ButtonGroup } from '../../common/ButtonGroup';
|
||||||
import { Column } from '../../common/Column';
|
import { Column } from '../../common/Column';
|
||||||
@ -25,7 +25,6 @@ import { AvatarEditorWardrobeView } from './views/wardrobe/AvatarEditorWardrobeV
|
|||||||
|
|
||||||
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_MALE_FIGURE: string = 'hr-100.hd-180-7.ch-215-66.lg-270-79.sh-305-62.ha-1002-70.wa-2007';
|
||||||
const DEFAULT_FEMALE_FIGURE: string = 'hr-515-33.hd-600-1.ch-635-70.lg-716-66-62.sh-735-68';
|
const DEFAULT_FEMALE_FIGURE: string = 'hr-515-33.hd-600-1.ch-635-70.lg-716-66-62.sh-735-68';
|
||||||
const MAX_SAVED_FIGURES: number = 10;
|
|
||||||
|
|
||||||
export const AvatarEditorView: FC<{}> = props =>
|
export const AvatarEditorView: FC<{}> = props =>
|
||||||
{
|
{
|
||||||
@ -36,13 +35,15 @@ export const AvatarEditorView: FC<{}> = props =>
|
|||||||
const [ activeCategory, setActiveCategory ] = useState<IAvatarEditorCategoryModel>(null);
|
const [ activeCategory, setActiveCategory ] = useState<IAvatarEditorCategoryModel>(null);
|
||||||
const [ figureSetIds, setFigureSetIds ] = useState<number[]>([]);
|
const [ figureSetIds, setFigureSetIds ] = useState<number[]>([]);
|
||||||
const [ boundFurnitureNames, setBoundFurnitureNames ] = useState<string[]>([]);
|
const [ boundFurnitureNames, setBoundFurnitureNames ] = useState<string[]>([]);
|
||||||
const [ savedFigures, setSavedFigures ] = useState<[ IAvatarFigureContainer, string ][]>(new Array(MAX_SAVED_FIGURES));
|
const [ savedFigures, setSavedFigures ] = useState<[ IAvatarFigureContainer, string ][]>([]);
|
||||||
const [ isWardrobeVisible, setIsWardrobeVisible ] = useState(false);
|
const [ isWardrobeVisible, setIsWardrobeVisible ] = useState(false);
|
||||||
const [ lastFigure, setLastFigure ] = useState<string>(null);
|
const [ lastFigure, setLastFigure ] = useState<string>(null);
|
||||||
const [ lastGender, setLastGender ] = useState<string>(null);
|
const [ lastGender, setLastGender ] = useState<string>(null);
|
||||||
const [ needsReset, setNeedsReset ] = useState(false);
|
const [ needsReset, setNeedsReset ] = useState(false);
|
||||||
const [ isInitalized, setIsInitalized ] = useState(false);
|
const [ isInitalized, setIsInitalized ] = useState(false);
|
||||||
|
|
||||||
|
const maxWardrobeSlots = useMemo(() => GetConfiguration<number>('avatar.wardrobe.max.slots', 10), []);
|
||||||
|
|
||||||
const onAvatarEditorEvent = useCallback((event: AvatarEditorEvent) =>
|
const onAvatarEditorEvent = useCallback((event: AvatarEditorEvent) =>
|
||||||
{
|
{
|
||||||
switch(event.type)
|
switch(event.type)
|
||||||
@ -88,7 +89,7 @@ export const AvatarEditorView: FC<{}> = props =>
|
|||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
while(i < MAX_SAVED_FIGURES)
|
while(i < maxWardrobeSlots)
|
||||||
{
|
{
|
||||||
savedFigures.push([ null, null ]);
|
savedFigures.push([ null, null ]);
|
||||||
|
|
||||||
@ -103,7 +104,7 @@ export const AvatarEditorView: FC<{}> = props =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
setSavedFigures(savedFigures)
|
setSavedFigures(savedFigures)
|
||||||
}, []);
|
}, [ maxWardrobeSlots ]);
|
||||||
|
|
||||||
CreateMessageHook(UserWardrobePageEvent, onUserWardrobePageEvent);
|
CreateMessageHook(UserWardrobePageEvent, onUserWardrobePageEvent);
|
||||||
|
|
||||||
@ -194,6 +195,11 @@ export const AvatarEditorView: FC<{}> = props =>
|
|||||||
setFigureData(figures.get(gender));
|
setFigureData(figures.get(gender));
|
||||||
}, [ figures ]);
|
}, [ figures ]);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
setSavedFigures(new Array(maxWardrobeSlots));
|
||||||
|
}, [ maxWardrobeSlots ]);
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
if(!isWardrobeVisible) return;
|
if(!isWardrobeVisible) return;
|
||||||
|
@ -26,7 +26,7 @@ export const AvatarEditorPaletteSetItem: FC<AvatarEditorPaletteSetItemProps> = p
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LayoutGridItem itemColor={ colorItem.color } itemActive={ colorItem.isSelected } { ...rest }>
|
<LayoutGridItem itemHighlight itemColor={ colorItem.color } itemActive={ colorItem.isSelected } { ...rest }>
|
||||||
{ colorItem.isHC && <CurrencyIcon className="position-absolute end-1 bottom-1" type={ 'hc' } /> }
|
{ colorItem.isHC && <CurrencyIcon className="position-absolute end-1 bottom-1" type={ 'hc' } /> }
|
||||||
{ children }
|
{ children }
|
||||||
</LayoutGridItem>
|
</LayoutGridItem>
|
||||||
|
@ -27,7 +27,7 @@ export const AvatarEditorPaletteSetView: FC<AvatarEditorPaletteSetViewProps> = p
|
|||||||
}, [ model, category, paletteSet, paletteIndex ]);
|
}, [ model, category, paletteSet, paletteIndex ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AutoGrid columnCount={ 4 } columnMinWidth={ 30 }>
|
<AutoGrid gap={ 1 } columnCount={ 5 } columnMinWidth={ 30 }>
|
||||||
{ (paletteSet.length > 0) && paletteSet.map((item, index) =>
|
{ (paletteSet.length > 0) && paletteSet.map((item, index) =>
|
||||||
<AvatarEditorPaletteSetItem key={ index } colorItem={ item } onClick={ event => selectColor(item) } />) }
|
<AvatarEditorPaletteSetItem key={ index } colorItem={ item } onClick={ event => selectColor(item) } />) }
|
||||||
</AutoGrid>
|
</AutoGrid>
|
||||||
|
@ -44,7 +44,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nitro-catalog-navigation-grid-container {
|
.nitro-catalog-navigation-grid-container {
|
||||||
border-radius: 0.25rem;
|
|
||||||
border-color: #B6BEC5 !important;
|
border-color: #B6BEC5 !important;
|
||||||
background-color: #CDD3D9;
|
background-color: #CDD3D9;
|
||||||
border: 2px solid;
|
border: 2px solid;
|
||||||
|
@ -20,7 +20,7 @@ export const CatalogNavigationView: FC<CatalogNavigationViewProps> = props =>
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CatalogSearchView />
|
<CatalogSearchView />
|
||||||
<Column fullHeight className="nitro-catalog-navigation-grid-container p-1" overflow="hidden">
|
<Column fullHeight className="nitro-catalog-navigation-grid-container rounded p-1" overflow="hidden">
|
||||||
<AutoGrid gap={ 1 } columnCount={ 1 }>
|
<AutoGrid gap={ 1 } columnCount={ 1 }>
|
||||||
{ searchResult && (searchResult.filteredNodes.length > 0) && searchResult.filteredNodes.map((n, index) =>
|
{ searchResult && (searchResult.filteredNodes.length > 0) && searchResult.filteredNodes.map((n, index) =>
|
||||||
{
|
{
|
||||||
|
@ -13,9 +13,9 @@ import { CatalogEvent } from '../../../../../events/catalog/CatalogEvent';
|
|||||||
import { useUiEvent } from '../../../../../hooks';
|
import { useUiEvent } from '../../../../../hooks';
|
||||||
import { SendMessageHook } from '../../../../../hooks/messages/message-event';
|
import { SendMessageHook } from '../../../../../hooks/messages/message-event';
|
||||||
import { LoadingSpinnerView } from '../../../../../layout/loading-spinner/LoadingSpinnerView';
|
import { LoadingSpinnerView } from '../../../../../layout/loading-spinner/LoadingSpinnerView';
|
||||||
import { GetCurrencyAmount } from '../../../../../views/purse/common/CurrencyHelper';
|
|
||||||
import { GLOBAL_PURSE } from '../../../../../views/purse/PurseView';
|
|
||||||
import { CurrencyIcon } from '../../../../../views/shared/currency-icon/CurrencyIcon';
|
import { CurrencyIcon } from '../../../../../views/shared/currency-icon/CurrencyIcon';
|
||||||
|
import { GetCurrencyAmount } from '../../../../purse/common/CurrencyHelper';
|
||||||
|
import { GLOBAL_PURSE } from '../../../../purse/PurseView';
|
||||||
import { useCatalogContext } from '../../../CatalogContext';
|
import { useCatalogContext } from '../../../CatalogContext';
|
||||||
import { CatalogPurchaseState } from '../../../common/CatalogPurchaseState';
|
import { CatalogPurchaseState } from '../../../common/CatalogPurchaseState';
|
||||||
import { CatalogLayoutProps } from './CatalogLayout.types';
|
import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||||
|
@ -9,7 +9,7 @@ import { Text } from '../../../../../../common/Text';
|
|||||||
import { BatchUpdates, CreateMessageHook, SendMessageHook } from '../../../../../../hooks';
|
import { BatchUpdates, CreateMessageHook, SendMessageHook } from '../../../../../../hooks';
|
||||||
import { NotificationAlertType } from '../../../../../../views/notification-center/common/NotificationAlertType';
|
import { NotificationAlertType } from '../../../../../../views/notification-center/common/NotificationAlertType';
|
||||||
import { NotificationUtilities } from '../../../../../../views/notification-center/common/NotificationUtilities';
|
import { NotificationUtilities } from '../../../../../../views/notification-center/common/NotificationUtilities';
|
||||||
import { GetCurrencyAmount } from '../../../../../../views/purse/common/CurrencyHelper';
|
import { GetCurrencyAmount } from '../../../../../purse/common/CurrencyHelper';
|
||||||
import { CatalogLayoutProps } from '../CatalogLayout.types';
|
import { CatalogLayoutProps } from '../CatalogLayout.types';
|
||||||
import { CatalogLayoutMarketplaceItemView, PUBLIC_OFFER } from './CatalogLayoutMarketplaceItemView';
|
import { CatalogLayoutMarketplaceItemView, PUBLIC_OFFER } from './CatalogLayoutMarketplaceItemView';
|
||||||
import { SearchFormView } from './CatalogLayoutMarketplaceSearchFormView';
|
import { SearchFormView } from './CatalogLayoutMarketplaceSearchFormView';
|
||||||
|
@ -8,7 +8,7 @@ import { CatalogInitPurchaseEvent } from '../../../../../events/catalog/CatalogI
|
|||||||
import { CatalogWidgetEvent } from '../../../../../events/catalog/CatalogWidgetEvent';
|
import { CatalogWidgetEvent } from '../../../../../events/catalog/CatalogWidgetEvent';
|
||||||
import { dispatchUiEvent, SendMessageHook, useUiEvent } from '../../../../../hooks';
|
import { dispatchUiEvent, SendMessageHook, useUiEvent } from '../../../../../hooks';
|
||||||
import { LoadingSpinnerView } from '../../../../../layout';
|
import { LoadingSpinnerView } from '../../../../../layout';
|
||||||
import { GetCurrencyAmount } from '../../../../../views/purse/common/CurrencyHelper';
|
import { GetCurrencyAmount } from '../../../../purse/common/CurrencyHelper';
|
||||||
import { useCatalogContext } from '../../../CatalogContext';
|
import { useCatalogContext } from '../../../CatalogContext';
|
||||||
import { CatalogPurchaseState } from '../../../common/CatalogPurchaseState';
|
import { CatalogPurchaseState } from '../../../common/CatalogPurchaseState';
|
||||||
import { Offer } from '../../../common/Offer';
|
import { Offer } from '../../../common/Offer';
|
||||||
|
21
src/components/chat-history/ChatHistoryContext.tsx
Normal file
21
src/components/chat-history/ChatHistoryContext.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { createContext, FC, ProviderProps, useContext } from 'react';
|
||||||
|
import { IChatHistoryState } from './common/IChatHistoryState';
|
||||||
|
import { IRoomHistoryState } from './common/IRoomHistoryState';
|
||||||
|
|
||||||
|
export interface IChatHistoryContext
|
||||||
|
{
|
||||||
|
chatHistoryState: IChatHistoryState;
|
||||||
|
roomHistoryState: IRoomHistoryState;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChatHistoryContext = createContext<IChatHistoryContext>({
|
||||||
|
chatHistoryState: null,
|
||||||
|
roomHistoryState: null
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ChatHistoryContextProvider: FC<ProviderProps<IChatHistoryContext>> = props =>
|
||||||
|
{
|
||||||
|
return <ChatHistoryContext.Provider value={ props.value }>{ props.children }</ChatHistoryContext.Provider>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useChatHistoryContext = () => useContext(ChatHistoryContext);
|
@ -2,9 +2,14 @@ import { GetGuestRoomResultEvent, RoomSessionChatEvent, RoomSessionEvent } from
|
|||||||
import { FC, useCallback, useState } from 'react';
|
import { FC, useCallback, useState } from 'react';
|
||||||
import { GetRoomSession } from '../../api';
|
import { GetRoomSession } from '../../api';
|
||||||
import { CreateMessageHook, useRoomSessionManagerEvent } from '../../hooks';
|
import { CreateMessageHook, useRoomSessionManagerEvent } from '../../hooks';
|
||||||
import { useChatHistoryContext } from './context/ChatHistoryContext';
|
import { useChatHistoryContext } from './ChatHistoryContext';
|
||||||
import { ChatEntryType, CHAT_HISTORY_MAX, IChatEntry, IRoomHistoryEntry, ROOM_HISTORY_MAX } from './context/ChatHistoryContext.types';
|
import { ChatEntryType } from './common/ChatEntryType';
|
||||||
import { currentDate } from './utils/Utilities';
|
import { IChatEntry } from './common/IChatEntry';
|
||||||
|
import { IRoomHistoryEntry } from './common/IRoomHistoryEntry';
|
||||||
|
import { currentDate } from './common/Utilities';
|
||||||
|
|
||||||
|
const CHAT_HISTORY_MAX = 1000;
|
||||||
|
const ROOM_HISTORY_MAX = 10;
|
||||||
|
|
||||||
export const ChatHistoryMessageHandler: FC<{}> = props =>
|
export const ChatHistoryMessageHandler: FC<{}> = props =>
|
||||||
{
|
{
|
8
src/components/chat-history/ChatHistoryView.scss
Normal file
8
src/components/chat-history/ChatHistoryView.scss
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.nitro-chat-history {
|
||||||
|
width: $chat-history-width;
|
||||||
|
height: $chat-history-height;
|
||||||
|
|
||||||
|
.content-area {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
}
|
155
src/components/chat-history/ChatHistoryView.tsx
Normal file
155
src/components/chat-history/ChatHistoryView.tsx
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import { ILinkEventTracker } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { AutoSizer, CellMeasurer, CellMeasurerCache, List, ListRowProps, ListRowRenderer, Size } from 'react-virtualized';
|
||||||
|
import { RenderedRows } from 'react-virtualized/dist/es/List';
|
||||||
|
import { AddEventLinkTracker, LocalizeText, RemoveLinkEventTracker } from '../../api';
|
||||||
|
import { Flex, Text } from '../../common';
|
||||||
|
import { BatchUpdates } from '../../hooks';
|
||||||
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout';
|
||||||
|
import { ChatHistoryContextProvider } from './ChatHistoryContext';
|
||||||
|
import { ChatHistoryMessageHandler } from './ChatHistoryMessageHandler';
|
||||||
|
import { ChatEntryType } from './common/ChatEntryType';
|
||||||
|
import { ChatHistoryState } from './common/ChatHistoryState';
|
||||||
|
import { SetChatHistory } from './common/GetChatHistory';
|
||||||
|
import { RoomHistoryState } from './common/RoomHistoryState';
|
||||||
|
|
||||||
|
export const ChatHistoryView: FC<{}> = props =>
|
||||||
|
{
|
||||||
|
const [ isVisible, setIsVisible ] = useState(false);
|
||||||
|
const [ needsScroll, setNeedsScroll ] = useState(false);
|
||||||
|
const [ chatHistoryUpdateId, setChatHistoryUpdateId ] = useState(-1);
|
||||||
|
const [ roomHistoryUpdateId, setRoomHistoryUpdateId ] = useState(-1);
|
||||||
|
const [ chatHistoryState, setChatHistoryState ] = useState(new ChatHistoryState());
|
||||||
|
const [ roomHistoryState, setRoomHistoryState ] = useState(new RoomHistoryState());
|
||||||
|
const elementRef = useRef<List>(null);
|
||||||
|
|
||||||
|
const cache = useMemo(() => new CellMeasurerCache({ defaultHeight: 25, fixedWidth: true }), []);
|
||||||
|
|
||||||
|
const RowRenderer: ListRowRenderer = (props: ListRowProps) =>
|
||||||
|
{
|
||||||
|
const item = chatHistoryState.chats[props.index];
|
||||||
|
|
||||||
|
const isDark = (props.index % 2 === 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CellMeasurer cache={ cache } columnIndex={ 0 } key={ props.key } parent={ props.parent } rowIndex={ props.index }>
|
||||||
|
<Flex key={ props.key } style={ props.style } className="p-1" gap={ 1 }>
|
||||||
|
<Text variant="muted">{ item.timestamp }</Text>
|
||||||
|
{ (item.type === ChatEntryType.TYPE_CHAT) &&
|
||||||
|
<>
|
||||||
|
<Text pointer noWrap dangerouslySetInnerHTML={ { __html: (item.name + ':') }} />
|
||||||
|
<Text textBreak wrap grow>{ item.message }</Text>
|
||||||
|
</> }
|
||||||
|
{ (item.type === ChatEntryType.TYPE_ROOM_INFO) &&
|
||||||
|
<>
|
||||||
|
<i className="icon icon-small-room" />
|
||||||
|
<Text textBreak wrap grow>{ item.name }</Text>
|
||||||
|
</> }
|
||||||
|
</Flex>
|
||||||
|
</CellMeasurer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onResize = (info: Size) => cache.clearAll();
|
||||||
|
|
||||||
|
const onRowsRendered = (info: RenderedRows) =>
|
||||||
|
{
|
||||||
|
if(elementRef && elementRef.current && isVisible && needsScroll)
|
||||||
|
{
|
||||||
|
elementRef.current.scrollToRow(chatHistoryState.chats.length);
|
||||||
|
|
||||||
|
setNeedsScroll(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkReceived = useCallback((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;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
const linkTracker: ILinkEventTracker = {
|
||||||
|
linkReceived,
|
||||||
|
eventUrlPrefix: 'chat-history/'
|
||||||
|
};
|
||||||
|
|
||||||
|
AddEventLinkTracker(linkTracker);
|
||||||
|
|
||||||
|
return () => RemoveLinkEventTracker(linkTracker);
|
||||||
|
}, [ linkReceived ]);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
const chatState = new ChatHistoryState();
|
||||||
|
const roomState = new RoomHistoryState();
|
||||||
|
|
||||||
|
SetChatHistory(chatState);
|
||||||
|
|
||||||
|
chatState.notifier = () => setChatHistoryUpdateId(prevValue => (prevValue + 1));
|
||||||
|
roomState.notifier = () => setRoomHistoryUpdateId(prevValue => (prevValue + 1));
|
||||||
|
|
||||||
|
BatchUpdates(() =>
|
||||||
|
{
|
||||||
|
setChatHistoryState(chatState);
|
||||||
|
setRoomHistoryState(roomState);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () =>
|
||||||
|
{
|
||||||
|
chatState.notifier = null;
|
||||||
|
roomState.notifier = null;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if(elementRef && elementRef.current && isVisible) elementRef.current.scrollToRow(chatHistoryState.chats.length);
|
||||||
|
|
||||||
|
setNeedsScroll(true);
|
||||||
|
}, [ isVisible, chatHistoryState.chats, chatHistoryUpdateId ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChatHistoryContextProvider value={ { chatHistoryState, roomHistoryState } }>
|
||||||
|
<ChatHistoryMessageHandler />
|
||||||
|
{ isVisible &&
|
||||||
|
<NitroCardView uniqueKey="chat-history" className="nitro-chat-history">
|
||||||
|
<NitroCardHeaderView headerText={ LocalizeText('room.chathistory.button.text') } onCloseClick={ event => setIsVisible(false) }/>
|
||||||
|
<NitroCardContentView>
|
||||||
|
<AutoSizer defaultWidth={ 300 } defaultHeight={ 200 } onResize={ onResize }>
|
||||||
|
{ ({ height, width }) =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
ref={ elementRef }
|
||||||
|
width={ width }
|
||||||
|
height={ height }
|
||||||
|
rowCount={ chatHistoryState.chats.length }
|
||||||
|
rowHeight={ cache.rowHeight }
|
||||||
|
className={ 'chat-history-list' }
|
||||||
|
rowRenderer={ RowRenderer }
|
||||||
|
onRowsRendered={ onRowsRendered }
|
||||||
|
deferredMeasurementCache={ cache } />
|
||||||
|
)
|
||||||
|
} }
|
||||||
|
</AutoSizer>
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView> }
|
||||||
|
</ChatHistoryContextProvider>
|
||||||
|
);
|
||||||
|
}
|
5
src/components/chat-history/common/ChatEntryType.ts
Normal file
5
src/components/chat-history/common/ChatEntryType.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export class ChatEntryType
|
||||||
|
{
|
||||||
|
public static TYPE_CHAT = 1;
|
||||||
|
public static TYPE_ROOM_INFO = 2;
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import { IChatEntry, IChatHistoryState } from '../context/ChatHistoryContext.types';
|
import { IChatEntry } from './IChatEntry';
|
||||||
|
import { IChatHistoryState } from './IChatHistoryState';
|
||||||
|
|
||||||
export class ChatHistoryState implements IChatHistoryState
|
export class ChatHistoryState implements IChatHistoryState
|
||||||
{
|
{
|
||||||
@ -10,6 +11,11 @@ export class ChatHistoryState implements IChatHistoryState
|
|||||||
this._chats = [];
|
this._chats = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public notify(): void
|
||||||
|
{
|
||||||
|
if(this._notifier) this._notifier();
|
||||||
|
}
|
||||||
|
|
||||||
public get chats(): IChatEntry[]
|
public get chats(): IChatEntry[]
|
||||||
{
|
{
|
||||||
return this._chats;
|
return this._chats;
|
||||||
@ -24,9 +30,4 @@ export class ChatHistoryState implements IChatHistoryState
|
|||||||
{
|
{
|
||||||
this._notifier = notifier;
|
this._notifier = notifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
notify(): void
|
|
||||||
{
|
|
||||||
if(this._notifier) this._notifier();
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { IChatHistoryState } from '../context/ChatHistoryContext.types';
|
import { IChatHistoryState } from './IChatHistoryState';
|
||||||
|
|
||||||
let GLOBAL_CHATS: IChatHistoryState = null;
|
let GLOBAL_CHATS: IChatHistoryState = null;
|
||||||
|
|
12
src/components/chat-history/common/IChatEntry.ts
Normal file
12
src/components/chat-history/common/IChatEntry.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export interface IChatEntry
|
||||||
|
{
|
||||||
|
id: number;
|
||||||
|
entityId: number;
|
||||||
|
name: string;
|
||||||
|
look?: string;
|
||||||
|
message?: string;
|
||||||
|
entityType?: number;
|
||||||
|
roomId: number;
|
||||||
|
timestamp: string;
|
||||||
|
type: number;
|
||||||
|
}
|
8
src/components/chat-history/common/IChatHistoryState.ts
Normal file
8
src/components/chat-history/common/IChatHistoryState.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { IChatEntry } from './IChatEntry';
|
||||||
|
|
||||||
|
export interface IChatHistoryState
|
||||||
|
{
|
||||||
|
chats: IChatEntry[];
|
||||||
|
notifier: () => void
|
||||||
|
notify(): void;
|
||||||
|
}
|
4
src/components/chat-history/common/IRoomHistoryEntry.ts
Normal file
4
src/components/chat-history/common/IRoomHistoryEntry.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export interface IRoomHistoryEntry {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
8
src/components/chat-history/common/IRoomHistoryState.ts
Normal file
8
src/components/chat-history/common/IRoomHistoryState.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { IRoomHistoryEntry } from './IRoomHistoryEntry';
|
||||||
|
|
||||||
|
export interface IRoomHistoryState
|
||||||
|
{
|
||||||
|
roomHistory: IRoomHistoryEntry[];
|
||||||
|
notifier: () => void;
|
||||||
|
notify: () => void;
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import { IRoomHistoryEntry, IRoomHistoryState } from '../context/ChatHistoryContext.types';
|
import { IRoomHistoryEntry } from './IRoomHistoryEntry';
|
||||||
|
import { IRoomHistoryState } from './IRoomHistoryState';
|
||||||
|
|
||||||
export class RoomHistoryState implements IRoomHistoryState
|
export class RoomHistoryState implements IRoomHistoryState
|
||||||
{
|
{
|
@ -1,6 +1,7 @@
|
|||||||
import { IChatEntry } from '../../../views/chat-history/context/ChatHistoryContext.types';
|
import { IChatEntry } from '../../chat-history/common/IChatEntry';
|
||||||
|
|
||||||
export interface IHelpReportState {
|
export interface IHelpReportState
|
||||||
|
{
|
||||||
reportedUserId: number;
|
reportedUserId: number;
|
||||||
reportedChats: IChatEntry[];
|
reportedChats: IChatEntry[];
|
||||||
cfhCategory: number;
|
cfhCategory: number;
|
||||||
|
@ -2,8 +2,9 @@ import { RoomObjectType } from '@nitrots/nitro-renderer';
|
|||||||
import { FC, useMemo, useState } from 'react';
|
import { FC, useMemo, useState } from 'react';
|
||||||
import { LocalizeText } from '../../../api';
|
import { LocalizeText } from '../../../api';
|
||||||
import { Button, Column, Flex, Grid, LayoutGridItem, Text } from '../../../common';
|
import { Button, Column, Flex, Grid, LayoutGridItem, Text } from '../../../common';
|
||||||
import { GetChatHistory } from '../../../views/chat-history/common/GetChatHistory';
|
import { ChatEntryType } from '../../chat-history/common/ChatEntryType';
|
||||||
import { ChatEntryType, IChatEntry } from '../../../views/chat-history/context/ChatHistoryContext.types';
|
import { GetChatHistory } from '../../chat-history/common/GetChatHistory';
|
||||||
|
import { IChatEntry } from '../../chat-history/common/IChatEntry';
|
||||||
import { useHelpContext } from '../HelpContext';
|
import { useHelpContext } from '../HelpContext';
|
||||||
|
|
||||||
export const SelectReportedChatsView: FC<{}> = props =>
|
export const SelectReportedChatsView: FC<{}> = props =>
|
||||||
|
@ -2,8 +2,8 @@ import { RoomObjectType } from '@nitrots/nitro-renderer';
|
|||||||
import { FC, useMemo, useState } from 'react';
|
import { FC, useMemo, useState } from 'react';
|
||||||
import { GetSessionDataManager, LocalizeText } from '../../../api';
|
import { GetSessionDataManager, LocalizeText } from '../../../api';
|
||||||
import { Button, Column, Flex, Grid, LayoutGridItem, Text } from '../../../common';
|
import { Button, Column, Flex, Grid, LayoutGridItem, Text } from '../../../common';
|
||||||
import { GetChatHistory } from '../../../views/chat-history/common/GetChatHistory';
|
import { ChatEntryType } from '../../chat-history/common/ChatEntryType';
|
||||||
import { ChatEntryType } from '../../../views/chat-history/context/ChatHistoryContext.types';
|
import { GetChatHistory } from '../../chat-history/common/GetChatHistory';
|
||||||
import { IReportedUser } from '../common/IReportedUser';
|
import { IReportedUser } from '../common/IReportedUser';
|
||||||
import { useHelpContext } from '../HelpContext';
|
import { useHelpContext } from '../HelpContext';
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { FC, useMemo, useState } from 'react';
|
import { FC, useMemo, useState } from 'react';
|
||||||
import { LocalizeText } from '../../../api';
|
import { LocalizeText } from '../../../api';
|
||||||
import { Button, Column, Flex, Text } from '../../../common';
|
import { Button, Column, Flex, Text } from '../../../common';
|
||||||
import { GetCfhCategories } from '../../../views/mod-tools/common/GetCFHCategories';
|
import { GetCfhCategories } from '../../mod-tools/common/GetCFHCategories';
|
||||||
import { useHelpContext } from '../HelpContext';
|
import { useHelpContext } from '../HelpContext';
|
||||||
|
|
||||||
export const SelectTopicView: FC<{}> = props =>
|
export const SelectTopicView: FC<{}> = props =>
|
||||||
|
@ -2,10 +2,16 @@
|
|||||||
@import './avatar-editor/AvatarEditorView';
|
@import './avatar-editor/AvatarEditorView';
|
||||||
@import './camera/CameraWidgetView';
|
@import './camera/CameraWidgetView';
|
||||||
@import './catalog/CatalogView';
|
@import './catalog/CatalogView';
|
||||||
|
@import './chat-history/ChatHistoryView';
|
||||||
@import './groups/GroupView';
|
@import './groups/GroupView';
|
||||||
@import './help/HelpView';
|
@import './help/HelpView';
|
||||||
@import './inventory/InventoryView';
|
@import './inventory/InventoryView';
|
||||||
|
@import './loading/LoadingView';
|
||||||
|
@import './mod-tools/ModToolsView';
|
||||||
@import './navigator/NavigatorView';
|
@import './navigator/NavigatorView';
|
||||||
|
@import './purse/PurseView';
|
||||||
|
@import './right-side/RightSideView';
|
||||||
@import './toolbar/ToolbarView';
|
@import './toolbar/ToolbarView';
|
||||||
|
@import './user-profile/UserProfileVew';
|
||||||
@import './user-settings/UserSettingsView';
|
@import './user-settings/UserSettingsView';
|
||||||
@import './wired/WiredView';
|
@import './wired/WiredView';
|
||||||
|
37
src/components/loading/LoadingView.tsx
Normal file
37
src/components/loading/LoadingView.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { FC, useEffect, useState } from 'react';
|
||||||
|
import { Base, Column } from '../../common';
|
||||||
|
import { NotificationUtilities } from '../../views/notification-center/common/NotificationUtilities';
|
||||||
|
|
||||||
|
interface LoadingViewProps
|
||||||
|
{
|
||||||
|
isError: boolean;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoadingView: FC<LoadingViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { isError = false, message = '' } = props;
|
||||||
|
const [ loadingShowing, setLoadingShowing ] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if(!isError) return;
|
||||||
|
|
||||||
|
NotificationUtilities.simpleAlert(message, null, null, null, 'Connection Error');
|
||||||
|
}, [ isError, message ]);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
const timeout = setTimeout(() => setLoadingShowing(true), 500);
|
||||||
|
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column fit center position="relative" className="nitro-loading">
|
||||||
|
<Base className="connecting-duck" />
|
||||||
|
{ isError && (message && message.length) &&
|
||||||
|
<Base className="m-auto bottom-3 fs-4 text-shadow" position="absolute">{ message }</Base> }
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
}
|
@ -1,33 +1,33 @@
|
|||||||
import { HabboWebTools, RoomSessionEvent } from '@nitrots/nitro-renderer';
|
import { HabboWebTools, RoomSessionEvent } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useEffect, useState } from 'react';
|
import { FC, useCallback, useEffect, useState } from 'react';
|
||||||
import { AddEventLinkTracker, GetCommunication, RemoveLinkEventTracker } from '../../api';
|
import { AddEventLinkTracker, GetCommunication, RemoveLinkEventTracker } from '../../api';
|
||||||
import { AchievementsView } from '../../components/achievements/AchievementsView';
|
import { Base } from '../../common';
|
||||||
import { AvatarEditorView } from '../../components/avatar-editor/AvatarEditorView';
|
|
||||||
import { CameraWidgetView } from '../../components/camera/CameraWidgetView';
|
|
||||||
import { CatalogView } from '../../components/catalog/CatalogView';
|
|
||||||
import { GroupsView } from '../../components/groups/GroupsView';
|
|
||||||
import { HelpView } from '../../components/help/HelpView';
|
|
||||||
import { InventoryView } from '../../components/inventory/InventoryView';
|
|
||||||
import { NavigatorView } from '../../components/navigator/NavigatorView';
|
|
||||||
import { ToolbarView } from '../../components/toolbar/ToolbarView';
|
|
||||||
import { UserSettingsView } from '../../components/user-settings/UserSettingsView';
|
|
||||||
import { WiredView } from '../../components/wired/WiredView';
|
|
||||||
import { useRoomSessionManagerEvent } from '../../hooks/events/nitro/session/room-session-manager-event';
|
import { useRoomSessionManagerEvent } from '../../hooks/events/nitro/session/room-session-manager-event';
|
||||||
import { TransitionAnimation, TransitionAnimationTypes } from '../../layout';
|
import { TransitionAnimation, TransitionAnimationTypes } from '../../layout';
|
||||||
import { CampaignView } from '../campaign/CampaignView';
|
import { CampaignView } from '../../views/campaign/CampaignView';
|
||||||
|
import { FloorplanEditorView } from '../../views/floorplan-editor/FloorplanEditorView';
|
||||||
|
import { FriendsView } from '../../views/friends/FriendsView';
|
||||||
|
import { HcCenterView } from '../../views/hc-center/HcCenterView';
|
||||||
|
import { HotelView } from '../../views/hotel-view/HotelView';
|
||||||
|
import { NitropediaView } from '../../views/nitropedia/NitropediaView';
|
||||||
|
import { AchievementsView } from '../achievements/AchievementsView';
|
||||||
|
import { AvatarEditorView } from '../avatar-editor/AvatarEditorView';
|
||||||
|
import { CameraWidgetView } from '../camera/CameraWidgetView';
|
||||||
|
import { CatalogView } from '../catalog/CatalogView';
|
||||||
import { ChatHistoryView } from '../chat-history/ChatHistoryView';
|
import { ChatHistoryView } from '../chat-history/ChatHistoryView';
|
||||||
import { FloorplanEditorView } from '../floorplan-editor/FloorplanEditorView';
|
import { GroupsView } from '../groups/GroupsView';
|
||||||
import { FriendsView } from '../friends/FriendsView';
|
import { HelpView } from '../help/HelpView';
|
||||||
import { HcCenterView } from '../hc-center/HcCenterView';
|
import { InventoryView } from '../inventory/InventoryView';
|
||||||
import { HotelView } from '../hotel-view/HotelView';
|
|
||||||
import { ModToolsView } from '../mod-tools/ModToolsView';
|
import { ModToolsView } from '../mod-tools/ModToolsView';
|
||||||
import { NitropediaView } from '../nitropedia/NitropediaView';
|
import { NavigatorView } from '../navigator/NavigatorView';
|
||||||
import { RightSideView } from '../right-side/RightSideView';
|
import { RightSideView } from '../right-side/RightSideView';
|
||||||
import { RoomHostView } from '../room-host/RoomHostView';
|
import { RoomHostView } from '../room-host/RoomHostView';
|
||||||
|
import { ToolbarView } from '../toolbar/ToolbarView';
|
||||||
import { UserProfileView } from '../user-profile/UserProfileView';
|
import { UserProfileView } from '../user-profile/UserProfileView';
|
||||||
import { MainViewProps } from './MainView.types';
|
import { UserSettingsView } from '../user-settings/UserSettingsView';
|
||||||
|
import { WiredView } from '../wired/WiredView';
|
||||||
|
|
||||||
export const MainView: FC<MainViewProps> = props =>
|
export const MainView: FC<{}> = props =>
|
||||||
{
|
{
|
||||||
const [ isReady, setIsReady ] = useState(false);
|
const [ isReady, setIsReady ] = useState(false);
|
||||||
const [ landingViewVisible, setLandingViewVisible ] = useState(true);
|
const [ landingViewVisible, setLandingViewVisible ] = useState(true);
|
||||||
@ -93,7 +93,7 @@ export const MainView: FC<MainViewProps> = props =>
|
|||||||
}, [onLinkReceived]);
|
}, [onLinkReceived]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="nitro-main">
|
<Base fit>
|
||||||
<TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ landingViewVisible } timeout={ 300 }>
|
<TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ landingViewVisible } timeout={ 300 }>
|
||||||
<HotelView />
|
<HotelView />
|
||||||
</TransitionAnimation>
|
</TransitionAnimation>
|
||||||
@ -118,6 +118,6 @@ export const MainView: FC<MainViewProps> = props =>
|
|||||||
<NitropediaView />
|
<NitropediaView />
|
||||||
<HcCenterView />
|
<HcCenterView />
|
||||||
<CampaignView />
|
<CampaignView />
|
||||||
</div>
|
</Base>
|
||||||
);
|
);
|
||||||
}
|
}
|
20
src/components/mod-tools/ModToolsContext.tsx
Normal file
20
src/components/mod-tools/ModToolsContext.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { createContext, Dispatch, FC, ProviderProps, useContext } from 'react';
|
||||||
|
import { IModToolsAction, IModToolsState } from './reducers/ModToolsReducer';
|
||||||
|
|
||||||
|
export interface IModToolsContext
|
||||||
|
{
|
||||||
|
modToolsState: IModToolsState;
|
||||||
|
dispatchModToolsState: Dispatch<IModToolsAction>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModToolsContext = createContext<IModToolsContext>({
|
||||||
|
modToolsState: null,
|
||||||
|
dispatchModToolsState: null
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ModToolsContextProvider: FC<ProviderProps<IModToolsContext>> = props =>
|
||||||
|
{
|
||||||
|
return <ModToolsContext.Provider value={ props.value }>{ props.children }</ModToolsContext.Provider>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useModToolsContext = () => useContext(ModToolsContext);
|
@ -7,10 +7,10 @@ import { ModToolsOpenRoomInfoEvent } from '../../events/mod-tools/ModToolsOpenRo
|
|||||||
import { ModToolsOpenUserChatlogEvent } from '../../events/mod-tools/ModToolsOpenUserChatlogEvent';
|
import { ModToolsOpenUserChatlogEvent } from '../../events/mod-tools/ModToolsOpenUserChatlogEvent';
|
||||||
import { ModToolsOpenUserInfoEvent } from '../../events/mod-tools/ModToolsOpenUserInfoEvent';
|
import { ModToolsOpenUserInfoEvent } from '../../events/mod-tools/ModToolsOpenUserInfoEvent';
|
||||||
import { CreateMessageHook, useRoomEngineEvent, useUiEvent } from '../../hooks';
|
import { CreateMessageHook, useRoomEngineEvent, useUiEvent } from '../../hooks';
|
||||||
import { NotificationAlertType } from '../notification-center/common/NotificationAlertType';
|
import { NotificationAlertType } from '../../views/notification-center/common/NotificationAlertType';
|
||||||
import { NotificationUtilities } from '../notification-center/common/NotificationUtilities';
|
import { NotificationUtilities } from '../../views/notification-center/common/NotificationUtilities';
|
||||||
import { SetCfhCategories } from './common/GetCFHCategories';
|
import { SetCfhCategories } from './common/GetCFHCategories';
|
||||||
import { useModToolsContext } from './context/ModToolsContext';
|
import { useModToolsContext } from './ModToolsContext';
|
||||||
import { ModToolsActions } from './reducers/ModToolsReducer';
|
import { ModToolsActions } from './reducers/ModToolsReducer';
|
||||||
|
|
||||||
export const ModToolsMessageHandler: FC<{}> = props =>
|
export const ModToolsMessageHandler: FC<{}> = props =>
|
93
src/components/mod-tools/ModToolsView.scss
Normal file
93
src/components/mod-tools/ModToolsView.scss
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
.nitro-mod-tools {
|
||||||
|
width: $nitro-mod-tools-width;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitro-mod-tools-room {
|
||||||
|
width: 240px;
|
||||||
|
|
||||||
|
.username {
|
||||||
|
color: #1E7295;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitro-mod-tools-user {
|
||||||
|
width: 350px;
|
||||||
|
height: 370px;
|
||||||
|
|
||||||
|
.username {
|
||||||
|
color: #1E7295;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
color: $black;
|
||||||
|
|
||||||
|
> :not(caption) > * > * {
|
||||||
|
box-shadow: none;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, .2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.table-striped > tbody > tr:nth-of-type(odd) {
|
||||||
|
color: $black;
|
||||||
|
background: rgba(0, 0, 0, .05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitro-mod-tools-user-visits {
|
||||||
|
min-width: 300px;
|
||||||
|
|
||||||
|
.user-visits {
|
||||||
|
min-height: 200px;
|
||||||
|
|
||||||
|
.roomvisits-container {
|
||||||
|
div.room-visit {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitro-mod-tools-chatlog {
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitro-mod-tools-user-visits {
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitro-mod-tools-tickets {
|
||||||
|
width: 400px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitro-mod-tools-handle-issue {
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitro-mod-tools-chatlog,
|
||||||
|
.nitro-mod-tools-user-visits {
|
||||||
|
|
||||||
|
.log-container {
|
||||||
|
min-height: 200px;
|
||||||
|
|
||||||
|
.log-entry-container {
|
||||||
|
|
||||||
|
.log-entry {
|
||||||
|
|
||||||
|
&.highlighted {
|
||||||
|
border: 1px solid $red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.highlighted {
|
||||||
|
border: 1px solid $red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { RoomEngineObjectEvent, RoomObjectCategory } from '@nitrots/nitro-renderer';
|
import { RoomEngineObjectEvent, RoomObjectCategory } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useReducer, useState } from 'react';
|
import { FC, useCallback, useReducer, useState } from 'react';
|
||||||
import { GetRoomSession } from '../../api';
|
import { GetRoomSession } from '../../api';
|
||||||
|
import { Button } from '../../common';
|
||||||
import { ModToolsEvent } from '../../events/mod-tools/ModToolsEvent';
|
import { ModToolsEvent } from '../../events/mod-tools/ModToolsEvent';
|
||||||
import { ModToolsOpenRoomChatlogEvent } from '../../events/mod-tools/ModToolsOpenRoomChatlogEvent';
|
import { ModToolsOpenRoomChatlogEvent } from '../../events/mod-tools/ModToolsOpenRoomChatlogEvent';
|
||||||
import { ModToolsOpenRoomInfoEvent } from '../../events/mod-tools/ModToolsOpenRoomInfoEvent';
|
import { ModToolsOpenRoomInfoEvent } from '../../events/mod-tools/ModToolsOpenRoomInfoEvent';
|
||||||
@ -8,24 +10,23 @@ import { ModToolsOpenUserInfoEvent } from '../../events/mod-tools/ModToolsOpenUs
|
|||||||
import { useRoomEngineEvent } from '../../hooks/events';
|
import { useRoomEngineEvent } from '../../hooks/events';
|
||||||
import { dispatchUiEvent, useUiEvent } from '../../hooks/events/ui/ui-event';
|
import { dispatchUiEvent, useUiEvent } from '../../hooks/events/ui/ui-event';
|
||||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout';
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout';
|
||||||
import { ModToolsContextProvider } from './context/ModToolsContext';
|
import { ModToolsContextProvider } from './ModToolsContext';
|
||||||
import { ModToolsMessageHandler } from './ModToolsMessageHandler';
|
import { ModToolsMessageHandler } from './ModToolsMessageHandler';
|
||||||
import { ModToolsViewProps } from './ModToolsView.types';
|
|
||||||
import { initialModTools, ModToolsActions, ModToolsReducer } from './reducers/ModToolsReducer';
|
import { initialModTools, ModToolsActions, ModToolsReducer } from './reducers/ModToolsReducer';
|
||||||
import { ISelectedUser } from './utils/ISelectedUser';
|
import { ISelectedUser } from './utils/ISelectedUser';
|
||||||
import { ModToolsChatlogView } from './views/room/room-chatlog/ModToolsChatlogView';
|
import { ModToolsChatlogView } from './views/room/ModToolsChatlogView';
|
||||||
import { ModToolsRoomView } from './views/room/room-tools/ModToolsRoomView';
|
import { ModToolsRoomView } from './views/room/ModToolsRoomView';
|
||||||
import { ModToolsTicketsView } from './views/tickets/ModToolsTicketsView';
|
import { ModToolsTicketsView } from './views/tickets/ModToolsTicketsView';
|
||||||
import { ModToolsUserChatlogView } from './views/user/user-chatlog/ModToolsUserChatlogView';
|
import { ModToolsUserChatlogView } from './views/user/ModToolsUserChatlogView';
|
||||||
import { ModToolsUserView } from './views/user/user-info/ModToolsUserView';
|
import { ModToolsUserView } from './views/user/ModToolsUserView';
|
||||||
|
|
||||||
export const ModToolsView: FC<ModToolsViewProps> = props =>
|
export const ModToolsView: FC<{}> = props =>
|
||||||
{
|
{
|
||||||
const [ isVisible, setIsVisible ] = useState(false);
|
const [ isVisible, setIsVisible ] = useState(false);
|
||||||
const [ modToolsState, dispatchModToolsState ] = useReducer(ModToolsReducer, initialModTools);
|
|
||||||
const { currentRoomId = null, openRooms = null, openRoomChatlogs = null, openUserChatlogs = null, openUserInfo = null } = modToolsState;
|
|
||||||
const [ selectedUser, setSelectedUser] = useState<ISelectedUser>(null);
|
const [ selectedUser, setSelectedUser] = useState<ISelectedUser>(null);
|
||||||
const [ isTicketsVisible, setIsTicketsVisible ] = useState(false);
|
const [ isTicketsVisible, setIsTicketsVisible ] = useState(false);
|
||||||
|
const [ modToolsState, dispatchModToolsState ] = useReducer(ModToolsReducer, initialModTools);
|
||||||
|
const { currentRoomId = null, openRooms = null, openRoomChatlogs = null, openUserChatlogs = null, openUserInfo = null } = modToolsState;
|
||||||
|
|
||||||
const onModToolsEvent = useCallback((event: ModToolsEvent) =>
|
const onModToolsEvent = useCallback((event: ModToolsEvent) =>
|
||||||
{
|
{
|
||||||
@ -191,11 +192,19 @@ export const ModToolsView: FC<ModToolsViewProps> = props =>
|
|||||||
{ isVisible &&
|
{ isVisible &&
|
||||||
<NitroCardView uniqueKey="mod-tools" className="nitro-mod-tools" simple={ false }>
|
<NitroCardView uniqueKey="mod-tools" className="nitro-mod-tools" simple={ false }>
|
||||||
<NitroCardHeaderView headerText={ 'Mod Tools' } onCloseClick={ event => setIsVisible(false) } />
|
<NitroCardHeaderView headerText={ 'Mod Tools' } onCloseClick={ event => setIsVisible(false) } />
|
||||||
<NitroCardContentView className="text-black">
|
<NitroCardContentView className="text-black" gap={ 1 }>
|
||||||
<button className="btn btn-primary btn-sm w-100 mb-2" onClick={ () => handleClick('toggle_room') } disabled={ !currentRoomId }><i className="fas fa-home"></i> Room Tool</button>
|
<Button gap={ 1 } onClick={ event => handleClick('toggle_room') } disabled={ !currentRoomId }>
|
||||||
<button className="btn btn-primary btn-sm w-100 mb-2" onClick={ () => handleClick('toggle_room_chatlog') } disabled={ !currentRoomId }><i className="fas fa-comments"></i> Chatlog Tool</button>
|
<FontAwesomeIcon icon="home" /> Room Tool
|
||||||
<button className="btn btn-primary btn-sm w-100 mb-2" onClick={ () => handleClick('toggle_user_info') } disabled={ !selectedUser }><i className="fas fa-user"></i> User: { selectedUser ? selectedUser.username : '' }</button>
|
</Button>
|
||||||
<button className="btn btn-primary btn-sm w-100" onClick={ () => setIsTicketsVisible(value => !value) }><i className="fas fa-exclamation-circle"></i> Report Tool</button>
|
<Button gap={ 1 } onClick={ event => handleClick('toggle_room_chatlog') } disabled={ !currentRoomId }>
|
||||||
|
<FontAwesomeIcon icon="comments" /> Chatlog Tool
|
||||||
|
</Button>
|
||||||
|
<Button gap={ 1 } onClick={ () => handleClick('toggle_user_info') } disabled={ !selectedUser }>
|
||||||
|
<FontAwesomeIcon icon="user" /> User: { selectedUser ? selectedUser.username : '' }
|
||||||
|
</Button>
|
||||||
|
<Button gap={ 1 } onClick={ () => setIsTicketsVisible(value => !value) }>
|
||||||
|
<FontAwesomeIcon icon="exclamation-circle" /> Report Tool
|
||||||
|
</Button>
|
||||||
</NitroCardContentView>
|
</NitroCardContentView>
|
||||||
</NitroCardView> }
|
</NitroCardView> }
|
||||||
{ openRooms && openRooms.map(roomId =>
|
{ openRooms && openRooms.map(roomId =>
|
158
src/components/mod-tools/views/chatlog/ChatlogView.tsx
Normal file
158
src/components/mod-tools/views/chatlog/ChatlogView.tsx
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
import { ChatRecordData, UserProfileComposer } from '@nitrots/nitro-renderer';
|
||||||
|
import { CSSProperties, FC, Key, useCallback } from 'react';
|
||||||
|
import { AutoSizer, CellMeasurer, CellMeasurerCache, List, ListRowProps } from 'react-virtualized';
|
||||||
|
import { TryVisitRoom } from '../../../../api';
|
||||||
|
import { Base, Button, Column, Flex, Grid, Text } from '../../../../common';
|
||||||
|
import { ModToolsOpenRoomInfoEvent } from '../../../../events/mod-tools/ModToolsOpenRoomInfoEvent';
|
||||||
|
import { dispatchUiEvent, SendMessageHook } from '../../../../hooks';
|
||||||
|
|
||||||
|
interface ChatlogViewProps
|
||||||
|
{
|
||||||
|
records: ChatRecordData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChatlogView: FC<ChatlogViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { records = null } = props;
|
||||||
|
|
||||||
|
const rowRenderer = (props: ListRowProps) =>
|
||||||
|
{
|
||||||
|
let chatlogEntry = records[0].chatlog[props.index];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CellMeasurer
|
||||||
|
cache={ cache }
|
||||||
|
columnIndex={ 0 }
|
||||||
|
key={ props.key }
|
||||||
|
parent={ props.parent }
|
||||||
|
rowIndex={ props.index }
|
||||||
|
>
|
||||||
|
<Grid key={ props.key } fullHeight={ false } style={ props.style } gap={ 1 } alignItems="center" className="log-entry py-1 border-bottom">
|
||||||
|
<Text className="g-col-2">{ chatlogEntry.timestamp }</Text>
|
||||||
|
<Text className="g-col-3" bold underline pointer onClick={ event => SendMessageHook(new UserProfileComposer(chatlogEntry.userId)) }>{ chatlogEntry.userName }</Text>
|
||||||
|
<Text textBreak wrap className="g-col-7">{ chatlogEntry.message }</Text>
|
||||||
|
</Grid>
|
||||||
|
</CellMeasurer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const advancedRowRenderer = (props: ListRowProps) =>
|
||||||
|
{
|
||||||
|
let chatlogEntry = null;
|
||||||
|
let currentRecord: ChatRecordData = null;
|
||||||
|
let isRoomInfo = false;
|
||||||
|
let totalIndex = 0;
|
||||||
|
|
||||||
|
for(let i = 0; i < records.length; i++)
|
||||||
|
{
|
||||||
|
currentRecord = records[i];
|
||||||
|
|
||||||
|
totalIndex++; // row for room info
|
||||||
|
totalIndex = (totalIndex + currentRecord.chatlog.length);
|
||||||
|
|
||||||
|
if(props.index > (totalIndex - 1)) continue;
|
||||||
|
|
||||||
|
if((props.index + 1) === (totalIndex - currentRecord.chatlog.length))
|
||||||
|
{
|
||||||
|
isRoomInfo = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = (props.index - (totalIndex - currentRecord.chatlog.length));
|
||||||
|
|
||||||
|
chatlogEntry = currentRecord.chatlog[index];
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CellMeasurer
|
||||||
|
cache={ cache }
|
||||||
|
columnIndex={ 0 }
|
||||||
|
key={ props.key }
|
||||||
|
parent={ props.parent }
|
||||||
|
rowIndex={ props.index }
|
||||||
|
>
|
||||||
|
{ (isRoomInfo && currentRecord) &&
|
||||||
|
<RoomInfo roomId={ currentRecord.roomId } roomName={ currentRecord.roomName } uniqueKey={ props.key } style={ props.style } /> }
|
||||||
|
{ !isRoomInfo &&
|
||||||
|
<Grid key={ props.key } style={ props.style } gap={ 1 } alignItems="center" className="log-entry py-1 border-bottom">
|
||||||
|
<Text className="g-col-2">{ chatlogEntry.timestamp }</Text>
|
||||||
|
<Text className="g-col-3" bold underline pointer onClick={ event => SendMessageHook(new UserProfileComposer(chatlogEntry.userId)) }>{ chatlogEntry.userName }</Text>
|
||||||
|
<Text textBreak wrap className="g-col-7">{ chatlogEntry.message }</Text>
|
||||||
|
</Grid> }
|
||||||
|
</CellMeasurer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNumRowsForAdvanced = useCallback(() =>
|
||||||
|
{
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
for(let i = 0; i < records.length; i++)
|
||||||
|
{
|
||||||
|
count++; // add room info row
|
||||||
|
count = count + records[i].chatlog.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}, [records]);
|
||||||
|
|
||||||
|
const RoomInfo = (props: { roomId: number, roomName: string, uniqueKey: Key, style: CSSProperties }) =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<Flex key={ props.uniqueKey } gap={ 2 } alignItems="center" justifyContent="between" className="room-info bg-muted rounded p-1" style={ props.style }>
|
||||||
|
<Flex gap={ 1 }>
|
||||||
|
<Text bold>Room name:</Text>
|
||||||
|
<Text>{ props.roomName }</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex gap={ 1 }>
|
||||||
|
<Button onClick={ event => TryVisitRoom(props.roomId) }>Visit Room</Button>
|
||||||
|
<Button onClick={ event => dispatchUiEvent(new ModToolsOpenRoomInfoEvent(props.roomId)) }>Room Tools</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cache = new CellMeasurerCache({
|
||||||
|
defaultHeight: 25,
|
||||||
|
fixedWidth: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{ (records && (records.length === 1)) &&
|
||||||
|
<RoomInfo roomId={records[0].roomId} roomName={records[0].roomName} uniqueKey={ null } style={ {} } /> }
|
||||||
|
<Column fit gap={ 0 } overflow="hidden">
|
||||||
|
<Column gap={ 2 }>
|
||||||
|
<Grid gap={ 1 } className="text-black fw-bold border-bottom pb-1">
|
||||||
|
<Base className="g-col-2">Time</Base>
|
||||||
|
<Base className="g-col-3">User</Base>
|
||||||
|
<Base className="g-col-7">Message</Base>
|
||||||
|
</Grid>
|
||||||
|
</Column>
|
||||||
|
{ (records && (records.length > 0)) &&
|
||||||
|
<Column className="log-container striped-children" overflow="auto" gap={ 0 }>
|
||||||
|
<AutoSizer defaultWidth={ 400 } defaultHeight={ 200 }>
|
||||||
|
{ ({ height, width }) =>
|
||||||
|
{
|
||||||
|
cache.clearAll();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
width={ width }
|
||||||
|
height={ height }
|
||||||
|
rowCount={ (records.length > 1) ? getNumRowsForAdvanced() : records[0].chatlog.length }
|
||||||
|
rowHeight={ cache.rowHeight }
|
||||||
|
className={ 'log-entry-container' }
|
||||||
|
rowRenderer={ (records.length > 1) ? advancedRowRenderer : rowRenderer }
|
||||||
|
deferredMeasurementCache={ cache } />
|
||||||
|
);
|
||||||
|
} }
|
||||||
|
</AutoSizer>
|
||||||
|
</Column> }
|
||||||
|
</Column>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -1,20 +1,19 @@
|
|||||||
import { ChatRecordData, GetRoomChatlogMessageComposer, RoomChatlogEvent } from '@nitrots/nitro-renderer';
|
import { ChatRecordData, GetRoomChatlogMessageComposer, RoomChatlogEvent } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useEffect, useState } from 'react';
|
import { FC, useCallback, useEffect, useState } from 'react';
|
||||||
import { CreateMessageHook, SendMessageHook } from '../../../../../hooks/messages';
|
import { CreateMessageHook, SendMessageHook } from '../../../../hooks/messages';
|
||||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout';
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
|
||||||
import { ChatlogView } from '../../chatlog/ChatlogView';
|
import { ChatlogView } from '../chatlog/ChatlogView';
|
||||||
import { ModToolsChatlogViewProps } from './ModToolsChatlogView.types';
|
|
||||||
|
interface ModToolsChatlogViewProps
|
||||||
|
{
|
||||||
|
roomId: number;
|
||||||
|
onCloseClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
export const ModToolsChatlogView: FC<ModToolsChatlogViewProps> = props =>
|
export const ModToolsChatlogView: FC<ModToolsChatlogViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { roomId = null, onCloseClick = null } = props;
|
const { roomId = null, onCloseClick = null } = props;
|
||||||
|
const [ roomChatlog, setRoomChatlog ] = useState<ChatRecordData>(null);
|
||||||
const [roomChatlog, setRoomChatlog] = useState<ChatRecordData>(null);
|
|
||||||
|
|
||||||
useEffect(() =>
|
|
||||||
{
|
|
||||||
SendMessageHook(new GetRoomChatlogMessageComposer(roomId));
|
|
||||||
}, [roomId]);
|
|
||||||
|
|
||||||
const onModtoolRoomChatlogEvent = useCallback((event: RoomChatlogEvent) =>
|
const onModtoolRoomChatlogEvent = useCallback((event: RoomChatlogEvent) =>
|
||||||
{
|
{
|
||||||
@ -23,17 +22,23 @@ export const ModToolsChatlogView: FC<ModToolsChatlogViewProps> = props =>
|
|||||||
if(!parser || parser.data.roomId !== roomId) return;
|
if(!parser || parser.data.roomId !== roomId) return;
|
||||||
|
|
||||||
setRoomChatlog(parser.data);
|
setRoomChatlog(parser.data);
|
||||||
}, [roomId, setRoomChatlog]);
|
}, [ roomId ]);
|
||||||
|
|
||||||
CreateMessageHook(RoomChatlogEvent, onModtoolRoomChatlogEvent);
|
CreateMessageHook(RoomChatlogEvent, onModtoolRoomChatlogEvent);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new GetRoomChatlogMessageComposer(roomId));
|
||||||
|
}, [ roomId ]);
|
||||||
|
|
||||||
|
if(!roomChatlog) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NitroCardView className="nitro-mod-tools-room-chatlog" simple={true}>
|
<NitroCardView className="nitro-mod-tools-chatlog" simple={true}>
|
||||||
<NitroCardHeaderView headerText={'Room Chatlog' + (roomChatlog ? ': ' + roomChatlog.roomName : '')} onCloseClick={() => onCloseClick()} />
|
<NitroCardHeaderView headerText={ `Room Chatlog ${ roomChatlog.roomName }` } onCloseClick={ onCloseClick } />
|
||||||
<NitroCardContentView className="text-black h-100">
|
<NitroCardContentView className="text-black h-100">
|
||||||
{roomChatlog &&
|
{ roomChatlog &&
|
||||||
<ChatlogView records={[roomChatlog]} />
|
<ChatlogView records={ [ roomChatlog ] } /> }
|
||||||
}
|
|
||||||
</NitroCardContentView>
|
</NitroCardContentView>
|
||||||
</NitroCardView>
|
</NitroCardView>
|
||||||
);
|
);
|
128
src/components/mod-tools/views/room/ModToolsRoomView.tsx
Normal file
128
src/components/mod-tools/views/room/ModToolsRoomView.tsx
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import { GetModeratorRoomInfoMessageComposer, ModerateRoomMessageComposer, ModeratorActionMessageComposer, ModeratorRoomInfoEvent } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useEffect, useState } from 'react';
|
||||||
|
import { TryVisitRoom } from '../../../../api';
|
||||||
|
import { Button, Column, Flex, Text } from '../../../../common';
|
||||||
|
import { ModToolsOpenRoomChatlogEvent } from '../../../../events/mod-tools/ModToolsOpenRoomChatlogEvent';
|
||||||
|
import { BatchUpdates, dispatchUiEvent } from '../../../../hooks';
|
||||||
|
import { CreateMessageHook, SendMessageHook } from '../../../../hooks/messages';
|
||||||
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
|
||||||
|
|
||||||
|
interface ModToolsRoomViewProps
|
||||||
|
{
|
||||||
|
roomId: number;
|
||||||
|
onCloseClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ModToolsRoomView: FC<ModToolsRoomViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { roomId = null, onCloseClick = null } = props;
|
||||||
|
|
||||||
|
const [ infoRequested, setInfoRequested ] = useState(false);
|
||||||
|
const [ loadedRoomId, setLoadedRoomId ] = useState(null);
|
||||||
|
|
||||||
|
const [ name, setName ] = useState(null);
|
||||||
|
const [ ownerId, setOwnerId ] = useState(null);
|
||||||
|
const [ ownerName, setOwnerName ] = useState(null);
|
||||||
|
const [ ownerInRoom, setOwnerInRoom ] = useState(false);
|
||||||
|
const [ usersInRoom, setUsersInRoom ] = useState(0);
|
||||||
|
|
||||||
|
//form data
|
||||||
|
const [ kickUsers, setKickUsers ] = useState(false);
|
||||||
|
const [ lockRoom, setLockRoom ] = useState(false);
|
||||||
|
const [ changeRoomName, setChangeRoomName ] = useState(false);
|
||||||
|
const [ message, setMessage ] = useState('');
|
||||||
|
|
||||||
|
const onModtoolRoomInfoEvent = useCallback((event: ModeratorRoomInfoEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!parser || parser.data.flatId !== roomId) return;
|
||||||
|
|
||||||
|
BatchUpdates(() =>
|
||||||
|
{
|
||||||
|
setLoadedRoomId(parser.data.flatId);
|
||||||
|
setName(parser.data.room.name);
|
||||||
|
setOwnerId(parser.data.ownerId);
|
||||||
|
setOwnerName(parser.data.ownerName);
|
||||||
|
setOwnerInRoom(parser.data.ownerInRoom);
|
||||||
|
setUsersInRoom(parser.data.userCount);
|
||||||
|
});
|
||||||
|
}, [ roomId ]);
|
||||||
|
|
||||||
|
CreateMessageHook(ModeratorRoomInfoEvent, onModtoolRoomInfoEvent);
|
||||||
|
|
||||||
|
const handleClick = useCallback((action: string, value?: string) =>
|
||||||
|
{
|
||||||
|
if(!action) return;
|
||||||
|
|
||||||
|
switch(action)
|
||||||
|
{
|
||||||
|
case 'alert_only':
|
||||||
|
if(message.trim().length === 0) return;
|
||||||
|
|
||||||
|
SendMessageHook(new ModeratorActionMessageComposer(ModeratorActionMessageComposer.ACTION_ALERT, message, ''));
|
||||||
|
return;
|
||||||
|
case 'send_message':
|
||||||
|
if(message.trim().length === 0) return;
|
||||||
|
|
||||||
|
SendMessageHook(new ModeratorActionMessageComposer(ModeratorActionMessageComposer.ACTION_MESSAGE, message, ''));
|
||||||
|
SendMessageHook(new ModerateRoomMessageComposer(roomId, lockRoom ? 1 : 0, changeRoomName ? 1 : 0, kickUsers ? 1 : 0))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, [ changeRoomName, kickUsers, lockRoom, message, roomId ]);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if(infoRequested) return;
|
||||||
|
|
||||||
|
SendMessageHook(new GetModeratorRoomInfoMessageComposer(roomId));
|
||||||
|
setInfoRequested(true);
|
||||||
|
}, [ roomId, infoRequested, setInfoRequested ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NitroCardView className="nitro-mod-tools-room" simple>
|
||||||
|
<NitroCardHeaderView headerText={ 'Room Info' + (name ? ': ' + name : '') } onCloseClick={ event => onCloseClick() } />
|
||||||
|
<NitroCardContentView className="text-black">
|
||||||
|
<Flex gap={ 2 }>
|
||||||
|
<Column justifyContent="center" grow gap={ 1 }>
|
||||||
|
<Flex alignItems="center" gap={ 2 }>
|
||||||
|
<Text bold align="end" className="col-7">Room Owner:</Text>
|
||||||
|
<Text underline pointer truncate>{ ownerName }</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex alignItems="center" gap={ 2 }>
|
||||||
|
<Text bold align="end" className="col-7">Users in room:</Text>
|
||||||
|
<Text>{ usersInRoom }</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex alignItems="center" gap={ 2 }>
|
||||||
|
<Text bold align="end" className="col-7">Owner in room:</Text>
|
||||||
|
<Text>{ ownerInRoom ? 'Yes' : 'No' }</Text>
|
||||||
|
</Flex>
|
||||||
|
</Column>
|
||||||
|
<Column gap={ 1 }>
|
||||||
|
<Button onClick={ event => TryVisitRoom(roomId) }>Visit Room</Button>
|
||||||
|
<Button onClick={ event => dispatchUiEvent(new ModToolsOpenRoomChatlogEvent(roomId)) }>Chatlog</Button>
|
||||||
|
</Column>
|
||||||
|
</Flex>
|
||||||
|
<Column className="bg-muted rounded p-2" gap={ 1 }>
|
||||||
|
<Flex alignItems="center" gap={ 1 }>
|
||||||
|
<input className="form-check-input" type="checkbox" checked={ kickUsers } onChange={ event => setKickUsers(event.target.checked) } />
|
||||||
|
<Text small>Kick everyone out</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex alignItems="center" gap={ 1 }>
|
||||||
|
<input className="form-check-input" type="checkbox" checked={ lockRoom } onChange={ event => setLockRoom(event.target.checked) } />
|
||||||
|
<Text small>Enable the doorbell</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex alignItems="center" gap={ 1 }>
|
||||||
|
<input className="form-check-input" type="checkbox" checked={ changeRoomName } onChange={ event => setChangeRoomName(event.target.checked) }/>
|
||||||
|
<Text small>Change room name</Text>
|
||||||
|
</Flex>
|
||||||
|
</Column>
|
||||||
|
<textarea className="form-control" placeholder="Type a mandatory message to the users in this text box..." value={ message } onChange={ event => setMessage(event.target.value) }></textarea>
|
||||||
|
<Flex justifyContent="between">
|
||||||
|
<Button variant="danger" onClick={ event => handleClick('send_message') }>Send Caution</Button>
|
||||||
|
<Button onClick={ event => handleClick('alert_only') }>Send Alert only</Button>
|
||||||
|
</Flex>
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView>
|
||||||
|
);
|
||||||
|
}
|
@ -1,9 +1,14 @@
|
|||||||
import { CfhChatlogData, CfhChatlogEvent, GetCfhChatlogMessageComposer } from '@nitrots/nitro-renderer';
|
import { CfhChatlogData, CfhChatlogEvent, GetCfhChatlogMessageComposer } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useEffect, useState } from 'react';
|
import { FC, useCallback, useEffect, useState } from 'react';
|
||||||
import { CreateMessageHook, SendMessageHook } from '../../../../../hooks';
|
import { CreateMessageHook, SendMessageHook } from '../../../../hooks';
|
||||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout';
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
|
||||||
import { ChatlogView } from '../../chatlog/ChatlogView';
|
import { ChatlogView } from '../chatlog/ChatlogView';
|
||||||
import { CfhChatlogViewProps } from './CfhChatlogView.types';
|
|
||||||
|
interface CfhChatlogViewProps
|
||||||
|
{
|
||||||
|
issueId: number;
|
||||||
|
onCloseClick(): void;
|
||||||
|
}
|
||||||
|
|
||||||
export const CfhChatlogView: FC<CfhChatlogViewProps> = props =>
|
export const CfhChatlogView: FC<CfhChatlogViewProps> = props =>
|
||||||
{
|
{
|
@ -0,0 +1,99 @@
|
|||||||
|
import { CloseIssuesMessageComposer, ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useMemo, useState } from 'react';
|
||||||
|
import { LocalizeText } from '../../../../api';
|
||||||
|
import { Button, Column, Grid, Text } from '../../../../common';
|
||||||
|
import { ModToolsOpenUserInfoEvent } from '../../../../events/mod-tools/ModToolsOpenUserInfoEvent';
|
||||||
|
import { dispatchUiEvent, SendMessageHook } from '../../../../hooks';
|
||||||
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
|
||||||
|
import { getSourceName } from '../../common/IssueCategoryNames';
|
||||||
|
import { useModToolsContext } from '../../ModToolsContext';
|
||||||
|
import { CfhChatlogView } from './CfhChatlogView';
|
||||||
|
|
||||||
|
interface IssueInfoViewProps
|
||||||
|
{
|
||||||
|
issueId: number;
|
||||||
|
onIssueInfoClosed(issueId: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ModToolsIssueInfoView: FC<IssueInfoViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { issueId = null, onIssueInfoClosed = null } = props;
|
||||||
|
const { modToolsState = null } = useModToolsContext();
|
||||||
|
const { tickets = null } = modToolsState;
|
||||||
|
const [ cfhChatlogOpen, setcfhChatlogOpen ] = useState(false);
|
||||||
|
|
||||||
|
const ticket = useMemo(() =>
|
||||||
|
{
|
||||||
|
if(!tickets || !tickets.length) return null;
|
||||||
|
|
||||||
|
return tickets.find(issue => issue.issueId === issueId);
|
||||||
|
}, [ issueId, tickets ]);
|
||||||
|
|
||||||
|
const releaseIssue = (issueId: number) =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new ReleaseIssuesMessageComposer([ issueId ]));
|
||||||
|
|
||||||
|
onIssueInfoClosed(issueId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeIssue = (resolutionType: number) =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new CloseIssuesMessageComposer([ issueId ], resolutionType));
|
||||||
|
|
||||||
|
onIssueInfoClosed(issueId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const openUserInfo = (userId: number) => dispatchUiEvent(new ModToolsOpenUserInfoEvent(userId));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NitroCardView className="nitro-mod-tools-handle-issue" simple>
|
||||||
|
<NitroCardHeaderView headerText={'Resolving issue ' + issueId} onCloseClick={() => onIssueInfoClosed(issueId)} />
|
||||||
|
<NitroCardContentView className="text-black">
|
||||||
|
<Text fontSize={ 4 }>Issue Information</Text>
|
||||||
|
<Grid>
|
||||||
|
<Column size={ 8 }>
|
||||||
|
<table className="table table-striped table-sm table-text-small text-black m-0">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Source</th>
|
||||||
|
<td>{ getSourceName(ticket.categoryId) }</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Category</th>
|
||||||
|
<td>{ LocalizeText('help.cfh.topic.' + ticket.reportedCategoryId) }</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Description</th>
|
||||||
|
<td>{ ticket.message }</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Caller</th>
|
||||||
|
<td>
|
||||||
|
<Text bold underline pointer onClick={ event => openUserInfo(ticket.reporterUserId) }>{ ticket.reporterUserName }</Text>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Reported User</th>
|
||||||
|
<td>
|
||||||
|
<Text bold underline pointer onClick={ event => openUserInfo(ticket.reportedUserId) }>{ ticket.reportedUserName }</Text>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</Column>
|
||||||
|
<Column size={ 4 } gap={ 1 }>
|
||||||
|
<Button variant="secondary" onClick={ () => setcfhChatlogOpen(!cfhChatlogOpen) }>Chatlog</Button>
|
||||||
|
<Button onClick={ event => closeIssue(CloseIssuesMessageComposer.RESOLUTION_USELESS) }>Close as useless</Button>
|
||||||
|
<Button variant="danger" onClick={ event => closeIssue(CloseIssuesMessageComposer.RESOLUTION_ABUSIVE) }>Close as abusive</Button>
|
||||||
|
<Button variant="success" onClick={ event => closeIssue(CloseIssuesMessageComposer.RESOLUTION_RESOLVED) }>Close as resolved</Button>
|
||||||
|
<Button variant="secondary" onClick={ event => releaseIssue(issueId)} >Release</Button>
|
||||||
|
</Column>
|
||||||
|
</Grid>
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView>
|
||||||
|
{ cfhChatlogOpen &&
|
||||||
|
<CfhChatlogView issueId={ issueId } onCloseClick={ () => setcfhChatlogOpen(false) }/> }
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
import { IssueMessageData, ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC } from 'react';
|
||||||
|
import { Base, Button, Column, Grid } from '../../../../common';
|
||||||
|
import { SendMessageHook } from '../../../../hooks';
|
||||||
|
|
||||||
|
interface ModToolsMyIssuesTabViewProps
|
||||||
|
{
|
||||||
|
myIssues: IssueMessageData[];
|
||||||
|
onIssueHandleClick(issueId: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ModToolsMyIssuesTabView: FC<ModToolsMyIssuesTabViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { myIssues = null, onIssueHandleClick = null } = props;
|
||||||
|
|
||||||
|
const onReleaseIssue = (issueId: number) => SendMessageHook(new ReleaseIssuesMessageComposer([issueId]));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column gap={ 0 } overflow="hidden">
|
||||||
|
<Column gap={ 2 }>
|
||||||
|
<Grid gap={ 1 } className="text-black fw-bold border-bottom pb-1">
|
||||||
|
<Base className="g-col-2">Type</Base>
|
||||||
|
<Base className="g-col-3">Room/Player</Base>
|
||||||
|
<Base className="g-col-3">Opened</Base>
|
||||||
|
<Base className="g-col-2"></Base>
|
||||||
|
<Base className="g-col-2"></Base>
|
||||||
|
</Grid>
|
||||||
|
</Column>
|
||||||
|
<Column overflow="auto" className="striped-children" gap={ 0 }>
|
||||||
|
{ myIssues && (myIssues.length > 0) && myIssues.map(issue =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<Grid key={ issue.issueId } gap={ 1 } alignItems="center" className="text-black py-1 border-bottom">
|
||||||
|
<Base className="g-col-2">{ issue.categoryId }</Base>
|
||||||
|
<Base className="g-col-3">{ issue.reportedUserName }</Base>
|
||||||
|
<Base className="g-col-3">{ new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString() }</Base>
|
||||||
|
<Base className="g-col-2">
|
||||||
|
<Button variant="primary" onClick={ event => onIssueHandleClick(issue.issueId) }>Handle</Button>
|
||||||
|
</Base>
|
||||||
|
<Base className="g-col-2">
|
||||||
|
<Button variant="danger" onClick={ event => onReleaseIssue(issue.issueId) }>Release</Button>
|
||||||
|
</Base>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}) }
|
||||||
|
</Column>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
import { IssueMessageData, PickIssuesMessageComposer } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC } from 'react';
|
||||||
|
import { Base, Button, Column, Grid } from '../../../../common';
|
||||||
|
import { SendMessageHook } from '../../../../hooks';
|
||||||
|
|
||||||
|
interface ModToolsOpenIssuesTabViewProps
|
||||||
|
{
|
||||||
|
openIssues: IssueMessageData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ModToolsOpenIssuesTabView: FC<ModToolsOpenIssuesTabViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { openIssues = null } = props;
|
||||||
|
|
||||||
|
const onPickIssue = (issueId: number) => SendMessageHook(new PickIssuesMessageComposer([issueId], false, 0, 'pick issue button'));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column gap={ 0 } overflow="hidden">
|
||||||
|
<Column gap={ 2 }>
|
||||||
|
<Grid gap={ 1 } className="text-black fw-bold border-bottom pb-1">
|
||||||
|
<Base className="g-col-2">Type</Base>
|
||||||
|
<Base className="g-col-3">Room/Player</Base>
|
||||||
|
<Base className="g-col-4">Opened</Base>
|
||||||
|
<Base className="g-col-3"></Base>
|
||||||
|
</Grid>
|
||||||
|
</Column>
|
||||||
|
<Column overflow="auto" className="striped-children" gap={ 0 }>
|
||||||
|
{ openIssues && (openIssues.length > 0) && openIssues.map(issue =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<Grid key={ issue.issueId } gap={ 1 } alignItems="center" className="text-black py-1 border-bottom">
|
||||||
|
<Base className="g-col-2">{ issue.categoryId }</Base>
|
||||||
|
<Base className="g-col-3">{ issue.reportedUserName }</Base>
|
||||||
|
<Base className="g-col-4">{ new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString() }</Base>
|
||||||
|
<Base className="g-col-3">
|
||||||
|
<Button variant="success" onClick={ event => onPickIssue(issue.issueId) }>Pick Issue</Button>
|
||||||
|
</Base>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}) }
|
||||||
|
</Column>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
import { IssueMessageData } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC } from 'react';
|
||||||
|
import { Base, Column, Grid } from '../../../../common';
|
||||||
|
|
||||||
|
interface ModToolsPickedIssuesTabViewProps
|
||||||
|
{
|
||||||
|
pickedIssues: IssueMessageData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ModToolsPickedIssuesTabView: FC<ModToolsPickedIssuesTabViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { pickedIssues = null } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column gap={ 0 } overflow="hidden">
|
||||||
|
<Column gap={ 2 }>
|
||||||
|
<Grid gap={ 1 } className="text-black fw-bold border-bottom pb-1">
|
||||||
|
<Base className="g-col-2">Type</Base>
|
||||||
|
<Base className="g-col-3">Room/Player</Base>
|
||||||
|
<Base className="g-col-4">Opened</Base>
|
||||||
|
<Base className="g-col-3">Picker</Base>
|
||||||
|
</Grid>
|
||||||
|
</Column>
|
||||||
|
<Column overflow="auto" className="striped-children" gap={ 0 }>
|
||||||
|
{ pickedIssues && (pickedIssues.length > 0) && pickedIssues.map(issue =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<Grid key={ issue.issueId } gap={ 1 } alignItems="center" className="text-black py-1 border-bottom">
|
||||||
|
<Base className="g-col-2">{ issue.categoryId }</Base>
|
||||||
|
<Base className="g-col-3">{ issue.reportedUserName }</Base>
|
||||||
|
<Base className="g-col-4">{ new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString() }</Base>
|
||||||
|
<Base className="g-col-3">{ issue.pickerUserName }</Base>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}) }
|
||||||
|
</Column>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
}
|
@ -2,12 +2,16 @@ import { IssueMessageData } from '@nitrots/nitro-renderer';
|
|||||||
import { FC, useCallback, useMemo, useState } from 'react';
|
import { FC, useCallback, useMemo, useState } from 'react';
|
||||||
import { GetSessionDataManager } from '../../../../api';
|
import { GetSessionDataManager } from '../../../../api';
|
||||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../layout';
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../layout';
|
||||||
import { useModToolsContext } from '../../context/ModToolsContext';
|
import { useModToolsContext } from '../../ModToolsContext';
|
||||||
import { IssueInfoView } from './issue-info/IssueInfoView';
|
import { ModToolsIssueInfoView } from './ModToolsIssueInfoView';
|
||||||
import { ModToolsTicketsViewProps } from './ModToolsTicketsView.types';
|
import { ModToolsMyIssuesTabView } from './ModToolsMyIssuesTabView';
|
||||||
import { ModToolsMyIssuesTabView } from './my-issues/ModToolsMyIssuesTabView';
|
import { ModToolsOpenIssuesTabView } from './ModToolsOpenIssuesTabView';
|
||||||
import { ModToolsOpenIssuesTabView } from './open-issues/ModToolsOpenIssuesTabView';
|
import { ModToolsPickedIssuesTabView } from './ModToolsPickedIssuesTabView';
|
||||||
import { ModToolsPickedIssuesTabView } from './picked-issues/ModToolsPickedIssuesTabView';
|
|
||||||
|
interface ModToolsTicketsViewProps
|
||||||
|
{
|
||||||
|
onCloseClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
const TABS: string[] = [
|
const TABS: string[] = [
|
||||||
'Open Issues',
|
'Open Issues',
|
||||||
@ -82,25 +86,21 @@ export const ModToolsTicketsView: FC<ModToolsTicketsViewProps> = props =>
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<NitroCardView className="nitro-mod-tools-tickets" simple={ false }>
|
<NitroCardView className="nitro-mod-tools-tickets">
|
||||||
<NitroCardHeaderView headerText={ 'Tickets' } onCloseClick={ onCloseClick } />
|
<NitroCardHeaderView headerText={ 'Tickets' } onCloseClick={ onCloseClick } />
|
||||||
<NitroCardContentView className="p-0 text-black">
|
|
||||||
<NitroCardTabsView>
|
<NitroCardTabsView>
|
||||||
{ TABS.map((tab, index) =>
|
{ TABS.map((tab, index) =>
|
||||||
{
|
{
|
||||||
return (<NitroCardTabsItemView key={ index } isActive={ currentTab === index } onClick={ () => setCurrentTab(index) }>
|
return (<NitroCardTabsItemView key={ index } isActive={ (currentTab === index) } onClick={ event => setCurrentTab(index) }>
|
||||||
{ tab }
|
{ tab }
|
||||||
</NitroCardTabsItemView>);
|
</NitroCardTabsItemView>);
|
||||||
}) }
|
}) }
|
||||||
</NitroCardTabsView>
|
</NitroCardTabsView>
|
||||||
<div className="p-2">
|
<NitroCardContentView gap={ 1 }>
|
||||||
<CurrentTabComponent />
|
<CurrentTabComponent />
|
||||||
</div>
|
|
||||||
</NitroCardContentView>
|
</NitroCardContentView>
|
||||||
</NitroCardView>
|
</NitroCardView>
|
||||||
{
|
{ issueInfoWindows && (issueInfoWindows.length > 0) && issueInfoWindows.map(issueId => <ModToolsIssueInfoView key={ issueId } issueId={ issueId } onIssueInfoClosed={ onIssueInfoClosed } />) }
|
||||||
issueInfoWindows && issueInfoWindows.map(issueId => <IssueInfoView key={issueId} issueId={issueId} onIssueInfoClosed={onIssueInfoClosed}/>)
|
|
||||||
}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -1,20 +1,20 @@
|
|||||||
import { ChatRecordData, GetUserChatlogMessageComposer, UserChatlogEvent } from '@nitrots/nitro-renderer';
|
import { ChatRecordData, GetUserChatlogMessageComposer, UserChatlogEvent } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useEffect, useState } from 'react';
|
import { FC, useCallback, useEffect, useState } from 'react';
|
||||||
import { CreateMessageHook, SendMessageHook } from '../../../../../hooks';
|
import { BatchUpdates, CreateMessageHook, SendMessageHook } from '../../../../hooks';
|
||||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout';
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
|
||||||
import { ChatlogView } from '../../chatlog/ChatlogView';
|
import { ChatlogView } from '../chatlog/ChatlogView';
|
||||||
import { ModToolsUserChatlogViewProps } from './ModToolsUserChatlogView.types';
|
|
||||||
|
interface ModToolsUserChatlogViewProps
|
||||||
|
{
|
||||||
|
userId: number;
|
||||||
|
onCloseClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
export const ModToolsUserChatlogView: FC<ModToolsUserChatlogViewProps> = props =>
|
export const ModToolsUserChatlogView: FC<ModToolsUserChatlogViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { userId = null, onCloseClick = null } = props;
|
const { userId = null, onCloseClick = null } = props;
|
||||||
const [userChatlog, setUserChatlog] = useState<ChatRecordData[]>(null);
|
const [ userChatlog, setUserChatlog ] = useState<ChatRecordData[]>(null);
|
||||||
const [username, setUsername] = useState<string>(null);
|
const [ username, setUsername ] = useState<string>(null);
|
||||||
|
|
||||||
useEffect(() =>
|
|
||||||
{
|
|
||||||
SendMessageHook(new GetUserChatlogMessageComposer(userId));
|
|
||||||
}, [userId]);
|
|
||||||
|
|
||||||
const onModtoolUserChatlogEvent = useCallback((event: UserChatlogEvent) =>
|
const onModtoolUserChatlogEvent = useCallback((event: UserChatlogEvent) =>
|
||||||
{
|
{
|
||||||
@ -22,19 +22,26 @@ export const ModToolsUserChatlogView: FC<ModToolsUserChatlogViewProps> = props =
|
|||||||
|
|
||||||
if(!parser || parser.data.userId !== userId) return;
|
if(!parser || parser.data.userId !== userId) return;
|
||||||
|
|
||||||
|
BatchUpdates(() =>
|
||||||
|
{
|
||||||
setUsername(parser.data.username);
|
setUsername(parser.data.username);
|
||||||
setUserChatlog(parser.data.roomChatlogs);
|
setUserChatlog(parser.data.roomChatlogs);
|
||||||
}, [setUsername, setUserChatlog, userId]);
|
});
|
||||||
|
}, [ userId ]);
|
||||||
|
|
||||||
CreateMessageHook(UserChatlogEvent, onModtoolUserChatlogEvent);
|
CreateMessageHook(UserChatlogEvent, onModtoolUserChatlogEvent);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new GetUserChatlogMessageComposer(userId));
|
||||||
|
}, [ userId ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NitroCardView className="nitro-mod-tools-user-chatlog" simple={true}>
|
<NitroCardView className="nitro-mod-tools-chatlog" simple>
|
||||||
<NitroCardHeaderView headerText={'User Chatlog' + (username ? ': ' + username : '')} onCloseClick={() => onCloseClick()} />
|
<NitroCardHeaderView headerText={ `User Chatlog: ${ username || '' }` } onCloseClick={ onCloseClick } />
|
||||||
<NitroCardContentView className="text-black h-100">
|
<NitroCardContentView className="text-black h-100">
|
||||||
{userChatlog &&
|
{ userChatlog &&
|
||||||
<ChatlogView records={userChatlog} />
|
<ChatlogView records={userChatlog} /> }
|
||||||
}
|
|
||||||
</NitroCardContentView>
|
</NitroCardContentView>
|
||||||
</NitroCardView>
|
</NitroCardView>
|
||||||
);
|
);
|
@ -0,0 +1,182 @@
|
|||||||
|
import { CallForHelpTopicData, DefaultSanctionMessageComposer, ModAlertMessageComposer, ModBanMessageComposer, ModKickMessageComposer, ModMessageMessageComposer, ModMuteMessageComposer, ModTradingLockMessageComposer } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useMemo, useState } from 'react';
|
||||||
|
import { LocalizeText } from '../../../../api';
|
||||||
|
import { Button, Column, Flex, Text } from '../../../../common';
|
||||||
|
import { SendMessageHook } from '../../../../hooks';
|
||||||
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
|
||||||
|
import { NotificationAlertType } from '../../../../views/notification-center/common/NotificationAlertType';
|
||||||
|
import { NotificationUtilities } from '../../../../views/notification-center/common/NotificationUtilities';
|
||||||
|
import { useModToolsContext } from '../../ModToolsContext';
|
||||||
|
import { ISelectedUser } from '../../utils/ISelectedUser';
|
||||||
|
import { ModActionDefinition } from '../../utils/ModActionDefinition';
|
||||||
|
|
||||||
|
interface ModToolsUserModActionViewProps
|
||||||
|
{
|
||||||
|
user: ISelectedUser;
|
||||||
|
onCloseClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MOD_ACTION_DEFINITIONS = [
|
||||||
|
new ModActionDefinition(1, 'Alert', ModActionDefinition.ALERT, 1, 0),
|
||||||
|
new ModActionDefinition(2, 'Mute 1h', ModActionDefinition.MUTE, 2, 0),
|
||||||
|
new ModActionDefinition(4, 'Ban 7 days', ModActionDefinition.BAN, 4, 0),
|
||||||
|
new ModActionDefinition(3, 'Ban 18h', ModActionDefinition.BAN, 3, 0),
|
||||||
|
new ModActionDefinition(5, 'Ban 30 days (step 1)', ModActionDefinition.BAN, 5, 0),
|
||||||
|
new ModActionDefinition(7, 'Ban 30 days (step 2)', ModActionDefinition.BAN, 7, 0),
|
||||||
|
new ModActionDefinition(6, 'Ban 100 years', ModActionDefinition.BAN, 6, 0),
|
||||||
|
new ModActionDefinition(106, 'Ban avatar-only 100 years', ModActionDefinition.BAN, 6, 0),
|
||||||
|
new ModActionDefinition(101, 'Kick', ModActionDefinition.KICK, 0, 0),
|
||||||
|
new ModActionDefinition(102, 'Lock trade 1 week', ModActionDefinition.TRADE_LOCK, 0, 168),
|
||||||
|
new ModActionDefinition(104, 'Lock trade permanent', ModActionDefinition.TRADE_LOCK, 0, 876000),
|
||||||
|
new ModActionDefinition(105, 'Message', ModActionDefinition.MESSAGE, 0, 0),
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ModToolsUserModActionView: FC<ModToolsUserModActionViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { user = null, onCloseClick = null } = props;
|
||||||
|
const [ selectedTopic, setSelectedTopic ] = useState(-1);
|
||||||
|
const [ selectedAction, setSelectedAction ] = useState(-1);
|
||||||
|
const [ message, setMessage ] = useState<string>('');
|
||||||
|
const { modToolsState = null } = useModToolsContext();
|
||||||
|
const { cfhCategories = null, settings = null } = modToolsState;
|
||||||
|
|
||||||
|
const topics = useMemo(() =>
|
||||||
|
{
|
||||||
|
const values: CallForHelpTopicData[] = [];
|
||||||
|
|
||||||
|
if(cfhCategories && cfhCategories.length)
|
||||||
|
{
|
||||||
|
for(const category of cfhCategories)
|
||||||
|
{
|
||||||
|
for(const topic of category.topics) values.push(topic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}, [ cfhCategories ]);
|
||||||
|
|
||||||
|
const sendAlert = (message: string) =>
|
||||||
|
{
|
||||||
|
NotificationUtilities.simpleAlert(message, NotificationAlertType.DEFAULT, null, null, 'Error');
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendDefaultSanction = () =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new DefaultSanctionMessageComposer(user.userId, selectedTopic, message));
|
||||||
|
|
||||||
|
onCloseClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendSanction = () =>
|
||||||
|
{
|
||||||
|
let errorMessage: string = null;
|
||||||
|
|
||||||
|
const category = topics[selectedTopic];
|
||||||
|
const sanction = MOD_ACTION_DEFINITIONS[selectedAction];
|
||||||
|
|
||||||
|
if((selectedTopic === -1) || (selectedAction === -1)) errorMessage = 'You must select a CFH topic and Sanction';
|
||||||
|
else if(!settings || !settings.cfhPermission) errorMessage = 'You do not have permission to do this';
|
||||||
|
else if(!category) errorMessage = 'You must select a CFH topic';
|
||||||
|
else if(!sanction) errorMessage = 'You must select a sanction';
|
||||||
|
|
||||||
|
if(errorMessage)
|
||||||
|
{
|
||||||
|
sendAlert('You must select a sanction');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageOrDefault = (message.trim().length === 0) ? LocalizeText(`help.cfh.topic.${ category.id }`) : message;
|
||||||
|
|
||||||
|
switch(sanction.actionType)
|
||||||
|
{
|
||||||
|
case ModActionDefinition.ALERT: {
|
||||||
|
if(!settings.alertPermission)
|
||||||
|
{
|
||||||
|
sendAlert('You have insufficient permissions');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(message.trim().length === 0)
|
||||||
|
{
|
||||||
|
sendAlert('Please write a message to user');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendMessageHook(new ModAlertMessageComposer(user.userId, message, category.id));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ModActionDefinition.MUTE:
|
||||||
|
SendMessageHook(new ModMuteMessageComposer(user.userId, messageOrDefault, category.id));
|
||||||
|
break;
|
||||||
|
case ModActionDefinition.BAN: {
|
||||||
|
if(!settings.banPermission)
|
||||||
|
{
|
||||||
|
sendAlert('You have insufficient permissions');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendMessageHook(new ModBanMessageComposer(user.userId, messageOrDefault, category.id, selectedAction, (sanction.actionId === 106)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ModActionDefinition.KICK: {
|
||||||
|
if(!settings.kickPermission)
|
||||||
|
{
|
||||||
|
sendAlert('You have insufficient permissions');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendMessageHook(new ModKickMessageComposer(user.userId, messageOrDefault, category.id));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ModActionDefinition.TRADE_LOCK: {
|
||||||
|
const numSeconds = (sanction.actionLengthHours * 60);
|
||||||
|
|
||||||
|
SendMessageHook(new ModTradingLockMessageComposer(user.userId, messageOrDefault, numSeconds, category.id));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ModActionDefinition.MESSAGE: {
|
||||||
|
if(message.trim().length === 0)
|
||||||
|
{
|
||||||
|
sendAlert('Please write a message to user');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendMessageHook(new ModMessageMessageComposer(user.userId, message, category.id));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCloseClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!user) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NitroCardView className="nitro-mod-tools-user-action" simple={true}>
|
||||||
|
<NitroCardHeaderView headerText={'Mod Action: ' + (user ? user.username : '')} onCloseClick={ () => onCloseClick() } />
|
||||||
|
<NitroCardContentView className="text-black">
|
||||||
|
<select className="form-select form-select-sm" value={ selectedTopic } onChange={ event => setSelectedTopic(parseInt(event.target.value)) }>
|
||||||
|
<option value={ -1 } disabled>CFH Topic</option>
|
||||||
|
{ topics.map((topic, index) => <option key={ index } value={ index }>{LocalizeText('help.cfh.topic.' + topic.id)}</option>) }
|
||||||
|
</select>
|
||||||
|
<select className="form-select form-select-sm" value={ selectedAction } onChange={ event => setSelectedAction(parseInt(event.target.value)) }>
|
||||||
|
<option value={ -1 } disabled>Sanction Type</option>
|
||||||
|
{ MOD_ACTION_DEFINITIONS.map((action, index) => <option key={ index } value={ index }>{ action.name }</option>) }
|
||||||
|
</select>
|
||||||
|
<Column gap={ 1 }>
|
||||||
|
<Text small>Optional message type, overrides default</Text>
|
||||||
|
<textarea className="form-control" value={ message } onChange={ event => setMessage(event.target.value) }/>
|
||||||
|
</Column>
|
||||||
|
<Flex justifyContent="between" gap={ 1 }>
|
||||||
|
<Button variant="danger" onClick={ sendSanction }>Sanction</Button>
|
||||||
|
<Button variant="success" onClick={ sendDefaultSanction }>Default Sanction</Button>
|
||||||
|
</Flex>
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
import { GetRoomVisitsMessageComposer, RoomVisitsData, RoomVisitsEvent } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useEffect, useState } from 'react';
|
||||||
|
import { AutoSizer, List, ListRowProps } from 'react-virtualized';
|
||||||
|
import { TryVisitRoom } from '../../../../api';
|
||||||
|
import { Base, Column, Grid, Text } from '../../../../common';
|
||||||
|
import { CreateMessageHook, SendMessageHook } from '../../../../hooks';
|
||||||
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
|
||||||
|
|
||||||
|
interface ModToolsUserRoomVisitsViewProps
|
||||||
|
{
|
||||||
|
userId: number;
|
||||||
|
onCloseClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ModToolsUserRoomVisitsView: FC<ModToolsUserRoomVisitsViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { userId = null, onCloseClick = null } = props;
|
||||||
|
const [ roomVisitData, setRoomVisitData ] = useState<RoomVisitsData>(null);
|
||||||
|
|
||||||
|
const onModtoolReceivedRoomsUserEvent = useCallback((event: RoomVisitsEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!parser || (parser.data.userId !== userId)) return;
|
||||||
|
|
||||||
|
setRoomVisitData(parser.data);
|
||||||
|
}, [ userId ]);
|
||||||
|
|
||||||
|
CreateMessageHook(RoomVisitsEvent, onModtoolReceivedRoomsUserEvent);
|
||||||
|
|
||||||
|
const RowRenderer = (props: ListRowProps) =>
|
||||||
|
{
|
||||||
|
const item = roomVisitData.rooms[props.index];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid key={ props.key } style={ props.style } gap={ 1 } alignItems="center" className="text-black py-1 border-bottom">
|
||||||
|
<Text className="g-col-2">{ item.enterHour.toString().padStart(2, '0') }: { item.enterMinute.toString().padStart(2, '0') }</Text>
|
||||||
|
<Text className="g-col-7">{ item.roomName }</Text>
|
||||||
|
<Text bold underline pointer variant="primary" className="g-col-3" onClick={ event => TryVisitRoom(item.roomId) }>Visit Room</Text>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new GetRoomVisitsMessageComposer(userId));
|
||||||
|
}, [userId]);
|
||||||
|
|
||||||
|
if(!userId) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NitroCardView className="nitro-mod-tools-user-visits" simple>
|
||||||
|
<NitroCardHeaderView headerText={ 'User Visits' } onCloseClick={ onCloseClick } />
|
||||||
|
<NitroCardContentView className="text-black" gap={ 1 }>
|
||||||
|
<Column gap={ 0 } overflow="hidden">
|
||||||
|
<Column gap={ 2 }>
|
||||||
|
<Grid gap={ 1 } className="text-black fw-bold border-bottom pb-1">
|
||||||
|
<Base className="g-col-2">Time</Base>
|
||||||
|
<Base className="g-col-7">Room name</Base>
|
||||||
|
<Base className="g-col-3">Visit</Base>
|
||||||
|
</Grid>
|
||||||
|
</Column>
|
||||||
|
<Column className="log-container striped-children" overflow="auto" gap={ 0 }>
|
||||||
|
{ roomVisitData &&
|
||||||
|
<AutoSizer defaultWidth={ 400 } defaultHeight={ 200 }>
|
||||||
|
{ ({ height, width }) =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
width={ width }
|
||||||
|
height={ height }
|
||||||
|
rowCount={ roomVisitData.rooms.length }
|
||||||
|
rowHeight={ 20 }
|
||||||
|
className={'log-entry-container' }
|
||||||
|
rowRenderer={ RowRenderer }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} }
|
||||||
|
</AutoSizer> }
|
||||||
|
</Column>
|
||||||
|
</Column>
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
import { ModMessageMessageComposer } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useState } from 'react';
|
||||||
|
import { Button, Text } from '../../../../common';
|
||||||
|
import { NotificationAlertEvent } from '../../../../events';
|
||||||
|
import { dispatchUiEvent, SendMessageHook } from '../../../../hooks';
|
||||||
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
|
||||||
|
import { ISelectedUser } from '../../utils/ISelectedUser';
|
||||||
|
|
||||||
|
interface ModToolsUserSendMessageViewProps
|
||||||
|
{
|
||||||
|
user: ISelectedUser;
|
||||||
|
onCloseClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ModToolsUserSendMessageView: FC<ModToolsUserSendMessageViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { user = null, onCloseClick = null } = props;
|
||||||
|
const [ message, setMessage ] = useState('');
|
||||||
|
|
||||||
|
const sendMessage = useCallback(() =>
|
||||||
|
{
|
||||||
|
if(message.trim().length === 0)
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new NotificationAlertEvent([ 'Please write a message to user.' ], null, null, null, 'Error', null));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendMessageHook(new ModMessageMessageComposer(user.userId, message, -999));
|
||||||
|
|
||||||
|
onCloseClick();
|
||||||
|
}, [ message, user, onCloseClick ]);
|
||||||
|
|
||||||
|
if(!user) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NitroCardView className="nitro-mod-tools-user-message" simple>
|
||||||
|
<NitroCardHeaderView headerText={'Send Message'} onCloseClick={ () => onCloseClick() } />
|
||||||
|
<NitroCardContentView className="text-black">
|
||||||
|
<Text>Message To: { user.username }</Text>
|
||||||
|
<textarea className="form-control" value={ message } onChange={ event => setMessage(event.target.value) }></textarea>
|
||||||
|
<Button fullWidth onClick={ sendMessage }>Send message</Button>
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView>
|
||||||
|
);
|
||||||
|
}
|
@ -1,13 +1,19 @@
|
|||||||
import { FriendlyTime, GetModeratorUserInfoMessageComposer, ModeratorUserInfoData, ModeratorUserInfoEvent } from '@nitrots/nitro-renderer';
|
import { FriendlyTime, GetModeratorUserInfoMessageComposer, ModeratorUserInfoData, ModeratorUserInfoEvent } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { LocalizeText } from '../../../../../api';
|
import { LocalizeText } from '../../../../api';
|
||||||
import { ModToolsOpenUserChatlogEvent } from '../../../../../events/mod-tools/ModToolsOpenUserChatlogEvent';
|
import { Button, Column, Grid } from '../../../../common';
|
||||||
import { CreateMessageHook, dispatchUiEvent, SendMessageHook } from '../../../../../hooks';
|
import { ModToolsOpenUserChatlogEvent } from '../../../../events/mod-tools/ModToolsOpenUserChatlogEvent';
|
||||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutButton, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../layout';
|
import { CreateMessageHook, dispatchUiEvent, SendMessageHook } from '../../../../hooks';
|
||||||
import { ModToolsUserModActionView } from '../user-mod-action/ModToolsUserModActionView';
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
|
||||||
import { ModToolsUserRoomVisitsView } from '../user-room-visits/ModToolsUserRoomVisitsView';
|
import { ModToolsUserModActionView } from './ModToolsUserModActionView';
|
||||||
import { ModToolsSendUserMessageView } from '../user-sendmessage/ModToolsSendUserMessageView';
|
import { ModToolsUserRoomVisitsView } from './ModToolsUserRoomVisitsView';
|
||||||
import { ModToolsUserViewProps } from './ModToolsUserView.types';
|
import { ModToolsUserSendMessageView } from './ModToolsUserSendMessageView';
|
||||||
|
|
||||||
|
interface ModToolsUserViewProps
|
||||||
|
{
|
||||||
|
userId: number;
|
||||||
|
onCloseClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
export const ModToolsUserView: FC<ModToolsUserViewProps> = props =>
|
export const ModToolsUserView: FC<ModToolsUserViewProps> = props =>
|
||||||
{
|
{
|
||||||
@ -17,11 +23,6 @@ export const ModToolsUserView: FC<ModToolsUserViewProps> = props =>
|
|||||||
const [ modActionVisible, setModActionVisible ] = useState(false);
|
const [ modActionVisible, setModActionVisible ] = useState(false);
|
||||||
const [ roomVisitsVisible, setRoomVisitsVisible ] = useState(false);
|
const [ roomVisitsVisible, setRoomVisitsVisible ] = useState(false);
|
||||||
|
|
||||||
useEffect(() =>
|
|
||||||
{
|
|
||||||
SendMessageHook(new GetModeratorUserInfoMessageComposer(userId));
|
|
||||||
}, [ userId ]);
|
|
||||||
|
|
||||||
const onModtoolUserInfoEvent = useCallback((event: ModeratorUserInfoEvent) =>
|
const onModtoolUserInfoEvent = useCallback((event: ModeratorUserInfoEvent) =>
|
||||||
{
|
{
|
||||||
const parser = event.getParser();
|
const parser = event.getParser();
|
||||||
@ -29,7 +30,7 @@ export const ModToolsUserView: FC<ModToolsUserViewProps> = props =>
|
|||||||
if(!parser || parser.data.userId !== userId) return;
|
if(!parser || parser.data.userId !== userId) return;
|
||||||
|
|
||||||
setUserInfo(parser.data);
|
setUserInfo(parser.data);
|
||||||
}, [setUserInfo, userId]);
|
}, [ userId ]);
|
||||||
|
|
||||||
CreateMessageHook(ModeratorUserInfoEvent, onModtoolUserInfoEvent);
|
CreateMessageHook(ModeratorUserInfoEvent, onModtoolUserInfoEvent);
|
||||||
|
|
||||||
@ -98,52 +99,58 @@ export const ModToolsUserView: FC<ModToolsUserViewProps> = props =>
|
|||||||
];
|
];
|
||||||
}, [ userInfo ]);
|
}, [ userInfo ]);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new GetModeratorUserInfoMessageComposer(userId));
|
||||||
|
}, [ userId ]);
|
||||||
|
|
||||||
if(!userInfo) return null;
|
if(!userInfo) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<NitroCardView className="nitro-mod-tools-user" simple={true}>
|
<NitroCardView className="nitro-mod-tools-user" simple>
|
||||||
<NitroCardHeaderView headerText={ LocalizeText('modtools.userinfo.title', [ 'username' ], [ userInfo.userName ]) } onCloseClick={ () => onCloseClick() } />
|
<NitroCardHeaderView headerText={ LocalizeText('modtools.userinfo.title', [ 'username' ], [ userInfo.userName ]) } onCloseClick={ () => onCloseClick() } />
|
||||||
<NitroCardContentView className="text-black">
|
<NitroCardContentView className="text-black">
|
||||||
<NitroLayoutGrid>
|
<Grid overflow="hidden">
|
||||||
<NitroLayoutGridColumn size={ 8 }>
|
<Column size={ 8 } overflow="auto">
|
||||||
<table className="table table-striped table-sm table-text-small text-black m-0">
|
<table className="table table-striped table-sm table-text-small text-black m-0">
|
||||||
<tbody>
|
<tbody>
|
||||||
{ userProperties.map( (property, index) =>
|
{ userProperties.map( (property, index) =>
|
||||||
{
|
{
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr key={index}>
|
<tr key={ index }>
|
||||||
<th scope="row">{ LocalizeText(property.localeKey) }</th>
|
<th scope="row">{ LocalizeText(property.localeKey) }</th>
|
||||||
<td>
|
<td>
|
||||||
{ property.value }
|
{ property.value }
|
||||||
{ property.showOnline && <i className={ `icon icon-pf-${ userInfo.online ? 'online' : 'offline' } ms-2` } /> }
|
{ property.showOnline &&
|
||||||
|
<i className={ `icon icon-pf-${ userInfo.online ? 'online' : 'offline' } ms-2` } /> }
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}) }
|
}) }
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</NitroLayoutGridColumn>
|
</Column>
|
||||||
<NitroLayoutGridColumn size={ 4 }>
|
<Column size={ 4 } gap={ 1 }>
|
||||||
<NitroLayoutButton variant="primary" size="sm" onClick={ event => dispatchUiEvent(new ModToolsOpenUserChatlogEvent(userId)) }>
|
<Button onClick={ event => dispatchUiEvent(new ModToolsOpenUserChatlogEvent(userId)) }>
|
||||||
Room Chat
|
Room Chat
|
||||||
</NitroLayoutButton>
|
</Button>
|
||||||
<NitroLayoutButton variant="primary" size="sm" onClick={ event => setSendMessageVisible(!sendMessageVisible) }>
|
<Button onClick={ event => setSendMessageVisible(!sendMessageVisible) }>
|
||||||
Send Message
|
Send Message
|
||||||
</NitroLayoutButton>
|
</Button>
|
||||||
<NitroLayoutButton variant="primary" size="sm" onClick={ event => setRoomVisitsVisible(!roomVisitsVisible) }>
|
<Button onClick={ event => setRoomVisitsVisible(!roomVisitsVisible) }>
|
||||||
Room Visits
|
Room Visits
|
||||||
</NitroLayoutButton>
|
</Button>
|
||||||
<NitroLayoutButton variant="primary" size="sm" onClick={ event => setModActionVisible(!modActionVisible) }>
|
<Button onClick={ event => setModActionVisible(!modActionVisible) }>
|
||||||
Mod Action
|
Mod Action
|
||||||
</NitroLayoutButton>
|
</Button>
|
||||||
</NitroLayoutGridColumn>
|
</Column>
|
||||||
</NitroLayoutGrid>
|
</Grid>
|
||||||
</NitroCardContentView>
|
</NitroCardContentView>
|
||||||
</NitroCardView>
|
</NitroCardView>
|
||||||
{ sendMessageVisible &&
|
{ sendMessageVisible &&
|
||||||
<ModToolsSendUserMessageView user={ { userId: userId, username: userInfo.userName } } onCloseClick={ () => setSendMessageVisible(false) } /> }
|
<ModToolsUserSendMessageView user={ { userId: userId, username: userInfo.userName } } onCloseClick={ () => setSendMessageVisible(false) } /> }
|
||||||
{ modActionVisible &&
|
{ modActionVisible &&
|
||||||
<ModToolsUserModActionView user={ { userId: userId, username: userInfo.userName } } onCloseClick={ () => setModActionVisible(false) } /> }
|
<ModToolsUserModActionView user={ { userId: userId, username: userInfo.userName } } onCloseClick={ () => setModActionVisible(false) } /> }
|
||||||
{ roomVisitsVisible &&
|
{ roomVisitsVisible &&
|
@ -1,11 +1,16 @@
|
|||||||
import { createContext, FC, useContext } from 'react';
|
import { createContext, FC, ProviderProps, useContext } from 'react';
|
||||||
import { IPurseContext, PurseContextProps } from './PurseContext.types';
|
import { IPurse } from './common/IPurse';
|
||||||
|
|
||||||
|
interface IPurseContext
|
||||||
|
{
|
||||||
|
purse: IPurse;
|
||||||
|
}
|
||||||
|
|
||||||
const PurseContext = createContext<IPurseContext>({
|
const PurseContext = createContext<IPurseContext>({
|
||||||
purse: null
|
purse: null
|
||||||
});
|
});
|
||||||
|
|
||||||
export const PurseContextProvider: FC<PurseContextProps> = props =>
|
export const PurseContextProvider: FC<ProviderProps<IPurseContext>> = props =>
|
||||||
{
|
{
|
||||||
return <PurseContext.Provider value={ props.value }>{ props.children }</PurseContext.Provider>
|
return <PurseContext.Provider value={ props.value }>{ props.children }</PurseContext.Provider>
|
||||||
}
|
}
|
@ -2,10 +2,9 @@ import { ActivityPointNotificationMessageEvent, UserCreditsEvent, UserCurrencyEv
|
|||||||
import { FC, useCallback } from 'react';
|
import { FC, useCallback } from 'react';
|
||||||
import { CREDITS, DUCKETS, PlaySound } from '../../api/utils/PlaySound';
|
import { CREDITS, DUCKETS, PlaySound } from '../../api/utils/PlaySound';
|
||||||
import { CreateMessageHook } from '../../hooks/messages/message-event';
|
import { CreateMessageHook } from '../../hooks/messages/message-event';
|
||||||
import { usePurseContext } from './context/PurseContext';
|
import { usePurseContext } from './PurseContext';
|
||||||
import { PurseMessageHandlerProps } from './PurseMessageHandler.types';
|
|
||||||
|
|
||||||
export const PurseMessageHandler: FC<PurseMessageHandlerProps> = props =>
|
export const PurseMessageHandler: FC<{}> = props =>
|
||||||
{
|
{
|
||||||
const { purse = null } = usePurseContext();
|
const { purse = null } = usePurseContext();
|
||||||
|
|
26
src/components/purse/PurseView.scss
Normal file
26
src/components/purse/PurseView.scss
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
.nitro-purse-container {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
|
.nitro-purse {
|
||||||
|
background-color: 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);
|
||||||
|
|
||||||
|
.nitro-purse-subscription {
|
||||||
|
background-color: rgba($light, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitro-purse-button {
|
||||||
|
padding: 3px 2px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba($light, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitro-purse-seasonal-currency {
|
||||||
|
background-color: rgba($dark, .95);
|
||||||
|
box-shadow: inset 0px 5px lighten(rgba($dark, .6),2.5), inset 0 -4px darken(rgba($dark, .6), 4);
|
||||||
|
}
|
||||||
|
}
|
137
src/components/purse/PurseView.tsx
Normal file
137
src/components/purse/PurseView.tsx
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import { FriendlyTime, HabboClubLevelEnum, UserCurrencyComposer, UserSubscriptionComposer } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { CreateLinkEvent, GetConfiguration, LocalizeText } from '../../api';
|
||||||
|
import { Column, Flex, Grid, Text } from '../../common';
|
||||||
|
import { HcCenterEvent } from '../../events/hc-center/HcCenterEvent';
|
||||||
|
import { UserSettingsUIEvent } from '../../events/user-settings/UserSettingsUIEvent';
|
||||||
|
import { dispatchUiEvent } from '../../hooks';
|
||||||
|
import { SendMessageHook } from '../../hooks/messages/message-event';
|
||||||
|
import { CurrencyIcon } from '../../views/shared/currency-icon/CurrencyIcon';
|
||||||
|
import { IPurse } from './common/IPurse';
|
||||||
|
import { Purse } from './common/Purse';
|
||||||
|
import { PurseContextProvider } from './PurseContext';
|
||||||
|
import { PurseMessageHandler } from './PurseMessageHandler';
|
||||||
|
import { CurrencyView } from './views/CurrencyView';
|
||||||
|
import { SeasonalView } from './views/SeasonalView';
|
||||||
|
|
||||||
|
export let GLOBAL_PURSE: IPurse = null;
|
||||||
|
|
||||||
|
export const PurseView: FC<{}> = props =>
|
||||||
|
{
|
||||||
|
const [ purse, setPurse ] = useState<IPurse>(new Purse());
|
||||||
|
const [ updateId, setUpdateId ] = useState(-1);
|
||||||
|
|
||||||
|
const handleUserSettingsClick = () => dispatchUiEvent(new UserSettingsUIEvent(UserSettingsUIEvent.TOGGLE_USER_SETTINGS));
|
||||||
|
|
||||||
|
const handleHelpCenterClick = () => CreateLinkEvent('help/show');
|
||||||
|
|
||||||
|
const handleHcCenterClick = () => dispatchUiEvent(new HcCenterEvent(HcCenterEvent.TOGGLE_HC_CENTER));
|
||||||
|
|
||||||
|
const displayedCurrencies = useMemo(() => GetConfiguration<number[]>('system.currency.types', []), []);
|
||||||
|
|
||||||
|
const currencyDisplayNumberShort = useMemo(() => GetConfiguration<boolean>('currency.display.number.short', false), []);
|
||||||
|
|
||||||
|
const getClubText = useMemo(() =>
|
||||||
|
{
|
||||||
|
const totalDays = ((purse.clubPeriods * 31) + purse.clubDays);
|
||||||
|
const minutesUntilExpiration = purse.minutesUntilExpiration;
|
||||||
|
|
||||||
|
if(purse.clubLevel === HabboClubLevelEnum.NO_CLUB) return LocalizeText('purse.clubdays.zero.amount.text');
|
||||||
|
|
||||||
|
else if((minutesUntilExpiration > -1) && (minutesUntilExpiration < (60 * 24))) return FriendlyTime.shortFormat(minutesUntilExpiration * 60);
|
||||||
|
|
||||||
|
else return FriendlyTime.shortFormat(totalDays * 86400);
|
||||||
|
}, [ purse ]);
|
||||||
|
|
||||||
|
const getCurrencyElements = useCallback((offset: number, limit: number = -1, seasonal: boolean = false) =>
|
||||||
|
{
|
||||||
|
if(!purse.activityPoints.size) return null;
|
||||||
|
|
||||||
|
const types = Array.from(purse.activityPoints.keys()).filter(type => (displayedCurrencies.indexOf(type) >= 0));
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
while(count < offset)
|
||||||
|
{
|
||||||
|
types.shift();
|
||||||
|
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
|
||||||
|
const elements: JSX.Element[] = [];
|
||||||
|
|
||||||
|
for(const type of types)
|
||||||
|
{
|
||||||
|
if((limit > -1) && (count === limit)) break;
|
||||||
|
|
||||||
|
if(seasonal) elements.push(<SeasonalView key={ type } type={ type } amount={ purse.activityPoints.get(type) } />);
|
||||||
|
else elements.push(<CurrencyView key={ type } type={ type } amount={ purse.activityPoints.get(type) } short={ currencyDisplayNumberShort } />);
|
||||||
|
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements;
|
||||||
|
}, [ purse, displayedCurrencies, currencyDisplayNumberShort ]);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
const purse = new Purse();
|
||||||
|
|
||||||
|
GLOBAL_PURSE = purse;
|
||||||
|
|
||||||
|
purse.notifier = () => setUpdateId(prevValue => (prevValue + 1));
|
||||||
|
|
||||||
|
setPurse(purse);
|
||||||
|
|
||||||
|
return () => (purse.notifier = null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if(!purse) return;
|
||||||
|
|
||||||
|
SendMessageHook(new UserCurrencyComposer());
|
||||||
|
}, [ purse ]);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new UserSubscriptionComposer('habbo_club'));
|
||||||
|
|
||||||
|
const interval = setInterval(() => SendMessageHook(new UserSubscriptionComposer('habbo_club')), 50000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [ purse ]);
|
||||||
|
|
||||||
|
if(!purse) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PurseContextProvider value={ { purse } }>
|
||||||
|
<PurseMessageHandler />
|
||||||
|
<Column className="nitro-purse-container" gap={ 1 }>
|
||||||
|
<Flex className="nitro-purse rounded-bottom p-1">
|
||||||
|
<Grid fullWidth gap={ 1 }>
|
||||||
|
<Column justifyContent="center" size={ 6 } gap={ 0 }>
|
||||||
|
<CurrencyView type={ -1 } amount={ purse.credits } short={ currencyDisplayNumberShort } />
|
||||||
|
{ getCurrencyElements(0, 2) }
|
||||||
|
</Column>
|
||||||
|
<Column center pointer size={ 4 } gap={ 1 } className="nitro-purse-subscription rounded" onClick={ handleHcCenterClick }>
|
||||||
|
<CurrencyIcon 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={ handleHelpCenterClick }>
|
||||||
|
<i className="icon icon-help"/>
|
||||||
|
</Flex>
|
||||||
|
<Flex center pointer fullHeight className="nitro-purse-button p-1 rounded" onClick={ handleUserSettingsClick } >
|
||||||
|
<i className="icon icon-cog"/>
|
||||||
|
</Flex>
|
||||||
|
</Column>
|
||||||
|
</Grid>
|
||||||
|
</Flex>
|
||||||
|
{ getCurrencyElements(2, -1, true) }
|
||||||
|
</Column>
|
||||||
|
</PurseContextProvider>
|
||||||
|
);
|
||||||
|
}
|
40
src/components/purse/views/CurrencyView.tsx
Normal file
40
src/components/purse/views/CurrencyView.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { FC, useMemo } from 'react';
|
||||||
|
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
|
||||||
|
import { LocalizeFormattedNumber, LocalizeShortNumber } from '../../../api';
|
||||||
|
import { Flex, Text } from '../../../common';
|
||||||
|
import { CurrencyIcon } from '../../../views/shared/currency-icon/CurrencyIcon';
|
||||||
|
|
||||||
|
interface CurrencyViewProps
|
||||||
|
{
|
||||||
|
type: number;
|
||||||
|
amount: number;
|
||||||
|
short: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CurrencyView: FC<CurrencyViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { type = -1, amount = -1, short = false } = props;
|
||||||
|
|
||||||
|
const element = useMemo(() =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<Flex justifyContent="end" pointer gap={ 1 } className="nitro-purse-button rounded">
|
||||||
|
<Text truncate textEnd variant="white" grow>{ short ? LocalizeShortNumber(amount) : LocalizeFormattedNumber(amount) }</Text>
|
||||||
|
<CurrencyIcon type={ type } />
|
||||||
|
</Flex>);
|
||||||
|
}, [ amount, short, type ]);
|
||||||
|
|
||||||
|
if(!short) return element;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OverlayTrigger
|
||||||
|
placement="left"
|
||||||
|
overlay={
|
||||||
|
<Tooltip id={ `tooltip-${ type }` }>
|
||||||
|
{ LocalizeFormattedNumber(amount) }
|
||||||
|
</Tooltip>
|
||||||
|
}>
|
||||||
|
{ element }
|
||||||
|
</OverlayTrigger>
|
||||||
|
);
|
||||||
|
}
|
25
src/components/purse/views/SeasonalView.tsx
Normal file
25
src/components/purse/views/SeasonalView.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { LocalizeFormattedNumber, LocalizeText } from '../../../api';
|
||||||
|
import { Flex, Text } from '../../../common';
|
||||||
|
import { CurrencyIcon } from '../../../views/shared/currency-icon/CurrencyIcon';
|
||||||
|
|
||||||
|
interface SeasonalViewProps
|
||||||
|
{
|
||||||
|
type: number;
|
||||||
|
amount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SeasonalView: FC<SeasonalViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { type = -1, amount = -1 } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex justifyContent="between" className="nitro-purse-seasonal-currency p-2 rounded">
|
||||||
|
<Text variant="white">{ LocalizeText(`purse.seasonal.currency.${ type }`) }</Text>
|
||||||
|
<Flex gap={ 1 }>
|
||||||
|
<Text variant="white">{ LocalizeFormattedNumber(amount) }</Text>
|
||||||
|
<CurrencyIcon type={ type } />
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
18
src/components/right-side/RightSideView.tsx
Normal file
18
src/components/right-side/RightSideView.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { Column } from '../../common';
|
||||||
|
import { NotificationCenterView } from '../../views/notification-center/NotificationCenterView';
|
||||||
|
import { GroupRoomInformationView } from '../groups/views/room-information/GroupRoomInformationView';
|
||||||
|
import { PurseView } from '../purse/PurseView';
|
||||||
|
|
||||||
|
export const RightSideView: FC<{}> = props =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<div className="nitro-right-side">
|
||||||
|
<Column position="relative" gap={ 1 }>
|
||||||
|
<PurseView />
|
||||||
|
<GroupRoomInformationView />
|
||||||
|
<NotificationCenterView />
|
||||||
|
</Column>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,10 +1,11 @@
|
|||||||
import { IRoomSession, RoomEngineEvent, RoomId, RoomSessionEvent } from '@nitrots/nitro-renderer';
|
import { IRoomSession, RoomEngineEvent, RoomId, RoomSessionEvent } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useState } from 'react';
|
import { FC, useCallback, useState } from 'react';
|
||||||
import { GetRoomSession, SetActiveRoomId, StartRoomSession } from '../../api';
|
import { GetRoomSession, SetActiveRoomId, StartRoomSession } from '../../api';
|
||||||
|
import { Base } from '../../common';
|
||||||
import { useRoomEngineEvent } from '../../hooks/events/nitro/room/room-engine-event';
|
import { useRoomEngineEvent } from '../../hooks/events/nitro/room/room-engine-event';
|
||||||
import { useRoomSessionManagerEvent } from '../../hooks/events/nitro/session/room-session-manager-event';
|
import { useRoomSessionManagerEvent } from '../../hooks/events/nitro/session/room-session-manager-event';
|
||||||
import { TransitionAnimation, TransitionAnimationTypes } from '../../layout';
|
import { TransitionAnimation, TransitionAnimationTypes } from '../../layout';
|
||||||
import { RoomView } from '../room/RoomView';
|
import { RoomView } from '../../views/room/RoomView';
|
||||||
|
|
||||||
export const RoomHostView: FC<{}> = props =>
|
export const RoomHostView: FC<{}> = props =>
|
||||||
{
|
{
|
||||||
@ -51,9 +52,9 @@ export const RoomHostView: FC<{}> = props =>
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ !!roomSession } timeout={ 300 }>
|
<TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ !!roomSession } timeout={ 300 }>
|
||||||
<div className="nitro-room-host w-100 h-100">
|
<Base fit>
|
||||||
<RoomView roomSession={ roomSession } />
|
<RoomView roomSession={ roomSession } />
|
||||||
</div>
|
</Base>
|
||||||
</TransitionAnimation>
|
</TransitionAnimation>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { RoomObjectCategory } from '@nitrots/nitro-renderer';
|
import { RoomObjectCategory } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useEffect } from 'react';
|
import { FC, useEffect } from 'react';
|
||||||
import { GetRoomEngine, GetRoomSession } from '../../api';
|
import { GetRoomEngine, GetRoomSession } from '../../api';
|
||||||
|
import { Base, Flex } from '../../common';
|
||||||
import { ItemCountView } from '../../views/shared/item-count/ItemCountView';
|
import { ItemCountView } from '../../views/shared/item-count/ItemCountView';
|
||||||
import { ToolbarViewItems } from './common/ToolbarViewItems';
|
import { ToolbarViewItems } from './common/ToolbarViewItems';
|
||||||
|
|
||||||
@ -24,35 +25,15 @@ export const ToolbarMeView: FC<ToolbarMeViewProps> = props =>
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="d-flex nitro-toolbar-me px-1 py-2">
|
<Flex alignItems="center" className="nitro-toolbar-me p-2" gap={ 2 }>
|
||||||
<div className="navigation-items">
|
<Base pointer className="navigation-item icon icon-me-achievements" onClick={ () => handleToolbarItemClick(ToolbarViewItems.ACHIEVEMENTS_ITEM) }>
|
||||||
<div className="navigation-item">
|
|
||||||
<i className="icon icon-me-talents"></i>
|
|
||||||
</div>
|
|
||||||
<div className="navigation-item">
|
|
||||||
<i className="icon icon-me-helper-tool"></i>
|
|
||||||
</div>
|
|
||||||
<div className="navigation-item" onClick={ () => handleToolbarItemClick(ToolbarViewItems.ACHIEVEMENTS_ITEM) }>
|
|
||||||
<i className="icon icon-me-achievements"></i>
|
|
||||||
{ (unseenAchievementCount > 0) &&
|
{ (unseenAchievementCount > 0) &&
|
||||||
<ItemCountView count={ unseenAchievementCount } /> }
|
<ItemCountView count={ unseenAchievementCount } /> }
|
||||||
</div>
|
</Base>
|
||||||
<div className="navigation-item" onClick={ () => handleToolbarItemClick(ToolbarViewItems.PROFILE_ITEM) }>
|
<Base pointer className="navigation-item icon icon-me-profile" onClick={ () => handleToolbarItemClick(ToolbarViewItems.PROFILE_ITEM) } />
|
||||||
<i className="icon icon-me-profile"></i>
|
<Base pointer className="navigation-item icon icon-me-rooms" />
|
||||||
</div>
|
<Base pointer className="navigation-item icon icon-me-clothing" onClick={ () => handleToolbarItemClick(ToolbarViewItems.CLOTHING_ITEM) } />
|
||||||
<div className="navigation-item">
|
<Base pointer className="navigation-item icon icon-me-settings" onClick={ () => handleToolbarItemClick(ToolbarViewItems.SETTINGS_ITEM) } />
|
||||||
<i className="icon icon-me-rooms"></i>
|
</Flex>
|
||||||
</div>
|
|
||||||
<div className="navigation-item" onClick={ () => handleToolbarItemClick(ToolbarViewItems.CLOTHING_ITEM) }>
|
|
||||||
<i className="icon icon-me-clothing"></i>
|
|
||||||
</div>
|
|
||||||
<div className="navigation-item">
|
|
||||||
<i className="icon icon-me-forums"></i>
|
|
||||||
</div>
|
|
||||||
<div className="navigation-item" onClick={ () => handleToolbarItemClick(ToolbarViewItems.SETTINGS_ITEM) }>
|
|
||||||
<i className="icon icon-me-settings"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,16 @@
|
|||||||
.nitro-toolbar-container {
|
.nitro-toolbar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: $toolbar-height;
|
height: $toolbar-height;
|
||||||
z-index: $toolbar-zindex;
|
z-index: $toolbar-zindex;
|
||||||
|
|
||||||
.nitro-toolbar {
|
|
||||||
height: 100%;
|
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
background: rgba($dark, 0.95);
|
background: rgba($dark, 0.95);
|
||||||
box-shadow: inset 0px 5px lighten(rgba($dark, 0.6), 2.5),
|
box-shadow: inset 0px 5px lighten(rgba($dark, 0.6), 2.5),
|
||||||
inset 0 -4px darken(rgba($dark, 0.6), 4);
|
inset 0 -4px darken(rgba($dark, 0.6), 4);
|
||||||
|
|
||||||
#toolbar-chat-input-container {
|
|
||||||
margin: 0 10px;
|
|
||||||
|
|
||||||
@include media-breakpoint-down(sm) {
|
|
||||||
width: 0px;
|
|
||||||
height: 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.navigation-items {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
&.navigation-avatar {
|
|
||||||
border-right: 1px solid rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navigation-item {
|
.navigation-item {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
//margin: 0 1px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&.item-avatar {
|
&.item-avatar {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
@ -49,63 +23,45 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon,
|
&:hover {
|
||||||
&.item-avatar {
|
|
||||||
position: relative;
|
|
||||||
//transition: transform .2s ease-out;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&.active {
|
|
||||||
-webkit-transform: translate(-1px, -1px);
|
-webkit-transform: translate(-1px, -1px);
|
||||||
transform: translate(-1px, -1px);
|
transform: translate(-1px, -1px);
|
||||||
filter: drop-shadow(2px 2px 0 rgba($black, 0.8));
|
filter: drop-shadow(2px 2px 0 rgba($black, 0.8));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-image {
|
&.active,
|
||||||
pointer-events: none;
|
&:active {
|
||||||
}
|
-webkit-transform: translate(0px, 0px);
|
||||||
|
transform: translate(0px, 0px);
|
||||||
.chat-input-container {
|
filter: drop-shadow(2px 2px 0 rgba($black, 0.8));
|
||||||
left: 60px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nitro-toolbar-me-menu {
|
#toolbar-chat-input-container {
|
||||||
bottom: 77px;
|
|
||||||
left: 200px;
|
@include media-breakpoint-down(sm) {
|
||||||
|
width: 0px;
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitro-toolbar-me {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: 12px;
|
bottom: 60px;
|
||||||
|
left: 15px;
|
||||||
z-index: $toolbar-memenu-zindex;
|
z-index: $toolbar-memenu-zindex;
|
||||||
|
background: rgba(20, 20, 20, .95);
|
||||||
|
border: 1px solid #101010;
|
||||||
|
box-shadow: inset 2px 2px rgba(255, 255, 255, .1), inset -2px -2px rgba(255, 255, 255, .1);
|
||||||
|
border-radius: $border-radius;
|
||||||
|
|
||||||
.list-group {
|
.navigation-item {
|
||||||
.list-group-item {
|
transition: filter .2s ease-out;
|
||||||
min-width: 70px;
|
|
||||||
transition: all 0.3s;
|
|
||||||
font-size: 10px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
i {
|
|
||||||
filter: grayscale(1);
|
filter: grayscale(1);
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $cyan;
|
filter: grayscale(0) drop-shadow(2px 2px 0 rgba($black, 0.8));
|
||||||
text-decoration: underline;
|
|
||||||
|
|
||||||
i {
|
|
||||||
filter: grayscale(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.count {
|
|
||||||
top: 0px;
|
|
||||||
right: 5px;
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,45 +78,3 @@
|
|||||||
drop-shadow(-2px 1px 0 rgba($white, 1))
|
drop-shadow(-2px 1px 0 rgba($white, 1))
|
||||||
drop-shadow(0 -2px 0 rgba($white, 1));
|
drop-shadow(0 -2px 0 rgba($white, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.nitro-toolbar-me {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 65px;
|
|
||||||
left: 15px;
|
|
||||||
z-index: $toolbar-me-zindex;
|
|
||||||
background: rgba(20, 20, 20, .95);
|
|
||||||
border: 1px solid #101010;
|
|
||||||
box-shadow: inset 2px 2px rgba(255, 255, 255, .1), inset -2px -2px rgba(255, 255, 255, .1);
|
|
||||||
border-radius: $border-radius;
|
|
||||||
|
|
||||||
.navigation-items {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
&.navigation-avatar {
|
|
||||||
border-right: 1px solid rgba(0, 0, 0, .3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navigation-item {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 50px;
|
|
||||||
font-size: 11px;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
transition: filter .2s ease-out;
|
|
||||||
filter: grayscale(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.icon {
|
|
||||||
filter: grayscale(0) drop-shadow(2px 2px 0 rgba($black, 0.8));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Dispose, DropBounce, EaseOut, FigureUpdateEvent, JumpBy, Motions, NitroToolbarAnimateIconEvent, Queue, UserInfoDataParser, UserInfoEvent, Wait } from '@nitrots/nitro-renderer';
|
import { Dispose, DropBounce, EaseOut, FigureUpdateEvent, JumpBy, Motions, NitroToolbarAnimateIconEvent, Queue, UserInfoDataParser, UserInfoEvent, Wait } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useState } from 'react';
|
import { FC, useCallback, useState } from 'react';
|
||||||
import { CreateLinkEvent, GetRoomSession, GetRoomSessionManager, GetSessionDataManager, GetUserProfile, GoToDesktop, OpenMessengerChat } from '../../api';
|
import { CreateLinkEvent, GetRoomSession, GetRoomSessionManager, GetSessionDataManager, GetUserProfile, GoToDesktop, OpenMessengerChat } from '../../api';
|
||||||
|
import { Base, Flex } from '../../common';
|
||||||
import { AvatarEditorEvent, FriendsEvent, FriendsMessengerIconEvent, FriendsRequestCountEvent, InventoryEvent, NavigatorEvent, RoomWidgetCameraEvent } from '../../events';
|
import { AvatarEditorEvent, FriendsEvent, FriendsMessengerIconEvent, FriendsRequestCountEvent, InventoryEvent, NavigatorEvent, RoomWidgetCameraEvent } from '../../events';
|
||||||
import { AchievementsUIEvent, AchievementsUIUnseenCountEvent } from '../../events/achievements';
|
import { AchievementsUIEvent, AchievementsUIUnseenCountEvent } from '../../events/achievements';
|
||||||
import { UnseenItemTrackerUpdateEvent } from '../../events/inventory/UnseenItemTrackerUpdateEvent';
|
import { UnseenItemTrackerUpdateEvent } from '../../events/inventory/UnseenItemTrackerUpdateEvent';
|
||||||
@ -181,64 +182,47 @@ export const ToolbarView: FC<ToolbarViewProps> = props =>
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="nitro-toolbar-container">
|
<>
|
||||||
<TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ isMeExpanded } timeout={ 300 }>
|
<TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ isMeExpanded } timeout={ 300 }>
|
||||||
<ToolbarMeView unseenAchievementCount={ unseenAchievementCount } handleToolbarItemClick={ handleToolbarItemClick } />
|
<ToolbarMeView unseenAchievementCount={ unseenAchievementCount } handleToolbarItemClick={ handleToolbarItemClick } />
|
||||||
</TransitionAnimation>
|
</TransitionAnimation>
|
||||||
<div className="d-flex justify-content-between align-items-center nitro-toolbar py-1 px-3">
|
<Flex alignItems="center" justifyContent="between" gap={ 2 } className="nitro-toolbar py-1 px-3">
|
||||||
<div className="d-flex align-items-center">
|
<Flex gap={ 2 } alignItems="center">
|
||||||
<div className="navigation-items gap-2">
|
<Flex alignItems="center" gap={ 2 }>
|
||||||
<div 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) }>
|
||||||
<AvatarImageView figure={ userFigure } direction={ 2 } />
|
<AvatarImageView figure={ userFigure } direction={ 2 } />
|
||||||
{ (unseenAchievementCount > 0) &&
|
{ (unseenAchievementCount > 0) &&
|
||||||
<ItemCountView count={ unseenAchievementCount } /> }
|
<ItemCountView count={ unseenAchievementCount } /> }
|
||||||
</div>
|
</Flex>
|
||||||
{ isInRoom && (
|
{ isInRoom &&
|
||||||
<div className="navigation-item" onClick={ visitDesktop }>
|
<Base pointer className="navigation-item icon icon-habbo" onClick={ visitDesktop } /> }
|
||||||
<i className="icon icon-habbo"></i>
|
{ !isInRoom &&
|
||||||
</div>) }
|
<Base pointer className="navigation-item icon icon-house" onClick={ event => CreateLinkEvent('navigator/goto/home') } /> }
|
||||||
{ !isInRoom && (
|
<Base pointer className="navigation-item icon icon-rooms" onClick={ event => handleToolbarItemClick(ToolbarViewItems.NAVIGATOR_ITEM) } />
|
||||||
<div className="navigation-item" onClick={ event => CreateLinkEvent('navigator/goto/home') }>
|
<Base pointer className="navigation-item icon icon-catalog" onClick={ event => handleToolbarItemClick(ToolbarViewItems.CATALOG_ITEM) } />
|
||||||
<i className="icon icon-house"></i>
|
<Base pointer className="navigation-item icon icon-inventory" onClick={ event => handleToolbarItemClick(ToolbarViewItems.INVENTORY_ITEM) }>
|
||||||
</div>) }
|
|
||||||
<div className="navigation-item" onClick={ event => handleToolbarItemClick(ToolbarViewItems.NAVIGATOR_ITEM) }>
|
|
||||||
<i className="icon icon-rooms"></i>
|
|
||||||
</div>
|
|
||||||
<div className="navigation-item" onClick={ event => handleToolbarItemClick(ToolbarViewItems.CATALOG_ITEM) }>
|
|
||||||
<i className="icon icon-catalog"></i>
|
|
||||||
</div>
|
|
||||||
<div className="navigation-item" onClick={ event => handleToolbarItemClick(ToolbarViewItems.INVENTORY_ITEM) }>
|
|
||||||
<i className="icon icon-inventory"></i>
|
|
||||||
{ (unseenInventoryCount > 0) &&
|
{ (unseenInventoryCount > 0) &&
|
||||||
<ItemCountView count={ unseenInventoryCount } /> }
|
<ItemCountView count={ unseenInventoryCount } /> }
|
||||||
</div>
|
</Base>
|
||||||
{ isInRoom && (
|
{ isInRoom &&
|
||||||
<div className="navigation-item" onClick={ event => handleToolbarItemClick(ToolbarViewItems.CAMERA_ITEM) }>
|
<Base pointer className="navigation-item icon icon-camera" onClick={ event => handleToolbarItemClick(ToolbarViewItems.CAMERA_ITEM) } /> }
|
||||||
<i className="icon icon-camera"></i>
|
{ isMod &&
|
||||||
</div>) }
|
<Base pointer className="navigation-item icon icon-modtools" onClick={ event => handleToolbarItemClick(ToolbarViewItems.MOD_TOOLS_ITEM) } /> }
|
||||||
{ isMod && (
|
</Flex>
|
||||||
<div className="navigation-item" onClick={ event => handleToolbarItemClick(ToolbarViewItems.MOD_TOOLS_ITEM) }>
|
<Flex alignItems="center" id="toolbar-chat-input-container" />
|
||||||
<i className="icon icon-modtools"></i>
|
</Flex>
|
||||||
</div>) }
|
<Flex alignItems="center" gap={ 2 }>
|
||||||
</div>
|
<Flex gap={ 2 }>
|
||||||
<div id="toolbar-chat-input-container" className="d-flex align-items-center" />
|
<Base pointer className="navigation-item icon icon-friendall" onClick={ event => handleToolbarItemClick(ToolbarViewItems.FRIEND_LIST_ITEM) }>
|
||||||
</div>
|
|
||||||
<div className="d-flex align-items-center gap-2">
|
|
||||||
<div className="navigation-items gap-2">
|
|
||||||
<div className="navigation-item" onClick={ event => handleToolbarItemClick(ToolbarViewItems.FRIEND_LIST_ITEM) }>
|
|
||||||
<i className="icon icon-friendall"></i>
|
|
||||||
{ (unseenFriendRequestCount > 0) &&
|
{ (unseenFriendRequestCount > 0) &&
|
||||||
<ItemCountView count={ unseenFriendRequestCount } /> }
|
<ItemCountView count={ unseenFriendRequestCount } /> }
|
||||||
</div>
|
</Base>
|
||||||
{ ((chatIconType === CHAT_ICON_SHOWING) || (chatIconType === CHAT_ICON_UNREAD)) &&
|
{ ((chatIconType === CHAT_ICON_SHOWING) || (chatIconType === CHAT_ICON_UNREAD)) &&
|
||||||
<div className="navigation-item" onClick={ event => handleToolbarItemClick(ToolbarViewItems.FRIEND_CHAT_ITEM) }>
|
<Base pointer className={ `navigation-item icon icon-message ${ (chatIconType === CHAT_ICON_UNREAD) && 'is-unread' }` } onClick={ event => handleToolbarItemClick(ToolbarViewItems.FRIEND_CHAT_ITEM) } /> }
|
||||||
{ (chatIconType === CHAT_ICON_SHOWING) && <i className="icon icon-message" /> }
|
</Flex>
|
||||||
{ (chatIconType === CHAT_ICON_UNREAD) && <i className="icon icon-message is-unseen" /> }
|
<Base id="toolbar-friend-bar-container" className="d-none d-lg-block" />
|
||||||
</div> }
|
</Flex>
|
||||||
</div>
|
</Flex>
|
||||||
<div id="toolbar-friend-bar-container" className="d-none d-lg-block" />
|
</>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,3 @@
|
|||||||
export interface ToolbarViewProps
|
|
||||||
{
|
|
||||||
isInRoom: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ToolbarViewItems
|
export class ToolbarViewItems
|
||||||
{
|
{
|
||||||
public static NAVIGATOR_ITEM: string = 'TVI_NAVIGATOR_ITEM';
|
public static NAVIGATOR_ITEM: string = 'TVI_NAVIGATOR_ITEM';
|
||||||
|
106
src/components/user-profile/UserProfileVew.scss
Normal file
106
src/components/user-profile/UserProfileVew.scss
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
.user-profile {
|
||||||
|
width: 560px;
|
||||||
|
|
||||||
|
.content-area {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-container {
|
||||||
|
border-right: 1px solid gray;
|
||||||
|
|
||||||
|
.avatar-image {
|
||||||
|
left: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-friend {
|
||||||
|
margin: 5px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-container {
|
||||||
|
min-height: 50px;
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 0px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rooms-button-container {
|
||||||
|
border-top: 1px solid gray;
|
||||||
|
border-bottom: 1px solid gray;
|
||||||
|
padding: 1px;
|
||||||
|
|
||||||
|
.rooms-button {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
height: 100%;
|
||||||
|
text-decoration: underline;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.friends-container {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-groups {
|
||||||
|
height: 219px;
|
||||||
|
|
||||||
|
.profile-groups-item {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
border-color: $grid-border-color !important;
|
||||||
|
background-color: $grid-bg-color;
|
||||||
|
border: nth(map-values($border-widths), 2) solid;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: $grid-active-border-color !important;
|
||||||
|
background-color: $grid-active-bg-color !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
z-index: 1;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.relationships-container {
|
||||||
|
|
||||||
|
.relationship-container {
|
||||||
|
|
||||||
|
.relationship
|
||||||
|
{
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.advanced {
|
||||||
|
background-color: white;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relationship-text {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-image {
|
||||||
|
position: absolute;
|
||||||
|
width: 50px;
|
||||||
|
height: 80px;
|
||||||
|
right: 0;
|
||||||
|
margin-top: -60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.others-text {
|
||||||
|
margin-left: 20px;
|
||||||
|
height: 21px;
|
||||||
|
color: #939392;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
104
src/components/user-profile/UserProfileView.tsx
Normal file
104
src/components/user-profile/UserProfileView.tsx
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, UserCurrentBadgesComposer, UserCurrentBadgesEvent, UserProfileEvent, UserProfileParser, UserRelationshipsComposer } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useState } from 'react';
|
||||||
|
import { GetSessionDataManager, GetUserProfile, LocalizeText } from '../../api';
|
||||||
|
import { Column, Flex, Grid } from '../../common';
|
||||||
|
import { BatchUpdates, CreateMessageHook, SendMessageHook } from '../../hooks';
|
||||||
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout';
|
||||||
|
import { BadgesContainerView } from './views/BadgesContainerView';
|
||||||
|
import { FriendsContainerView } from './views/FriendsContainerView';
|
||||||
|
import { GroupsContainerView } from './views/GroupsContainerView';
|
||||||
|
import { UserContainerView } from './views/UserContainerView';
|
||||||
|
|
||||||
|
export const UserProfileView: FC<{}> = props =>
|
||||||
|
{
|
||||||
|
const [ userProfile, setUserProfile ] = useState<UserProfileParser>(null);
|
||||||
|
const [ userBadges, setUserBadges ] = useState<string[]>([]);
|
||||||
|
const [ userRelationships, setUserRelationships ] = useState<RelationshipStatusInfoMessageParser>(null);
|
||||||
|
|
||||||
|
const onClose = () =>
|
||||||
|
{
|
||||||
|
BatchUpdates(() =>
|
||||||
|
{
|
||||||
|
setUserProfile(null);
|
||||||
|
setUserBadges([]);
|
||||||
|
setUserRelationships(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const onLeaveGroup = useCallback(() =>
|
||||||
|
{
|
||||||
|
if(userProfile && userProfile.id === GetSessionDataManager().userId)
|
||||||
|
{
|
||||||
|
GetUserProfile(userProfile.id);
|
||||||
|
}
|
||||||
|
}, [ userProfile ]);
|
||||||
|
|
||||||
|
const onUserCurrentBadgesEvent = useCallback((event: UserCurrentBadgesEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!userProfile || (parser.userId !== userProfile.id)) return;
|
||||||
|
|
||||||
|
setUserBadges(parser.badges);
|
||||||
|
}, [ userProfile ]);
|
||||||
|
|
||||||
|
CreateMessageHook(UserCurrentBadgesEvent, onUserCurrentBadgesEvent);
|
||||||
|
|
||||||
|
const OnUserRelationshipsEvent = useCallback((event: RelationshipStatusInfoEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!userProfile || (parser.userId !== userProfile.id)) return;
|
||||||
|
|
||||||
|
setUserRelationships(parser);
|
||||||
|
}, [ userProfile ]);
|
||||||
|
|
||||||
|
CreateMessageHook(RelationshipStatusInfoEvent, OnUserRelationshipsEvent);
|
||||||
|
|
||||||
|
const onUserProfileEvent = useCallback((event: UserProfileEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(userProfile)
|
||||||
|
{
|
||||||
|
BatchUpdates(() =>
|
||||||
|
{
|
||||||
|
setUserProfile(null);
|
||||||
|
setUserBadges([]);
|
||||||
|
setUserRelationships(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setUserProfile(parser);
|
||||||
|
|
||||||
|
SendMessageHook(new UserCurrentBadgesComposer(parser.id));
|
||||||
|
SendMessageHook(new UserRelationshipsComposer(parser.id));
|
||||||
|
}, [ userProfile ]);
|
||||||
|
|
||||||
|
CreateMessageHook(UserProfileEvent, onUserProfileEvent);
|
||||||
|
|
||||||
|
if(!userProfile) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NitroCardView className="user-profile" simple>
|
||||||
|
<NitroCardHeaderView headerText={ LocalizeText('extendedprofile.caption') } onCloseClick={ onClose } />
|
||||||
|
<NitroCardContentView>
|
||||||
|
<Grid>
|
||||||
|
<Column size={ 7 } className="user-container">
|
||||||
|
<UserContainerView userProfile={ userProfile } />
|
||||||
|
<BadgesContainerView badges={ userBadges } />
|
||||||
|
</Column>
|
||||||
|
<Column size={ 5 }>
|
||||||
|
{
|
||||||
|
userRelationships && <FriendsContainerView relationships={userRelationships} friendsCount={userProfile.friendsCount} />
|
||||||
|
}
|
||||||
|
</Column>
|
||||||
|
</Grid>
|
||||||
|
<Flex alignItems="center" className="rooms-button-container">
|
||||||
|
<i className="icon icon-rooms" /><span className="rooms-button">{LocalizeText('extendedprofile.rooms')}</span>
|
||||||
|
</Flex>
|
||||||
|
<GroupsContainerView itsMe={ userProfile.id === GetSessionDataManager().userId } groups={ userProfile.groups } onLeaveGroup={ onLeaveGroup } />
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView>
|
||||||
|
)
|
||||||
|
}
|
@ -1,7 +1,11 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { NitroCardGridItemView, NitroCardGridView } from '../../../../layout';
|
import { NitroCardGridItemView, NitroCardGridView } from '../../../layout';
|
||||||
import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView';
|
import { BadgeImageView } from '../../../views/shared/badge-image/BadgeImageView';
|
||||||
import { BadgesContainerViewProps } from './BadgesContainerView.types';
|
|
||||||
|
interface BadgesContainerViewProps
|
||||||
|
{
|
||||||
|
badges: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export const BadgesContainerView: FC<BadgesContainerViewProps> = props =>
|
export const BadgesContainerView: FC<BadgesContainerViewProps> = props =>
|
||||||
{
|
{
|
@ -1,7 +1,13 @@
|
|||||||
|
import { RelationshipStatusInfoMessageParser } from '@nitrots/nitro-renderer';
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { LocalizeText } from '../../../../api';
|
import { LocalizeText } from '../../../api';
|
||||||
import { RelationshipsContainerView } from '../relationships-container/RelationshipsContainerView';
|
import { RelationshipsContainerView } from './RelationshipsContainerView';
|
||||||
import { FriendsContainerViewProps } from './FriendsContainerView.types';
|
|
||||||
|
interface FriendsContainerViewProps
|
||||||
|
{
|
||||||
|
relationships: RelationshipStatusInfoMessageParser;
|
||||||
|
friendsCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
export const FriendsContainerView: FC<FriendsContainerViewProps> = props =>
|
export const FriendsContainerView: FC<FriendsContainerViewProps> = props =>
|
||||||
{
|
{
|
@ -1,10 +1,16 @@
|
|||||||
import { GroupFavoriteComposer, GroupInformationComposer, GroupInformationEvent, GroupInformationParser } from '@nitrots/nitro-renderer';
|
import { GroupFavoriteComposer, GroupInformationComposer, GroupInformationEvent, GroupInformationParser, HabboGroupEntryData } from '@nitrots/nitro-renderer';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { FC, useCallback, useEffect, useState } from 'react';
|
import { FC, useCallback, useEffect, useState } from 'react';
|
||||||
import { GroupInformationView } from '../../../../components/groups/views/information/GroupInformationView';
|
import { CreateMessageHook, SendMessageHook } from '../../../hooks';
|
||||||
import { CreateMessageHook, SendMessageHook } from '../../../../hooks';
|
import { BadgeImageView } from '../../../views/shared/badge-image/BadgeImageView';
|
||||||
import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView';
|
import { GroupInformationView } from '../../groups/views/information/GroupInformationView';
|
||||||
import { GroupsContainerViewProps } from './GroupsContainerView.types';
|
|
||||||
|
interface GroupsContainerViewProps
|
||||||
|
{
|
||||||
|
itsMe: boolean;
|
||||||
|
groups: HabboGroupEntryData[];
|
||||||
|
onLeaveGroup: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
export const GroupsContainerView: FC<GroupsContainerViewProps> = props =>
|
export const GroupsContainerView: FC<GroupsContainerViewProps> = props =>
|
||||||
{
|
{
|
@ -1,8 +1,13 @@
|
|||||||
import { RelationshipStatusEnum, RelationshipStatusInfo } from '@nitrots/nitro-renderer';
|
import { RelationshipStatusEnum, RelationshipStatusInfo, RelationshipStatusInfoMessageParser } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback } from 'react';
|
import { FC, useCallback } from 'react';
|
||||||
import { GetUserProfile, LocalizeText } from '../../../../api';
|
import { GetUserProfile, LocalizeText } from '../../../api';
|
||||||
import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView';
|
import { AvatarImageView } from '../../../views/shared/avatar-image/AvatarImageView';
|
||||||
import { RelationshipsContainerViewProps } from './RelationshipsContainerView.types';
|
|
||||||
|
interface RelationshipsContainerViewProps
|
||||||
|
{
|
||||||
|
relationships: RelationshipStatusInfoMessageParser;
|
||||||
|
simple?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export const RelationshipsContainerView: FC<RelationshipsContainerViewProps> = props =>
|
export const RelationshipsContainerView: FC<RelationshipsContainerViewProps> = props =>
|
||||||
{
|
{
|
@ -1,8 +1,12 @@
|
|||||||
import { FriendlyTime } from '@nitrots/nitro-renderer';
|
import { FriendlyTime, UserProfileParser } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback } from 'react';
|
import { FC, useCallback } from 'react';
|
||||||
import { GetSessionDataManager, LocalizeText } from '../../../../api';
|
import { GetSessionDataManager, LocalizeText } from '../../../api';
|
||||||
import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView';
|
import { AvatarImageView } from '../../../views/shared/avatar-image/AvatarImageView';
|
||||||
import { UserContainerViewProps } from './UserContainerView.types';
|
|
||||||
|
interface UserContainerViewProps
|
||||||
|
{
|
||||||
|
userProfile: UserProfileParser;
|
||||||
|
}
|
||||||
|
|
||||||
export const UserContainerView: FC<UserContainerViewProps> = props =>
|
export const UserContainerView: FC<UserContainerViewProps> = props =>
|
||||||
{
|
{
|
@ -1,8 +0,0 @@
|
|||||||
import { NitroEvent } from '@nitrots/nitro-renderer';
|
|
||||||
|
|
||||||
export class ChatHistoryEvent extends NitroEvent
|
|
||||||
{
|
|
||||||
public static SHOW_CHAT_HISTORY: string = 'CHE_SHOW_CHAT_HISTORY';
|
|
||||||
public static HIDE_CHAT_HISTORY: string = 'CHE_HIDE_CHAT_HISTORY';
|
|
||||||
public static TOGGLE_CHAT_HISTORY: string = 'CHE_TOGGLE_CHAT_HISTORY';
|
|
||||||
}
|
|
@ -9,6 +9,7 @@ body {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
image-rendering: -moz-crisp-edges;
|
image-rendering: -moz-crisp-edges;
|
||||||
|
scrollbar-width: thin;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import { FC, useMemo } from 'react';
|
import { FC, useMemo } from 'react';
|
||||||
import { Column } from '../../../common/Column';
|
import { Column, ColumnProps } from '../../../common';
|
||||||
import { useNitroCardContext } from '../context';
|
import { useNitroCardContext } from '../context';
|
||||||
import { NitroCardContentViewProps } from './NitroCardContextView.types';
|
|
||||||
|
|
||||||
export const NitroCardContentView: FC<NitroCardContentViewProps> = props =>
|
export const NitroCardContentView: FC<ColumnProps> = props =>
|
||||||
{
|
{
|
||||||
const { theme = 'primary', classNames = [], ...rest } = props;
|
const { classNames = [], ...rest } = props;
|
||||||
const { simple = false } = useNitroCardContext();
|
const { theme = 'primary', simple = false } = useNitroCardContext();
|
||||||
|
|
||||||
const getClassNames = useMemo(() =>
|
const getClassNames = useMemo(() =>
|
||||||
{
|
{
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { ColumnProps } from '../../../common/Column';
|
|
||||||
|
|
||||||
|
|
||||||
export interface NitroCardContentViewProps extends ColumnProps
|
|
||||||
{
|
|
||||||
theme?: string;
|
|
||||||
}
|
|
@ -1,2 +1 @@
|
|||||||
export * from './NitroCardContentView';
|
export * from './NitroCardContentView';
|
||||||
export * from './NitroCardContextView.types';
|
|
||||||
|
@ -5,8 +5,8 @@ import { NitroCardHeaderViewProps } from './NitroCardHeaderView.types';
|
|||||||
|
|
||||||
export const NitroCardHeaderView: FC<NitroCardHeaderViewProps> = props =>
|
export const NitroCardHeaderView: FC<NitroCardHeaderViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { headerText = null, onCloseClick = null, theme = 'primary' } = props;
|
const { headerText = null, onCloseClick = null } = props;
|
||||||
const { simple = false } = useNitroCardContext();
|
const { theme = 'primary', simple = false } = useNitroCardContext();
|
||||||
|
|
||||||
const onMouseDown = useCallback((event: MouseEvent<HTMLDivElement>) =>
|
const onMouseDown = useCallback((event: MouseEvent<HTMLDivElement>) =>
|
||||||
{
|
{
|
||||||
|
@ -1,16 +1,8 @@
|
|||||||
@import "./shared/Shared";
|
@import "./shared/Shared";
|
||||||
@import "./friends/FriendsView";
|
@import "./friends/FriendsView";
|
||||||
@import "./hotel-view/HotelView";
|
@import "./hotel-view/HotelView";
|
||||||
@import "./loading/LoadingView";
|
|
||||||
@import "./main/MainView";
|
|
||||||
@import "./notification-center/NotificationCenterView";
|
@import "./notification-center/NotificationCenterView";
|
||||||
@import "./purse/PurseView";
|
|
||||||
@import "./right-side/RightSideView";
|
|
||||||
@import "./room/RoomView";
|
@import "./room/RoomView";
|
||||||
@import "./room-host/RoomHostView";
|
|
||||||
@import "./mod-tools/ModToolsView";
|
|
||||||
@import "./user-profile/UserProfileVew";
|
|
||||||
@import "./chat-history/ChatHistoryView";
|
|
||||||
@import "./floorplan-editor/FloorplanEditorView";
|
@import "./floorplan-editor/FloorplanEditorView";
|
||||||
@import "./nitropedia/NitropediaView";
|
@import "./nitropedia/NitropediaView";
|
||||||
@import "./hc-center/HcCenterView.scss";
|
@import "./hc-center/HcCenterView.scss";
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
.nitro-chat-history {
|
|
||||||
width: $chat-history-width;
|
|
||||||
height: $chat-history-height;
|
|
||||||
|
|
||||||
background-color: #1C323F;
|
|
||||||
border: 2px solid rgba(255, 255, 255, 0.5);
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
|
|
||||||
.nitro-card-header-container {
|
|
||||||
background-color: #3d5f6e;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-history-content {
|
|
||||||
.chat-history-container {
|
|
||||||
min-height: 200px;
|
|
||||||
|
|
||||||
.chat-history-list {
|
|
||||||
.chathistory-entry {
|
|
||||||
.light {
|
|
||||||
background-color: #121f27;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
background-color: #0d171d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,167 +0,0 @@
|
|||||||
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
||||||
import { AutoSizer, CellMeasurer, CellMeasurerCache, List, ListRowProps, ListRowRenderer, Size } from 'react-virtualized';
|
|
||||||
import { RenderedRows } from 'react-virtualized/dist/es/List';
|
|
||||||
import { ChatHistoryEvent } from '../../events/chat-history/ChatHistoryEvent';
|
|
||||||
import { useUiEvent } from '../../hooks';
|
|
||||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout';
|
|
||||||
import { ChatHistoryMessageHandler } from './ChatHistoryMessageHandler';
|
|
||||||
import { ChatHistoryState } from './common/ChatHistoryState';
|
|
||||||
import { SetChatHistory } from './common/GetChatHistory';
|
|
||||||
import { RoomHistoryState } from './common/RoomHistoryState';
|
|
||||||
import { ChatHistoryContextProvider } from './context/ChatHistoryContext';
|
|
||||||
import { ChatEntryType } from './context/ChatHistoryContext.types';
|
|
||||||
|
|
||||||
export const ChatHistoryView: FC<{}> = props =>
|
|
||||||
{
|
|
||||||
const [ isVisible, setIsVisible ] = useState(false);
|
|
||||||
const [ needsScroll, setNeedsScroll ] = useState(false);
|
|
||||||
const [ chatHistoryUpdateId, setChatHistoryUpdateId ] = useState(-1);
|
|
||||||
const [ roomHistoryUpdateId, setRoomHistoryUpdateId ] = useState(-1);
|
|
||||||
const [ chatHistoryState, setChatHistoryState ] = useState(new ChatHistoryState());
|
|
||||||
const [ roomHistoryState, setRoomHistoryState ] = useState(new RoomHistoryState());
|
|
||||||
const elementRef = useRef<List>(null);
|
|
||||||
|
|
||||||
useEffect(() =>
|
|
||||||
{
|
|
||||||
const chatState = new ChatHistoryState();
|
|
||||||
const roomState = new RoomHistoryState();
|
|
||||||
|
|
||||||
SetChatHistory(chatState);
|
|
||||||
|
|
||||||
chatState.notifier = () => setChatHistoryUpdateId(prevValue => (prevValue + 1));
|
|
||||||
roomState.notifier = () => setRoomHistoryUpdateId(prevValue => (prevValue + 1));
|
|
||||||
|
|
||||||
setChatHistoryState(chatState);
|
|
||||||
setRoomHistoryState(roomState);
|
|
||||||
|
|
||||||
return () => {chatState.notifier = null; roomState.notifier = null;};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onChatHistoryEvent = useCallback((event: ChatHistoryEvent) =>
|
|
||||||
{
|
|
||||||
switch(event.type)
|
|
||||||
{
|
|
||||||
case ChatHistoryEvent.SHOW_CHAT_HISTORY:
|
|
||||||
setIsVisible(true);
|
|
||||||
break;
|
|
||||||
case ChatHistoryEvent.HIDE_CHAT_HISTORY:
|
|
||||||
setIsVisible(false);
|
|
||||||
break;
|
|
||||||
case ChatHistoryEvent.TOGGLE_CHAT_HISTORY:
|
|
||||||
setIsVisible(!isVisible);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}, [isVisible]);
|
|
||||||
|
|
||||||
useUiEvent(ChatHistoryEvent.HIDE_CHAT_HISTORY, onChatHistoryEvent);
|
|
||||||
useUiEvent(ChatHistoryEvent.SHOW_CHAT_HISTORY, onChatHistoryEvent);
|
|
||||||
useUiEvent(ChatHistoryEvent.TOGGLE_CHAT_HISTORY, onChatHistoryEvent);
|
|
||||||
|
|
||||||
const cache = useMemo(() =>
|
|
||||||
{
|
|
||||||
return new CellMeasurerCache({
|
|
||||||
defaultHeight: 25,
|
|
||||||
fixedWidth: true,
|
|
||||||
//keyMapper: (index) => chatHistoryState.chats[index].id
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const RowRenderer: ListRowRenderer = (props: ListRowProps) =>
|
|
||||||
{
|
|
||||||
const item = chatHistoryState.chats[props.index];
|
|
||||||
|
|
||||||
const isDark = (props.index % 2 === 0);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CellMeasurer
|
|
||||||
cache={cache}
|
|
||||||
columnIndex={0}
|
|
||||||
key={props.key}
|
|
||||||
parent={props.parent}
|
|
||||||
rowIndex={props.index}
|
|
||||||
>
|
|
||||||
<div key={props.key} style={props.style} className="chathistory-entry justify-content-start">
|
|
||||||
{(item.type === ChatEntryType.TYPE_CHAT) &&
|
|
||||||
<div className={`p-1 d-flex gap-1 ${isDark ? 'dark' : 'light'}`}>
|
|
||||||
<div className="text-muted">{item.timestamp}</div>
|
|
||||||
<div className="cursor-pointer d-flex text-nowrap" dangerouslySetInnerHTML={ { __html: (item.name + ':') }} />
|
|
||||||
<div className="text-break text-wrap flex-grow-1">{item.message}</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
{(item.type === ChatEntryType.TYPE_ROOM_INFO) &&
|
|
||||||
<div className={`p-1 d-flex gap-1 ${isDark ? 'dark' : 'light'}`}>
|
|
||||||
<div className="text-muted">{item.timestamp}</div>
|
|
||||||
<i className="icon icon-small-room" />
|
|
||||||
<div className="cursor-pointer text-break text-wrap">{item.name}</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</CellMeasurer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onResize = useCallback((info: Size) =>
|
|
||||||
{
|
|
||||||
cache.clearAll();
|
|
||||||
}, [cache]);
|
|
||||||
|
|
||||||
const onRowsRendered = useCallback((info: RenderedRows) =>
|
|
||||||
{
|
|
||||||
if(elementRef && elementRef.current && isVisible && needsScroll)
|
|
||||||
{
|
|
||||||
console.log('stop ' + info.stopIndex);
|
|
||||||
//if(chatHistoryState.chats.length > 0) elementRef.current.measureAllRows();
|
|
||||||
elementRef.current.scrollToRow(chatHistoryState.chats.length);
|
|
||||||
console.log('scroll')
|
|
||||||
setNeedsScroll(false);
|
|
||||||
}
|
|
||||||
}, [chatHistoryState.chats.length, isVisible, needsScroll]);
|
|
||||||
|
|
||||||
useEffect(() =>
|
|
||||||
{
|
|
||||||
|
|
||||||
if(elementRef && elementRef.current && isVisible)
|
|
||||||
{
|
|
||||||
//if(chatHistoryState.chats.length > 0) elementRef.current.measureAllRows();
|
|
||||||
elementRef.current.scrollToRow(chatHistoryState.chats.length);
|
|
||||||
}
|
|
||||||
//console.log(chatHistoryState.chats.length);
|
|
||||||
|
|
||||||
setNeedsScroll(true);
|
|
||||||
}, [chatHistoryState.chats, isVisible, chatHistoryUpdateId]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ChatHistoryContextProvider value={ { chatHistoryState, roomHistoryState } }>
|
|
||||||
<ChatHistoryMessageHandler />
|
|
||||||
{isVisible &&
|
|
||||||
<NitroCardView uniqueKey="chat-history" className="nitro-chat-history" simple={ false } theme={'dark'} >
|
|
||||||
<NitroCardHeaderView headerText={ 'Chat History' } onCloseClick={ event => setIsVisible(false) } theme={'dark'}/>
|
|
||||||
<NitroCardContentView className="chat-history-content p-0" theme={'dark'}>
|
|
||||||
<div className="row w-100 h-100 chat-history-container">
|
|
||||||
<AutoSizer defaultWidth={300} defaultHeight={200} onResize={onResize}>
|
|
||||||
{({ height, width }) =>
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
<List
|
|
||||||
ref={elementRef}
|
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
rowCount={chatHistoryState.chats.length}
|
|
||||||
rowHeight={cache.rowHeight}
|
|
||||||
className={'chat-history-list'}
|
|
||||||
rowRenderer={RowRenderer}
|
|
||||||
onRowsRendered={onRowsRendered}
|
|
||||||
deferredMeasurementCache={cache}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</AutoSizer>
|
|
||||||
</div>
|
|
||||||
</NitroCardContentView>
|
|
||||||
</NitroCardView>
|
|
||||||
}
|
|
||||||
</ChatHistoryContextProvider>
|
|
||||||
);
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user