From ce7e6281dfb4104bdf2d60a5a9991f9717e5398f Mon Sep 17 00:00:00 2001 From: Bill Date: Sat, 25 Sep 2021 02:53:15 -0400 Subject: [PATCH 01/73] More inventory updates --- .../inventory/views/badge/InventoryBadgeView.tsx | 4 ++-- src/views/inventory/views/bot/InventoryBotView.tsx | 4 ++-- .../views/furniture/InventoryFurnitureView.tsx | 4 ++-- src/views/inventory/views/pet/InventoryPetView.tsx | 4 ++-- .../inventory/views/trade/InventoryTradeView.tsx | 14 +++++++------- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/views/inventory/views/badge/InventoryBadgeView.tsx b/src/views/inventory/views/badge/InventoryBadgeView.tsx index 47f2fe62..506db989 100644 --- a/src/views/inventory/views/badge/InventoryBadgeView.tsx +++ b/src/views/inventory/views/badge/InventoryBadgeView.tsx @@ -80,10 +80,10 @@ export const InventoryBadgeView: FC = props => return ( - + - + { LocalizeText('inventory.badges.activebadges') } diff --git a/src/views/inventory/views/bot/InventoryBotView.tsx b/src/views/inventory/views/bot/InventoryBotView.tsx index 59c02571..743d0c55 100644 --- a/src/views/inventory/views/bot/InventoryBotView.tsx +++ b/src/views/inventory/views/bot/InventoryBotView.tsx @@ -72,10 +72,10 @@ export const InventoryBotView: FC = props => return ( - + - + diff --git a/src/views/inventory/views/furniture/InventoryFurnitureView.tsx b/src/views/inventory/views/furniture/InventoryFurnitureView.tsx index 05e3afb7..bdddaf0f 100644 --- a/src/views/inventory/views/furniture/InventoryFurnitureView.tsx +++ b/src/views/inventory/views/furniture/InventoryFurnitureView.tsx @@ -108,11 +108,11 @@ export const InventoryFurnitureView: FC = props => return ( - + - + { groupItem && groupItem.stuffData.isUnique && diff --git a/src/views/inventory/views/pet/InventoryPetView.tsx b/src/views/inventory/views/pet/InventoryPetView.tsx index b9949c95..095e759b 100644 --- a/src/views/inventory/views/pet/InventoryPetView.tsx +++ b/src/views/inventory/views/pet/InventoryPetView.tsx @@ -71,10 +71,10 @@ export const InventoryPetView: FC = props => return ( - + - + diff --git a/src/views/inventory/views/trade/InventoryTradeView.tsx b/src/views/inventory/views/trade/InventoryTradeView.tsx index f376e890..0d7f9797 100644 --- a/src/views/inventory/views/trade/InventoryTradeView.tsx +++ b/src/views/inventory/views/trade/InventoryTradeView.tsx @@ -229,10 +229,10 @@ export const InventoryTradeView: FC = props => return ( - + - + { filteredGroupItems && (filteredGroupItems.length > 0) && filteredGroupItems.map((item, index) => { const count = item.getUnlockedCount(); @@ -252,12 +252,12 @@ export const InventoryTradeView: FC = props => { groupItem ? groupItem.name : LocalizeText('catalog_selectproduct') } - + - + { LocalizeText('inventory.trading.you') } { LocalizeText('inventory.trading.areoffering') }: - + { Array.from(Array(MAX_ITEMS_TO_TRADE), (e, i) => { const item = (tradeData.ownUser.items.getWithIndex(i) || null); @@ -279,10 +279,10 @@ export const InventoryTradeView: FC = props => { ownGroupItem ? ownGroupItem.name : LocalizeText('catalog_selectproduct') } - + { tradeData.otherUser.userName } { LocalizeText('inventory.trading.isoffering') }: - + { Array.from(Array(MAX_ITEMS_TO_TRADE), (e, i) => { const item = (tradeData.otherUser.items.getWithIndex(i) || null); From 8581ea99b97194dd3a63181fdceb4d44f270e1dc Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 27 Sep 2021 00:14:33 -0400 Subject: [PATCH 02/73] Chat fix for clearing --- src/views/room/widgets/chat/ChatWidgetView.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/views/room/widgets/chat/ChatWidgetView.tsx b/src/views/room/widgets/chat/ChatWidgetView.tsx index 30d67d04..6dab7b50 100644 --- a/src/views/room/widgets/chat/ChatWidgetView.tsx +++ b/src/views/room/widgets/chat/ChatWidgetView.tsx @@ -49,8 +49,10 @@ export const ChatWidgetView: FC<{}> = props => moveChatUp(existingChat, amount); }); + + removeHiddenChats(); } - }, [ chatMessages, moveChatUp ]); + }, [ chatMessages, moveChatUp, removeHiddenChats ]); const addChat = useCallback((chat: ChatBubbleMessage) => { From 3d6c5e8c978df1d0f779f4b7c5e4d996395d78e9 Mon Sep 17 00:00:00 2001 From: Bill Date: Wed, 29 Sep 2021 22:20:06 -0400 Subject: [PATCH 03/73] Update layout --- src/layout/base/NitroLayoutBase.types.ts | 4 ++-- .../button-group/NitroLayoutButtonGroup.tsx | 19 +++++++++++++++++ .../NitroLayoutButtonGroup.types.ts | 6 ++++++ src/layout/button-group/index.ts | 2 ++ src/layout/card/grid/NitroCardGridView.tsx | 2 +- .../card/grid/item/NitroCardGridItemView.tsx | 4 ++-- .../grid/item/NitroCardGridItemView.types.ts | 1 + .../card/header/NitroCardHeaderView.tsx | 4 ++-- src/layout/card/index.ts | 1 + .../sub-header/NitroCardSubHeaderView.tsx | 21 +++++++++++++++++++ .../NitroCardSubHeaderView.types.ts | 6 ++++++ src/layout/card/sub-header/index.ts | 2 ++ src/layout/common/NitroLayoutVariant.type.ts | 2 +- .../NitroLayoutGiftCardView.types.ts | 2 +- .../grid/column/NitroLayoutGridColumn.tsx | 6 +++--- src/layout/index.ts | 1 + 16 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 src/layout/button-group/NitroLayoutButtonGroup.tsx create mode 100644 src/layout/button-group/NitroLayoutButtonGroup.types.ts create mode 100644 src/layout/button-group/index.ts create mode 100644 src/layout/card/sub-header/NitroCardSubHeaderView.tsx create mode 100644 src/layout/card/sub-header/NitroCardSubHeaderView.types.ts create mode 100644 src/layout/card/sub-header/index.ts diff --git a/src/layout/base/NitroLayoutBase.types.ts b/src/layout/base/NitroLayoutBase.types.ts index fe2219fa..846ddaf2 100644 --- a/src/layout/base/NitroLayoutBase.types.ts +++ b/src/layout/base/NitroLayoutBase.types.ts @@ -1,7 +1,7 @@ -import { ButtonHTMLAttributes, DetailedHTMLProps } from 'react'; +import { DetailedHTMLProps, HTMLAttributes } from 'react'; import { NitroLayoutOverflow, NitroLayoutPosition, NitroLayoutSpacing } from '../common'; -export interface NitroLayoutBaseProps extends DetailedHTMLProps, HTMLDivElement> +export interface NitroLayoutBaseProps extends DetailedHTMLProps, HTMLDivElement> { overflow?: NitroLayoutOverflow; position?: NitroLayoutPosition; diff --git a/src/layout/button-group/NitroLayoutButtonGroup.tsx b/src/layout/button-group/NitroLayoutButtonGroup.tsx new file mode 100644 index 00000000..7617d07a --- /dev/null +++ b/src/layout/button-group/NitroLayoutButtonGroup.tsx @@ -0,0 +1,19 @@ +import { FC, useMemo } from 'react'; +import { NitroLayoutBase } from '../base'; +import { NitroLayoutButtonGroupProps } from './NitroLayoutButtonGroup.types'; + +export const NitroLayoutButtonGroup: FC = props => +{ + const { className = '', ...rest } = props; + + const getClassName = useMemo(() => + { + let newClassName = 'btn-group'; + + if(className && className.length) newClassName += ` ${ className }`; + + return newClassName; + }, [ className ]); + + return ; +} diff --git a/src/layout/button-group/NitroLayoutButtonGroup.types.ts b/src/layout/button-group/NitroLayoutButtonGroup.types.ts new file mode 100644 index 00000000..8e428d0e --- /dev/null +++ b/src/layout/button-group/NitroLayoutButtonGroup.types.ts @@ -0,0 +1,6 @@ +import { NitroLayoutBaseProps } from '../base'; + +export interface NitroLayoutButtonGroupProps extends NitroLayoutBaseProps +{ + +} diff --git a/src/layout/button-group/index.ts b/src/layout/button-group/index.ts new file mode 100644 index 00000000..23d85cc8 --- /dev/null +++ b/src/layout/button-group/index.ts @@ -0,0 +1,2 @@ +export * from './NitroLayoutButtonGroup'; +export * from './NitroLayoutButtonGroup.types'; diff --git a/src/layout/card/grid/NitroCardGridView.tsx b/src/layout/card/grid/NitroCardGridView.tsx index 97c17237..0eead8f5 100644 --- a/src/layout/card/grid/NitroCardGridView.tsx +++ b/src/layout/card/grid/NitroCardGridView.tsx @@ -20,7 +20,7 @@ export const NitroCardGridView: FC = props => if(columns && (columns >= 1)) { - newStyle['grid-template-columns'] = 'unset'; + newStyle.gridTemplateColumns = 'unset'; newStyle['--bs-columns'] = columns.toString(); } diff --git a/src/layout/card/grid/item/NitroCardGridItemView.tsx b/src/layout/card/grid/item/NitroCardGridItemView.tsx index 3bc431ef..6c1e4886 100644 --- a/src/layout/card/grid/item/NitroCardGridItemView.tsx +++ b/src/layout/card/grid/item/NitroCardGridItemView.tsx @@ -4,7 +4,7 @@ import { NitroCardGridItemViewProps } from './NitroCardGridItemView.types'; export const NitroCardGridItemView: FC = props => { - const { itemImage = undefined, itemColor = undefined, itemActive = false, itemCount = 1, itemUniqueNumber = -2, itemUnseen = false, className = '', style = {}, children = null, ...rest } = props; + const { itemImage = undefined, itemColor = undefined, itemActive = false, itemCount = 1, itemCountMinimum = 1, itemUniqueNumber = -2, itemUnseen = false, className = '', style = {}, children = null, ...rest } = props; const getClassName = useMemo(() => { @@ -38,7 +38,7 @@ export const NitroCardGridItemView: FC = props => return (
- { (itemCount > 1) && + { (itemCount > itemCountMinimum) && { itemCount } } { (itemUniqueNumber > 0) &&
diff --git a/src/layout/card/grid/item/NitroCardGridItemView.types.ts b/src/layout/card/grid/item/NitroCardGridItemView.types.ts index 97861569..3aa21515 100644 --- a/src/layout/card/grid/item/NitroCardGridItemView.types.ts +++ b/src/layout/card/grid/item/NitroCardGridItemView.types.ts @@ -6,6 +6,7 @@ export interface NitroCardGridItemViewProps extends DetailsHTMLAttributes = props => { return (
-
+
{ headerText }
@@ -31,7 +31,7 @@ export const NitroCardHeaderView: FC = props => return (
-
+
{ headerText }
diff --git a/src/layout/card/index.ts b/src/layout/card/index.ts index 9191b143..c12c5247 100644 --- a/src/layout/card/index.ts +++ b/src/layout/card/index.ts @@ -5,4 +5,5 @@ export * from './grid'; export * from './header'; export * from './NitroCardView'; export * from './NitroCardView.types'; +export * from './sub-header'; export * from './tabs'; diff --git a/src/layout/card/sub-header/NitroCardSubHeaderView.tsx b/src/layout/card/sub-header/NitroCardSubHeaderView.tsx new file mode 100644 index 00000000..e8e24ea3 --- /dev/null +++ b/src/layout/card/sub-header/NitroCardSubHeaderView.tsx @@ -0,0 +1,21 @@ +import { FC, useMemo } from 'react'; +import { NitroLayoutFlex } from '../..'; +import { NitroCardSubHeaderViewProps } from './NitroCardSubHeaderView.types'; + +export const NitroCardSubHeaderView: FC = props => +{ + const { className = '', ...rest } = props; + + const getClassName = useMemo(() => + { + let newClassName = 'container-fluid bg-muted justify-content-center py-1'; + + if(className && className.length) newClassName += ` ${ className }`; + + return newClassName; + }, [ className ]); + + return ( + + ); +} diff --git a/src/layout/card/sub-header/NitroCardSubHeaderView.types.ts b/src/layout/card/sub-header/NitroCardSubHeaderView.types.ts new file mode 100644 index 00000000..b160cc93 --- /dev/null +++ b/src/layout/card/sub-header/NitroCardSubHeaderView.types.ts @@ -0,0 +1,6 @@ +import { NitroLayoutFlexProps } from '../../flex'; + +export interface NitroCardSubHeaderViewProps extends NitroLayoutFlexProps +{ + +} diff --git a/src/layout/card/sub-header/index.ts b/src/layout/card/sub-header/index.ts new file mode 100644 index 00000000..3597f89b --- /dev/null +++ b/src/layout/card/sub-header/index.ts @@ -0,0 +1,2 @@ +export * from './NitroCardSubHeaderView'; +export * from './NitroCardSubHeaderView.types'; diff --git a/src/layout/common/NitroLayoutVariant.type.ts b/src/layout/common/NitroLayoutVariant.type.ts index 69c4699a..cf973357 100644 --- a/src/layout/common/NitroLayoutVariant.type.ts +++ b/src/layout/common/NitroLayoutVariant.type.ts @@ -1 +1 @@ -export type NitroLayoutVariant = 'primary' | 'success' | 'danger'; +export type NitroLayoutVariant = 'primary' | 'success' | 'danger' | 'secondary'; diff --git a/src/layout/gift-card/NitroLayoutGiftCardView.types.ts b/src/layout/gift-card/NitroLayoutGiftCardView.types.ts index c530d65c..def9113b 100644 --- a/src/layout/gift-card/NitroLayoutGiftCardView.types.ts +++ b/src/layout/gift-card/NitroLayoutGiftCardView.types.ts @@ -1,6 +1,6 @@ export interface NitroLayoutGiftCardViewProps { - figure?:string; + figure?: string; userName?: string; message?: string; editable?: boolean; diff --git a/src/layout/grid/column/NitroLayoutGridColumn.tsx b/src/layout/grid/column/NitroLayoutGridColumn.tsx index f169ca09..dfa34267 100644 --- a/src/layout/grid/column/NitroLayoutGridColumn.tsx +++ b/src/layout/grid/column/NitroLayoutGridColumn.tsx @@ -4,16 +4,16 @@ import { NitroLayoutGridColumnProps } from './NitroLayoutGridColumn.types'; export const NitroLayoutGridColumn: FC = props => { - const { className = '', size = 12, gap = 3, ...rest } = props; + const { className = '', size = 12, gap = 2, overflow = 'auto', ...rest } = props; const getClassName = useMemo(() => { let newClassName = `g-col-${ size }`; - if(className && className.length) newClassName += ' ' + className; + if(className && className.length) newClassName += ` ${ className }`; return newClassName; }, [ className, size ]); - return + return } diff --git a/src/layout/index.ts b/src/layout/index.ts index 2859e2a3..2d721551 100644 --- a/src/layout/index.ts +++ b/src/layout/index.ts @@ -1,4 +1,5 @@ export * from './button'; +export * from './button-group'; export * from './card'; export * from './common'; export * from './draggable-window'; From 7959b6772eff00a1cd890ff30fa2c61b442aae6d Mon Sep 17 00:00:00 2001 From: Bill Date: Wed, 29 Sep 2021 22:20:27 -0400 Subject: [PATCH 04/73] Update assets --- src/assets/images/achievements/back-arrow.png | Bin 0 -> 331 bytes .../loading/connecting-duck-spritesheet.png | Bin 0 -> 34720 bytes .../images/loading/connecting_duck_01.png | Bin 0 -> 3985 bytes .../images/loading/connecting_duck_02.png | Bin 0 -> 4923 bytes .../images/loading/connecting_duck_03.png | Bin 0 -> 5842 bytes .../images/loading/connecting_duck_04.png | Bin 0 -> 3270 bytes .../images/loading/connecting_duck_05.png | Bin 0 -> 4558 bytes .../images/loading/connecting_duck_06.png | Bin 0 -> 5290 bytes .../images/loading/connecting_duck_07.png | Bin 0 -> 2731 bytes src/assets/styles/icons.scss | 6 ++++++ src/assets/styles/utils.scss | 8 ++++++-- 11 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 src/assets/images/achievements/back-arrow.png create mode 100644 src/assets/images/loading/connecting-duck-spritesheet.png create mode 100644 src/assets/images/loading/connecting_duck_01.png create mode 100644 src/assets/images/loading/connecting_duck_02.png create mode 100644 src/assets/images/loading/connecting_duck_03.png create mode 100644 src/assets/images/loading/connecting_duck_04.png create mode 100644 src/assets/images/loading/connecting_duck_05.png create mode 100644 src/assets/images/loading/connecting_duck_06.png create mode 100644 src/assets/images/loading/connecting_duck_07.png diff --git a/src/assets/images/achievements/back-arrow.png b/src/assets/images/achievements/back-arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..e795c0ee17a2c814a38a2162768cea21d1415327 GIT binary patch literal 331 zcmeAS@N?(olHy`uVBq!ia0vp^ia@Ny!3-pyt#kMcq!^2X+?^QKos)S9^9T5ZxB}^$H*fy`|39ay8OWaA{>2MOv6ck+1p@{DGyLAh7Yr2OEbxdd zW?urU&rLVW89&r!frI5JWMbB%utImyY{}>HFbLOx7 RSP$|ngQu&X%Q~loCIDqWec1p2 literal 0 HcmV?d00001 diff --git a/src/assets/images/loading/connecting-duck-spritesheet.png b/src/assets/images/loading/connecting-duck-spritesheet.png new file mode 100644 index 0000000000000000000000000000000000000000..36c5daeca9ac359d2721a7541db1e600958ee20d GIT binary patch literal 34720 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 PfIn9?^wld=@e%(IBrHKV literal 0 HcmV?d00001 diff --git a/src/assets/images/loading/connecting_duck_01.png b/src/assets/images/loading/connecting_duck_01.png new file mode 100644 index 0000000000000000000000000000000000000000..e3772c8b4e3910d9c52f7865d8058c3e5c86d41c GIT binary patch literal 3985 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 RxzL7}n4_)B;YvaP^}pt-O8Wo+ literal 0 HcmV?d00001 diff --git a/src/assets/images/loading/connecting_duck_03.png b/src/assets/images/loading/connecting_duck_03.png new file mode 100644 index 0000000000000000000000000000000000000000..15a2bb9f4b69d21f1dfb005e2c0bf785f9d16617 GIT binary patch literal 5842 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 Bc3=Pi literal 0 HcmV?d00001 diff --git a/src/assets/images/loading/connecting_duck_06.png b/src/assets/images/loading/connecting_duck_06.png new file mode 100644 index 0000000000000000000000000000000000000000..0e51a51d7bceb555184e53fdc51325904a2e5edc GIT binary patch literal 5290 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 literal 0 HcmV?d00001 diff --git a/src/assets/styles/icons.scss b/src/assets/styles/icons.scss index 3d8f14ab..930c447d 100644 --- a/src/assets/styles/icons.scss +++ b/src/assets/styles/icons.scss @@ -705,6 +705,12 @@ height: 16px; } + &.icon-hc-banner { + background: url('../images/catalog/hc_big.png'); + width: 68px; + height: 40px; + } + &.spin { animation: rotating 1s linear infinite; } diff --git a/src/assets/styles/utils.scss b/src/assets/styles/utils.scss index cc44e17f..e0ccc5d4 100644 --- a/src/assets/styles/utils.scss +++ b/src/assets/styles/utils.scss @@ -7,7 +7,7 @@ } .scale-1 { - transform: scale(1) translateZ(0); + transform: scale(1); } .scale-1-25 { @@ -19,7 +19,7 @@ } .scale-2 { - transform: scale(2) translateZ(0); + transform: scale(2); } .opacity-0-5 { @@ -66,3 +66,7 @@ ul { -o-user-select: none; user-select: none; } + +.grayscale { + filter: grayscale(1); +} From 0e22b4fe7e6ed78c09317e5425d82e13c6f70aa7 Mon Sep 17 00:00:00 2001 From: Bill Date: Wed, 29 Sep 2021 22:29:26 -0400 Subject: [PATCH 05/73] Achievement updates --- .../AchievementsUIUnseenCountEvent.ts | 20 ++ src/events/achievements/index.ts | 1 + .../AchievementsMessageHandler.tsx | 70 ----- .../AchievementsMessageHandler.types.ts | 2 - src/views/achievements/AchievementsView.scss | 71 ++--- src/views/achievements/AchievementsView.tsx | 243 +++++++++++++++--- .../common/AchievementCategory.ts | 35 ++- .../common/AchievementUtilities.ts | 38 +++ .../common/IsIgnoredAchievement.ts | 15 ++ .../context/AchievementsContext.tsx | 14 - .../context/AchievementsContext.types.ts | 13 - .../reducers/AchievementsReducer.tsx | 87 ------- .../AchievementBadgeView.tsx | 15 ++ .../AchievementBadgeView.types.ts | 8 + .../AchievementDetailsView.tsx | 62 +++++ .../AchievementDetailsView.types.ts | 6 + .../AchievementListItemView.tsx | 17 ++ .../AchievementListItemView.types.ts | 7 + .../achievement-list/AchievementListView.tsx | 18 ++ .../AchievementListView.types.ts | 10 + .../AchievementCategoryListItemView.tsx | 68 ----- .../AchievementCategoryListItemView.types.ts | 4 +- .../AchievementsCategoryListItemView.tsx | 38 +++ .../AchievementsCategoryListView.tsx | 18 ++ .../AchievementsCategoryListView.types.ts | 10 + .../category-list/AchievementsListView.tsx | 18 -- .../category/AchievementCategoryView.tsx | 97 +++---- .../category/AchievementCategoryView.types.ts | 7 +- 28 files changed, 577 insertions(+), 435 deletions(-) create mode 100644 src/events/achievements/AchievementsUIUnseenCountEvent.ts delete mode 100644 src/views/achievements/AchievementsMessageHandler.tsx delete mode 100644 src/views/achievements/AchievementsMessageHandler.types.ts create mode 100644 src/views/achievements/common/AchievementUtilities.ts create mode 100644 src/views/achievements/common/IsIgnoredAchievement.ts delete mode 100644 src/views/achievements/context/AchievementsContext.tsx delete mode 100644 src/views/achievements/context/AchievementsContext.types.ts delete mode 100644 src/views/achievements/reducers/AchievementsReducer.tsx create mode 100644 src/views/achievements/views/achievement-badge/AchievementBadgeView.tsx create mode 100644 src/views/achievements/views/achievement-badge/AchievementBadgeView.types.ts create mode 100644 src/views/achievements/views/achievement-details/AchievementDetailsView.tsx create mode 100644 src/views/achievements/views/achievement-details/AchievementDetailsView.types.ts create mode 100644 src/views/achievements/views/achievement-list-item/AchievementListItemView.tsx create mode 100644 src/views/achievements/views/achievement-list-item/AchievementListItemView.types.ts create mode 100644 src/views/achievements/views/achievement-list/AchievementListView.tsx create mode 100644 src/views/achievements/views/achievement-list/AchievementListView.types.ts delete mode 100644 src/views/achievements/views/category-list-item/AchievementCategoryListItemView.tsx create mode 100644 src/views/achievements/views/category-list-item/AchievementsCategoryListItemView.tsx create mode 100644 src/views/achievements/views/category-list/AchievementsCategoryListView.tsx create mode 100644 src/views/achievements/views/category-list/AchievementsCategoryListView.types.ts delete mode 100644 src/views/achievements/views/category-list/AchievementsListView.tsx diff --git a/src/events/achievements/AchievementsUIUnseenCountEvent.ts b/src/events/achievements/AchievementsUIUnseenCountEvent.ts new file mode 100644 index 00000000..330e991c --- /dev/null +++ b/src/events/achievements/AchievementsUIUnseenCountEvent.ts @@ -0,0 +1,20 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; + +export class AchievementsUIUnseenCountEvent extends NitroEvent +{ + public static UNSEEN_COUNT: string = 'AUUCE_UNSEEN_COUNT'; + + private _count: number; + + constructor(count: number) + { + super(AchievementsUIUnseenCountEvent.UNSEEN_COUNT); + + this._count = count; + } + + public get count(): number + { + return this._count; + } +} diff --git a/src/events/achievements/index.ts b/src/events/achievements/index.ts index 264ce6ad..a8c548f4 100644 --- a/src/events/achievements/index.ts +++ b/src/events/achievements/index.ts @@ -1 +1,2 @@ export * from './AchievementsUIEvent'; +export * from './AchievementsUIUnseenCountEvent'; diff --git a/src/views/achievements/AchievementsMessageHandler.tsx b/src/views/achievements/AchievementsMessageHandler.tsx deleted file mode 100644 index d9e941a6..00000000 --- a/src/views/achievements/AchievementsMessageHandler.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { AchievementEvent, AchievementsEvent, AchievementsScoreEvent } from '@nitrots/nitro-renderer'; -import { FC, useCallback } from 'react'; -import { CreateMessageHook } from '../../hooks/messages'; -import { IAchievementsMessageHandlerProps } from './AchievementsMessageHandler.types'; -import { AchievementCategory } from './common/AchievementCategory'; -import { useAchievementsContext } from './context/AchievementsContext'; -import { AchievementsActions } from './reducers/AchievementsReducer'; - -export const AchievementsMessageHandler: FC = props => -{ - const { achievementsState = null, dispatchAchievementsState = null } = useAchievementsContext(); - - const onAchievementEvent = useCallback((event: AchievementEvent) => - { - const parser = event.getParser(); - - console.log(parser); - - }, [ dispatchAchievementsState ]); - - const onAchievementsEvent = useCallback((event: AchievementsEvent) => - { - const parser = event.getParser(); - - const categories: AchievementCategory[] = []; - - for(const achievement of parser.achievements) - { - const categoryName = achievement.category; - - const existing = categories.find(category => category.name === categoryName); - - if(existing) - { - existing.achievements.push(achievement); - continue; - } - - const category = new AchievementCategory(categoryName); - category.achievements.push(achievement); - categories.push(category); - } - - dispatchAchievementsState({ - type: AchievementsActions.SET_CATEGORIES, - payload: { - categories: categories - } - }); - }, [ dispatchAchievementsState ]); - - const onAchievementsScoreEvent = useCallback((event: AchievementsScoreEvent) => - { - const parser = event.getParser(); - - dispatchAchievementsState({ - type: AchievementsActions.SET_SCORE, - payload: { - score: parser.score - } - }); - - }, [ dispatchAchievementsState ]); - - CreateMessageHook(AchievementEvent, onAchievementEvent); - CreateMessageHook(AchievementsEvent, onAchievementsEvent); - CreateMessageHook(AchievementsScoreEvent, onAchievementsScoreEvent); - - return null; -}; diff --git a/src/views/achievements/AchievementsMessageHandler.types.ts b/src/views/achievements/AchievementsMessageHandler.types.ts deleted file mode 100644 index 3de1326b..00000000 --- a/src/views/achievements/AchievementsMessageHandler.types.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface IAchievementsMessageHandlerProps -{} diff --git a/src/views/achievements/AchievementsView.scss b/src/views/achievements/AchievementsView.scss index f9f69f5d..025a30c8 100644 --- a/src/views/achievements/AchievementsView.scss +++ b/src/views/achievements/AchievementsView.scss @@ -1,63 +1,28 @@ .nitro-achievements { width: $achievement-width; height: $achievement-height; +} - .score { - border-color: $grid-border-color !important; - background-color: $grid-bg-color; - } +.nitro-achievements-category-grid { + --nitro-grid-column-min-width: 80px !important; - .category { - border-color: $grid-border-color !important; - background-color: $grid-bg-color; - cursor: pointer; - - &.active { - border-color: $grid-active-border-color !important; - background-color: $grid-active-bg-color; - } - - .category-score { - margin-top: 43.5px; - } - } - - .achievements { - height: 152px; - overflow-y: auto; - overflow-x: hidden; - - .achievement { - border-color: $grid-border-color !important; - background-color: $grid-bg-color; - cursor: pointer; - - &.active { - border-color: $grid-active-border-color !important; - background-color: $grid-active-bg-color; - } - - &.gray { - div { - filter: grayscale(1); - opacity: .5; - } - } - - div { - height: 40px; - } - } - } - - .achievement-image { + .grid-item { height: 80px; - width: 80px; + max-height: 80px; - .badge-image { - width: 80px; - height: 80px; - background-size: contain; + .achievement-score { + top: 50px; } } } + +.nitro-achievements-back-arrow { + background: url('../../assets/images/achievements/back-arrow.png') no-repeat center; + width: 33px; + height: 34px; +} + +.nitro-achievements-badge-image { + width: 80px; + height: 80px; +} diff --git a/src/views/achievements/AchievementsView.tsx b/src/views/achievements/AchievementsView.tsx index 48bb619b..9ef315a6 100644 --- a/src/views/achievements/AchievementsView.tsx +++ b/src/views/achievements/AchievementsView.tsx @@ -1,22 +1,26 @@ -import { FC, useCallback, useReducer, useState } from 'react'; +import { AchievementData, AchievementEvent, AchievementsEvent, AchievementsScoreEvent, RequestAchievementsMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { LocalizeText } from '../../api'; -import { AchievementsUIEvent } from '../../events/achievements'; +import { AchievementsUIEvent, AchievementsUIUnseenCountEvent } from '../../events/achievements'; +import { BatchUpdates, CreateMessageHook, dispatchUiEvent, SendMessageHook } from '../../hooks'; import { useUiEvent } from '../../hooks/events'; -import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout'; -import { AchievementsMessageHandler } from './AchievementsMessageHandler'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardSubHeaderView, NitroCardView, NitroLayoutFlexColumn, NitroLayoutGrid, NitroLayoutGridColumn } from '../../layout'; +import { NitroLayoutBase } from '../../layout/base'; import { AchievementsViewProps } from './AchievementsView.types'; -import { AchievementsContextProvider } from './context/AchievementsContext'; -import { AchievementsReducer, initialAchievements } from './reducers/AchievementsReducer'; -import { AchievementsListView } from './views/category-list/AchievementsListView'; +import { AchievementCategory } from './common/AchievementCategory'; +import { AchievementUtilities } from './common/AchievementUtilities'; +import { AchievementsCategoryListView } from './views/category-list/AchievementsCategoryListView'; import { AchievementCategoryView } from './views/category/AchievementCategoryView'; export const AchievementsView: FC = props => { const [ isVisible, setIsVisible ] = useState(false); - const [ achievementsState, dispatchAchievementsState ] = useReducer(AchievementsReducer, initialAchievements); - const { score = null } = achievementsState; + const [ isInitalized, setIsInitalized ] = useState(false); + const [ achievementCategories, setAchievementCategories ] = useState([]); + const [ selectedCategoryCode, setSelectedCategoryCode ] = useState(null); + const [ achievementScore, setAchievementScore ] = useState(0); - const onAchievementsEvent = useCallback((event: AchievementsUIEvent) => + const onAchievementsUIEvent = useCallback((event: AchievementsUIEvent) => { switch(event.type) { @@ -32,30 +36,203 @@ export const AchievementsView: FC = props => } }, []); - useUiEvent(AchievementsUIEvent.SHOW_ACHIEVEMENTS, onAchievementsEvent); - useUiEvent(AchievementsUIEvent.HIDE_ACHIEVEMENTS, onAchievementsEvent); - useUiEvent(AchievementsUIEvent.TOGGLE_ACHIEVEMENTS, onAchievementsEvent); + useUiEvent(AchievementsUIEvent.SHOW_ACHIEVEMENTS, onAchievementsUIEvent); + useUiEvent(AchievementsUIEvent.HIDE_ACHIEVEMENTS, onAchievementsUIEvent); + useUiEvent(AchievementsUIEvent.TOGGLE_ACHIEVEMENTS, onAchievementsUIEvent); + + const onAchievementEvent = useCallback((event: AchievementEvent) => + { + const parser = event.getParser(); + const achievement = parser.achievement; + const newCategories = [ ...achievementCategories ]; + const categoryName = achievement.category; + const categoryIndex = newCategories.findIndex(existing => (existing.code === categoryName)); + + if(categoryIndex === -1) + { + const category = new AchievementCategory(categoryName); + + category.achievements.push(achievement); + + newCategories.push(category); + } + else + { + const category = newCategories[categoryIndex]; + const newAchievements = [ ...category.achievements ]; + const achievementIndex = newAchievements.findIndex(existing => (existing.achievementId === achievement.achievementId)); + let previousAchievement: AchievementData = null; + + if(achievementIndex === -1) + { + newAchievements.push(achievement); + } + else + { + previousAchievement = newAchievements[achievementIndex]; + + newAchievements[achievementIndex] = achievement; + } + + if(!AchievementUtilities.isIgnoredAchievement(achievement)) + { + achievement.unseen++; + + if(previousAchievement) achievement.unseen += previousAchievement.unseen; + } + + category.achievements = newAchievements; + } + + setAchievementCategories(newCategories); + }, [ achievementCategories ]); + + CreateMessageHook(AchievementEvent, onAchievementEvent); + + const onAchievementsEvent = useCallback((event: AchievementsEvent) => + { + const parser = event.getParser(); + + const categories: AchievementCategory[] = []; + + for(const achievement of parser.achievements) + { + const categoryName = achievement.category; + let existing = categories.find(category => (category.code === categoryName)); + + if(!existing) + { + existing = new AchievementCategory(categoryName); + + categories.push(existing); + } + + existing.achievements.push(achievement); + } + + BatchUpdates(() => + { + setAchievementCategories(categories); + setIsInitalized(true); + }); + }, []); + + CreateMessageHook(AchievementsEvent, onAchievementsEvent); + + const onAchievementsScoreEvent = useCallback((event: AchievementsScoreEvent) => + { + const parser = event.getParser(); + + setAchievementScore(parser.score); + }, []); + + CreateMessageHook(AchievementsScoreEvent, onAchievementsScoreEvent); + + const getTotalUnseen = useMemo(() => + { + let unseen = 0; + + for(const category of achievementCategories) + { + for(const achievement of category.achievements) unseen += achievement.unseen; + } + + return unseen; + }, [ achievementCategories ]); + + const getProgress = useMemo(() => + { + let progress = 0; + + for(const category of achievementCategories) progress += category.getProgress(); + + return progress; + }, [ achievementCategories ]); + + const getMaxProgress = useMemo(() => + { + let progress = 0; + + for(const category of achievementCategories) progress += category.getMaxProgress(); + + return progress; + }, [ achievementCategories ]); + + const getSelectedCategory = useMemo(() => + { + if(!achievementCategories || !achievementCategories.length) return null; + + return achievementCategories.find(existing => (existing.code === selectedCategoryCode)); + }, [ achievementCategories, selectedCategoryCode ]); + + const setAchievementSeen = useCallback((code: string, achievementId: number) => + { + const newCategories = [ ...achievementCategories ]; + + for(const category of newCategories) + { + if(category.code !== code) continue; + + for(const achievement of category.achievements) + { + if(achievement.achievementId !== achievementId) continue; + + achievement.unseen = 0; + } + } + + setAchievementCategories(newCategories); + }, [ achievementCategories ]); + + useEffect(() => + { + if(!isVisible || !isInitalized) return; + + SendMessageHook(new RequestAchievementsMessageComposer()); + }, [ isVisible, isInitalized ]); + + useEffect(() => + { + dispatchUiEvent(new AchievementsUIUnseenCountEvent(getTotalUnseen)); + }, [ getTotalUnseen ]); + + if(!isVisible || !isInitalized) return null; return ( - - - { isVisible && - - setIsVisible(false) } /> - -
-
- -
- { LocalizeText('achievements.categories.score', ['score'], [score.toString()]) } -
-
-
- -
-
-
-
} -
+ + setIsVisible(false) } /> + { getSelectedCategory && + + setSelectedCategoryCode(null) } className="nitro-achievements-back-arrow" /> + + + { LocalizeText(`quests.${ getSelectedCategory.code }.name`) } + + + { LocalizeText('achievements.details.categoryprogress', [ 'progress', 'limit' ], [ getSelectedCategory.getProgress().toString(), getSelectedCategory.getMaxProgress().toString() ]) } + + + } + + + + { !getSelectedCategory && + <> + + + + { LocalizeText('achievements.categories.totalprogress', [ 'progress', 'limit' ], [ getProgress.toString(), getMaxProgress.toString() ]) } + + + { LocalizeText('achievements.categories.score', [ 'score' ], [ achievementScore.toString() ]) } + + + } + { getSelectedCategory && + } + + + + ); }; diff --git a/src/views/achievements/common/AchievementCategory.ts b/src/views/achievements/common/AchievementCategory.ts index e40468d1..992c3a57 100644 --- a/src/views/achievements/common/AchievementCategory.ts +++ b/src/views/achievements/common/AchievementCategory.ts @@ -2,23 +2,42 @@ import { AchievementData } from '@nitrots/nitro-renderer'; export class AchievementCategory { - private _name: string; + private _code: string; private _achievements: AchievementData[]; - constructor(name: string) + constructor(code: string) { - this._name = name; - this._achievements = []; + this._code = code; + this._achievements = []; } - public get name(): string + public getProgress(): number { - return this._name; + let progress = 0; + + for(const achievement of this._achievements) + { + progress += (achievement.finalLevel ? achievement.level : (achievement.level - 1)); + } + + return progress; } - public set name(name: string) + public getMaxProgress(): number { - this._name = name; + let progress = 0; + + for(const achievement of this._achievements) + { + progress += achievement.levelCount; + } + + return progress; + } + + public get code(): string + { + return this._code; } public get achievements(): AchievementData[] diff --git a/src/views/achievements/common/AchievementUtilities.ts b/src/views/achievements/common/AchievementUtilities.ts new file mode 100644 index 00000000..f6c18ac3 --- /dev/null +++ b/src/views/achievements/common/AchievementUtilities.ts @@ -0,0 +1,38 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { GetConfiguration, GetLocalization } from '../../../api'; + +export class AchievementUtilities +{ + public static hasStarted(achievement: AchievementData): boolean + { + if(!achievement) return false; + + if(achievement.finalLevel || ((achievement.level - 1) > 0)) return true; + + return false; + } + + public static getBadgeCode(achievement: AchievementData): string + { + if(!achievement) return null; + + let badgeId = achievement.badgeId; + + if(!achievement.finalLevel) badgeId = GetLocalization().getPreviousLevelBadgeId(badgeId); + + return badgeId; + } + + public static isIgnoredAchievement(achievement: AchievementData): boolean + { + if(!achievement) return false; + + const ignored = GetConfiguration('achievements.unseen.ignored'); + const value = achievement.badgeId.replace(/[0-9]/g, ''); + const index = ignored.indexOf(value); + + if(index >= 0) return true; + + return false; + } +} diff --git a/src/views/achievements/common/IsIgnoredAchievement.ts b/src/views/achievements/common/IsIgnoredAchievement.ts new file mode 100644 index 00000000..0cc81fdd --- /dev/null +++ b/src/views/achievements/common/IsIgnoredAchievement.ts @@ -0,0 +1,15 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { GetConfiguration } from '../../../api'; + +export const IsIgnoredAchievement = (achievement: AchievementData) => +{ + if(!achievement) return false; + + const ignored = GetConfiguration('achievements.unseen.ignored'); + const value = achievement.badgeId.replace(/[0-9]/g, ''); + const index = ignored.indexOf(value); + + if(index >= 0) return true; + + return false; +} diff --git a/src/views/achievements/context/AchievementsContext.tsx b/src/views/achievements/context/AchievementsContext.tsx deleted file mode 100644 index c1162f4d..00000000 --- a/src/views/achievements/context/AchievementsContext.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { createContext, FC, useContext } from 'react'; -import { AchievementsContextProps, IAchievementsContext } from './AchievementsContext.types'; - -const AchievementsContext = createContext({ - achievementsState: null, - dispatchAchievementsState: null -}); - -export const AchievementsContextProvider: FC = props => -{ - return { props.children } -} - -export const useAchievementsContext = () => useContext(AchievementsContext); diff --git a/src/views/achievements/context/AchievementsContext.types.ts b/src/views/achievements/context/AchievementsContext.types.ts deleted file mode 100644 index 320cd787..00000000 --- a/src/views/achievements/context/AchievementsContext.types.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Dispatch, ProviderProps } from 'react'; -import { IAchievementsAction, IAchievementsState } from '../reducers/AchievementsReducer'; - -export interface IAchievementsContext -{ - achievementsState: IAchievementsState; - dispatchAchievementsState: Dispatch; -} - -export interface AchievementsContextProps extends ProviderProps -{ - -} diff --git a/src/views/achievements/reducers/AchievementsReducer.tsx b/src/views/achievements/reducers/AchievementsReducer.tsx deleted file mode 100644 index 6f8e2b11..00000000 --- a/src/views/achievements/reducers/AchievementsReducer.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { Reducer } from 'react'; -import { AchievementCategory } from '../common/AchievementCategory'; - -export interface IAchievementsState -{ - categories: AchievementCategory[], - score: number, - selectedCategoryName: string, - selectedAchievementId: number -} - -export interface IAchievementsAction -{ - type: string; - payload: { - categories?: AchievementCategory[], - score?: number, - selectedCategoryName?: string, - selectedAchievementId?: number - } -} - -export class AchievementsActions -{ - public static SET_CATEGORIES: string = 'AA_SET_CATEGORIES'; - public static SET_SCORE: string = 'AA_SET_SCORE'; - public static SELECT_CATEGORY: string = 'AA_SELECT_CATEGORY'; - public static SELECT_ACHIEVEMENT: string = 'AA_SELECT_ACHIEVEMENT'; -} - -export const initialAchievements: IAchievementsState = { - categories: null, - score: null, - selectedCategoryName: null, - selectedAchievementId: null -} - -export const AchievementsReducer: Reducer = (state, action) => -{ - switch(action.type) - { - case AchievementsActions.SET_CATEGORIES: { - const categories = (action.payload.categories || state.categories || null); - - let selectedCategoryName = null; - let selectedAchievementId = null; - - if(categories.length > 0) - { - selectedCategoryName = categories[0].name; - - if(categories[0].achievements.length > 0) selectedAchievementId = categories[0].achievements[0].achievementId; - } - - return { ...state, categories, selectedCategoryName, selectedAchievementId }; - } - case AchievementsActions.SET_SCORE: { - const score = (action.payload.score || state.score || null); - - return { ...state, score }; - } - case AchievementsActions.SELECT_CATEGORY: { - const selectedCategoryName = (action.payload.selectedCategoryName || state.selectedCategoryName || null); - - let selectedAchievementId = null; - - if(selectedCategoryName) - { - const category = state.categories.find(category => category.name === selectedCategoryName); - - if(category && category.achievements.length > 0) - { - selectedAchievementId = category.achievements[0].achievementId; - } - } - - return { ...state, selectedCategoryName, selectedAchievementId }; - } - case AchievementsActions.SELECT_ACHIEVEMENT: { - const selectedAchievementId = (action.payload.selectedAchievementId || state.selectedAchievementId || null); - - return { ...state, selectedAchievementId }; - } - default: - return state; - } -} diff --git a/src/views/achievements/views/achievement-badge/AchievementBadgeView.tsx b/src/views/achievements/views/achievement-badge/AchievementBadgeView.tsx new file mode 100644 index 00000000..578f86dc --- /dev/null +++ b/src/views/achievements/views/achievement-badge/AchievementBadgeView.tsx @@ -0,0 +1,15 @@ +import { FC } from 'react'; +import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView'; +import { AchievementUtilities } from '../../common/AchievementUtilities'; +import { AchievementBadgeViewProps } from './AchievementBadgeView.types'; + +export const AchievementBadgeView: FC = props => +{ + const { achievement = null, scale = 1, ...rest } = props; + + if(!achievement) return null; + + return ( + + ); +} diff --git a/src/views/achievements/views/achievement-badge/AchievementBadgeView.types.ts b/src/views/achievements/views/achievement-badge/AchievementBadgeView.types.ts new file mode 100644 index 00000000..c89706ac --- /dev/null +++ b/src/views/achievements/views/achievement-badge/AchievementBadgeView.types.ts @@ -0,0 +1,8 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { NitroLayoutBaseProps } from '../../../../layout/base'; + +export interface AchievementBadgeViewProps extends NitroLayoutBaseProps +{ + achievement: AchievementData; + scale?: number; +} diff --git a/src/views/achievements/views/achievement-details/AchievementDetailsView.tsx b/src/views/achievements/views/achievement-details/AchievementDetailsView.tsx new file mode 100644 index 00000000..be50d707 --- /dev/null +++ b/src/views/achievements/views/achievement-details/AchievementDetailsView.tsx @@ -0,0 +1,62 @@ +import { FC, useMemo } from 'react'; +import { LocalizeBadgeDescription, LocalizeBadgeName, LocalizeText } from '../../../../api'; +import { NitroLayoutFlex, NitroLayoutFlexColumn } from '../../../../layout'; +import { NitroLayoutBase } from '../../../../layout/base'; +import { CurrencyIcon } from '../../../shared/currency-icon/CurrencyIcon'; +import { AchievementUtilities } from '../../common/AchievementUtilities'; +import { AchievementBadgeView } from '../achievement-badge/AchievementBadgeView'; +import { AchievementDetailsViewProps } from './AchievementDetailsView.types'; + +export const AchievementDetailsView: FC = props => +{ + const { achievement = null } = props; + + const getAchievementLevel = useMemo(() => + { + if(achievement.finalLevel) return achievement.level; + + return (achievement.level - 1); + }, [ achievement ]); + + if(!achievement) return null; + + const currentAmount = achievement.currentPoints; + const maxAmount = achievement.scoreLimit; + const scoreAtStartOfLevel = achievement.scoreAtStartOfLevel; + + return ( + + + + + { LocalizeText('achievements.details.level', [ 'level', 'limit' ], [ getAchievementLevel.toString(), achievement.levelCount.toString() ]) } + + + + + + { LocalizeBadgeName(AchievementUtilities.getBadgeCode(achievement)) } + + + { LocalizeBadgeDescription(AchievementUtilities.getBadgeCode(achievement)) } + + + { ((achievement.levelRewardPoints > 0) || (maxAmount > 0)) && + + { (achievement.levelRewardPoints > 0) && + + + { LocalizeText('achievements.details.reward') } + + + { achievement.levelRewardPoints } + + + } + { (maxAmount > 0) && + LocalizeText('achievements.details.progress', [ 'progress', 'limit' ], [ (currentAmount + scoreAtStartOfLevel).toString(), (maxAmount + scoreAtStartOfLevel).toString() ]) } + } + + + ) +} diff --git a/src/views/achievements/views/achievement-details/AchievementDetailsView.types.ts b/src/views/achievements/views/achievement-details/AchievementDetailsView.types.ts new file mode 100644 index 00000000..098f0867 --- /dev/null +++ b/src/views/achievements/views/achievement-details/AchievementDetailsView.types.ts @@ -0,0 +1,6 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; + +export interface AchievementDetailsViewProps +{ + achievement: AchievementData; +} diff --git a/src/views/achievements/views/achievement-list-item/AchievementListItemView.tsx b/src/views/achievements/views/achievement-list-item/AchievementListItemView.tsx new file mode 100644 index 00000000..806c1ed5 --- /dev/null +++ b/src/views/achievements/views/achievement-list-item/AchievementListItemView.tsx @@ -0,0 +1,17 @@ +import { FC } from 'react'; +import { NitroCardGridItemView } from '../../../../layout'; +import { AchievementBadgeView } from '../achievement-badge/AchievementBadgeView'; +import { AchievementListItemViewProps } from './AchievementListItemView.types'; + +export const AchievementListItemView: FC = props => +{ + const { achievement = null, children = null, ...rest } = props; + + if(!achievement) return null; + + return ( + + + + ); +} diff --git a/src/views/achievements/views/achievement-list-item/AchievementListItemView.types.ts b/src/views/achievements/views/achievement-list-item/AchievementListItemView.types.ts new file mode 100644 index 00000000..78072e6d --- /dev/null +++ b/src/views/achievements/views/achievement-list-item/AchievementListItemView.types.ts @@ -0,0 +1,7 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { NitroCardGridItemViewProps } from '../../../../layout'; + +export interface AchievementListItemViewProps extends NitroCardGridItemViewProps +{ + achievement: AchievementData; +} diff --git a/src/views/achievements/views/achievement-list/AchievementListView.tsx b/src/views/achievements/views/achievement-list/AchievementListView.tsx new file mode 100644 index 00000000..8cb6d7e1 --- /dev/null +++ b/src/views/achievements/views/achievement-list/AchievementListView.tsx @@ -0,0 +1,18 @@ +import { FC } from 'react'; +import { NitroCardGridView } from '../../../../layout'; +import { AchievementListItemView } from '../achievement-list-item/AchievementListItemView'; +import { AchievementListViewProps } from './AchievementListView.types'; + +export const AchievementListView: FC = props => +{ + const { achievements = null, selectedAchievementId = 0, setSelectedAchievementId = null, ...rest } = props; + + return ( + + { achievements && (achievements.length > 0) && achievements.map(achievement => + { + return setSelectedAchievementId(achievement.achievementId) } />; + }) } + + ); +} diff --git a/src/views/achievements/views/achievement-list/AchievementListView.types.ts b/src/views/achievements/views/achievement-list/AchievementListView.types.ts new file mode 100644 index 00000000..9a6013f9 --- /dev/null +++ b/src/views/achievements/views/achievement-list/AchievementListView.types.ts @@ -0,0 +1,10 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { Dispatch, SetStateAction } from 'react'; +import { NitroCardGridViewProps } from '../../../../layout'; + +export interface AchievementListViewProps extends NitroCardGridViewProps +{ + achievements: AchievementData[]; + selectedAchievementId: number; + setSelectedAchievementId: Dispatch>; +} diff --git a/src/views/achievements/views/category-list-item/AchievementCategoryListItemView.tsx b/src/views/achievements/views/category-list-item/AchievementCategoryListItemView.tsx deleted file mode 100644 index ddc6ed24..00000000 --- a/src/views/achievements/views/category-list-item/AchievementCategoryListItemView.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import classNames from 'classnames'; -import { FC, useCallback, useMemo } from 'react'; -import { GetConfiguration } from '../../../../api'; -import { useAchievementsContext } from '../../context/AchievementsContext'; -import { AchievementsActions } from '../../reducers/AchievementsReducer'; -import { AchievementCategoryListItemViewProps } from './AchievementCategoryListItemView.types'; - -export const AchievementCategoryListItemView: FC = props => -{ - const { category = null, isActive = false } = props; - const { dispatchAchievementsState = null } = useAchievementsContext(); - - const categoryLevel = useMemo(() => - { - let level = 0; - - for(const achievement of category.achievements) - { - level = (level + (achievement.finalLevel ? achievement.level : (achievement.level - 1))); - } - - return level; - }, [ category ]); - - const getCategoryImage = useMemo(() => - { - const level = categoryLevel; - const imageUrl = GetConfiguration('achievements.images.url'); - - return imageUrl.replace('%image%', `achcategory_${ category.name }_${ ((level > 0) ? 'active' : 'inactive') }`); - }, [ category, categoryLevel ]); - - const getCategoryProgress = useMemo(() => - { - let completed = 0; - let total = 0; - - for(const achievement of category.achievements) - { - if(!achievement) continue; - - if(achievement.firstLevelAchieved) completed = (completed + ((achievement.finalLevel) ? achievement.level : (achievement.level - 1))); - - total += achievement.scoreLimit; - } - - return (completed + ' / ' + total); - }, [ category ]); - - const selectCategory = useCallback((name: string) => - { - dispatchAchievementsState({ - type: AchievementsActions.SELECT_CATEGORY, - payload: { - selectedCategoryName: name - } - }); - }, [ dispatchAchievementsState ]); - - return ( -
-
selectCategory(category.name) }> - -
{ getCategoryProgress }
-
-
- ); -} diff --git a/src/views/achievements/views/category-list-item/AchievementCategoryListItemView.types.ts b/src/views/achievements/views/category-list-item/AchievementCategoryListItemView.types.ts index 96e6b946..3f493995 100644 --- a/src/views/achievements/views/category-list-item/AchievementCategoryListItemView.types.ts +++ b/src/views/achievements/views/category-list-item/AchievementCategoryListItemView.types.ts @@ -1,7 +1,7 @@ +import { NitroCardGridItemViewProps } from '../../../../layout'; import { AchievementCategory } from '../../common/AchievementCategory'; -export interface AchievementCategoryListItemViewProps +export interface AchievementCategoryListItemViewProps extends NitroCardGridItemViewProps { category: AchievementCategory; - isActive?: boolean; } diff --git a/src/views/achievements/views/category-list-item/AchievementsCategoryListItemView.tsx b/src/views/achievements/views/category-list-item/AchievementsCategoryListItemView.tsx new file mode 100644 index 00000000..30a82dc9 --- /dev/null +++ b/src/views/achievements/views/category-list-item/AchievementsCategoryListItemView.tsx @@ -0,0 +1,38 @@ +import { FC, useCallback, useMemo } from 'react'; +import { GetConfiguration } from '../../../../api'; +import { NitroCardGridItemView } from '../../../../layout'; +import { NitroLayoutBase } from '../../../../layout/base'; +import { AchievementCategoryListItemViewProps } from './AchievementCategoryListItemView.types'; + +export const AchievementsCategoryListItemView: FC = props => +{ + const { category = null, ...rest } = props; + + const progress = category.getProgress(); + const maxProgress = category.getMaxProgress(); + + const getCategoryImage = useMemo(() => + { + const imageUrl = GetConfiguration('achievements.images.url'); + + return imageUrl.replace('%image%', `achcategory_${ category.code }_${ ((progress > 0) ? 'active' : 'inactive') }`); + }, [ category, progress ]); + + const getTotalUnseen = useCallback(() => + { + let unseen = 0; + + for(const achievement of category.achievements) unseen += achievement.unseen; + + return unseen; + }, [ category ]); + + return ( + + { category.code } + + { progress } / { maxProgress } + + + ); +} diff --git a/src/views/achievements/views/category-list/AchievementsCategoryListView.tsx b/src/views/achievements/views/category-list/AchievementsCategoryListView.tsx new file mode 100644 index 00000000..70cbc44d --- /dev/null +++ b/src/views/achievements/views/category-list/AchievementsCategoryListView.tsx @@ -0,0 +1,18 @@ +import { FC } from 'react'; +import { NitroCardGridView } from '../../../../layout'; +import { AchievementsCategoryListItemView } from '../category-list-item/AchievementsCategoryListItemView'; +import { AchievementsCategoryListViewProps } from './AchievementsCategoryListView.types'; + +export const AchievementsCategoryListView: FC = props => +{ + const { categories = null, selectedCategoryCode = null, setSelectedCategoryCode = null, ...rest } = props; + + return ( + + { categories && (categories.length > 0) && categories.map((category, index) => + { + return setSelectedCategoryCode(category.code) } />; + }) } + + ); +}; diff --git a/src/views/achievements/views/category-list/AchievementsCategoryListView.types.ts b/src/views/achievements/views/category-list/AchievementsCategoryListView.types.ts new file mode 100644 index 00000000..ef15c583 --- /dev/null +++ b/src/views/achievements/views/category-list/AchievementsCategoryListView.types.ts @@ -0,0 +1,10 @@ +import { Dispatch, SetStateAction } from 'react'; +import { NitroCardGridViewProps } from '../../../../layout'; +import { AchievementCategory } from '../../common/AchievementCategory'; + +export interface AchievementsCategoryListViewProps extends NitroCardGridViewProps +{ + categories: AchievementCategory[]; + selectedCategoryCode: string; + setSelectedCategoryCode: Dispatch>; +} diff --git a/src/views/achievements/views/category-list/AchievementsListView.tsx b/src/views/achievements/views/category-list/AchievementsListView.tsx deleted file mode 100644 index 2ca173c7..00000000 --- a/src/views/achievements/views/category-list/AchievementsListView.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { FC } from 'react'; -import { useAchievementsContext } from '../../context/AchievementsContext'; -import { AchievementCategoryListItemView } from '../category-list-item/AchievementCategoryListItemView'; - -export const AchievementsListView: FC<{}> = props => -{ - const { achievementsState = null, dispatchAchievementsState = null } = useAchievementsContext();; - const { categories = null, selectedCategoryName = null } = achievementsState; - - return ( -
- { categories && categories.map((category, index) => - { - return ; - }) } -
- ); -}; diff --git a/src/views/achievements/views/category/AchievementCategoryView.tsx b/src/views/achievements/views/category/AchievementCategoryView.tsx index 879c4d84..4041bf21 100644 --- a/src/views/achievements/views/category/AchievementCategoryView.tsx +++ b/src/views/achievements/views/category/AchievementCategoryView.tsx @@ -1,86 +1,51 @@ -import { AchievementData } from '@nitrots/nitro-renderer'; -import classNames from 'classnames'; -import { FC, useCallback, useMemo } from 'react'; -import { LocalizeBadgeDescription, LocalizeBadgeName, LocalizeText } from '../../../../api'; -import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView'; -import { useAchievementsContext } from '../../context/AchievementsContext'; -import { AchievementsActions } from '../../reducers/AchievementsReducer'; +import { FC, useEffect, useMemo, useState } from 'react'; +import { NitroLayoutFlexColumn } from '../../../../layout'; +import { AchievementDetailsView } from '../achievement-details/AchievementDetailsView'; +import { AchievementListView } from '../achievement-list/AchievementListView'; import { AchievementCategoryViewProps } from './AchievementCategoryView.types'; export const AchievementCategoryView: FC = props => { - const achievementsContext = useAchievementsContext(); - - const { achievementsState = null, dispatchAchievementsState = null } = achievementsContext; - const { categories = null, selectedCategoryName = null, selectedAchievementId = null } = achievementsState; + const { category = null, setAchievementSeen = null } = props; + const [ selectedAchievementId, setSelectedAchievementId ] = useState(0); - const getSelectedCategory = useCallback(() => + const getSelectedAchievement = useMemo(() => { - return categories.find(category => category.name === selectedCategoryName); - }, [ categories, selectedCategoryName ]); + if(!category || !category.achievements.length) return null; - const getAchievementImage = useCallback((achievement: AchievementData) => + return category.achievements.find(existing => (existing.achievementId === selectedAchievementId)); + }, [ category, selectedAchievementId ]); + + useEffect(() => { - if(!achievement) return null; - - let badgeId = achievement.badgeId; + let achievementId = 0; - if(achievement.levelCount > 1) + if(!category || !category.achievements.length) { - badgeId = badgeId.replace(/[0-9]/g, ''); - badgeId = (badgeId + (((achievement.level - 1) > 0) ? (achievement.level - 1) : achievement.level)); + achievementId = 0; + } + else + { + achievementId = category.achievements[0].achievementId; } - return badgeId; - }, []); + setSelectedAchievementId(achievementId); + }, [ category ]); - const selectedAchievement = useMemo(() => + useEffect(() => { - if(!getSelectedCategory()) return null; - - return getSelectedCategory().achievements.find(achievement => achievement.achievementId === selectedAchievementId); - }, [ getSelectedCategory, selectedAchievementId ]); + if(!getSelectedAchievement || !getSelectedAchievement.unseen) return; - const selectAchievement = useCallback((id: number) => - { - dispatchAchievementsState({ - type: AchievementsActions.SELECT_ACHIEVEMENT, - payload: { - selectedAchievementId: id - } - }); - }, [ dispatchAchievementsState ]); + setAchievementSeen(category.code, getSelectedAchievement.achievementId); + }, [ category, getSelectedAchievement, setAchievementSeen ]); + if(!category) return null; return ( -
-
-
{ LocalizeText('quests.' + selectedCategoryName + '.name') }
-
IMAGE
-
- { selectedAchievement &&
-
- -
-
-
{ LocalizeBadgeName(selectedAchievement.badgeId) }
-
{ LocalizeBadgeDescription(selectedAchievement.badgeId) }
-
-
} -
-
- { getSelectedCategory().achievements.map((achievement, index) => - { - return ( -
-
selectAchievement(achievement.achievementId)}> - -
-
- ) - }) } -
-
-
+ + + { getSelectedAchievement && + } + ); } diff --git a/src/views/achievements/views/category/AchievementCategoryView.types.ts b/src/views/achievements/views/category/AchievementCategoryView.types.ts index d6c378d4..0e819a34 100644 --- a/src/views/achievements/views/category/AchievementCategoryView.types.ts +++ b/src/views/achievements/views/category/AchievementCategoryView.types.ts @@ -1,2 +1,7 @@ +import { AchievementCategory } from '../../common/AchievementCategory'; + export class AchievementCategoryViewProps -{} +{ + category: AchievementCategory; + setAchievementSeen: (code: string, achievementId: number) => void; +} From c9e64c83dc46b31bc83e6b4a1a0a467fd624a9e8 Mon Sep 17 00:00:00 2001 From: Bill Date: Wed, 29 Sep 2021 22:29:46 -0400 Subject: [PATCH 06/73] Add toolbar achievement unseen --- src/views/toolbar/ToolbarView.tsx | 17 ++++++++++++----- src/views/toolbar/me/ToolbarMeView.tsx | 4 +++- src/views/toolbar/me/ToolbarMeView.types.ts | 1 + 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/views/toolbar/ToolbarView.tsx b/src/views/toolbar/ToolbarView.tsx index 4d10456b..4fbaf697 100644 --- a/src/views/toolbar/ToolbarView.tsx +++ b/src/views/toolbar/ToolbarView.tsx @@ -2,7 +2,7 @@ import { Dispose, DropBounce, EaseOut, FigureUpdateEvent, JumpBy, Motions, Nitro import { FC, useCallback, useState } from 'react'; import { GetRoomSession, GetRoomSessionManager, GetSessionDataManager, GoToDesktop, OpenMessengerChat } from '../../api'; import { AvatarEditorEvent, CatalogEvent, FriendsEvent, FriendsMessengerIconEvent, InventoryEvent, NavigatorEvent, RoomWidgetCameraEvent } from '../../events'; -import { AchievementsUIEvent } from '../../events/achievements'; +import { AchievementsUIEvent, AchievementsUIUnseenCountEvent } from '../../events/achievements'; import { UnseenItemTrackerUpdateEvent } from '../../events/inventory/UnseenItemTrackerUpdateEvent'; import { ModToolsEvent } from '../../events/mod-tools/ModToolsEvent'; import { UserSettingsUIEvent } from '../../events/user-settings/UserSettingsUIEvent'; @@ -27,9 +27,9 @@ export const ToolbarView: FC = props => const [ isMeExpanded, setMeExpanded ] = useState(false); const [ chatIconType, setChatIconType ] = useState(CHAT_ICON_HIDDEN); const [ unseenInventoryCount, setUnseenInventoryCount ] = useState(0); + const [ unseenAchievementCount, setUnseenAchievementCount ] = useState(0); const unseenFriendListCount = 0; - const unseenAchievementsCount = 0; const onUserInfoEvent = useCallback((event: UserInfoEvent) => { @@ -64,6 +64,13 @@ export const ToolbarView: FC = props => useUiEvent(UnseenItemTrackerUpdateEvent.UPDATE_COUNT, onUnseenItemTrackerUpdateEvent); + const onAchievementsUIUnseenCountEvent = useCallback((event: AchievementsUIUnseenCountEvent) => + { + setUnseenAchievementCount(event.count); + }, []); + + useUiEvent(AchievementsUIUnseenCountEvent.UNSEEN_COUNT, onAchievementsUIUnseenCountEvent); + const animationIconToToolbar = useCallback((iconName: string, image: HTMLImageElement, x: number, y: number) => { const target = (document.body.getElementsByClassName(iconName)[0] as HTMLElement); @@ -160,15 +167,15 @@ export const ToolbarView: FC = props => return (
- +
setMeExpanded(!isMeExpanded) }> - { (unseenAchievementsCount > 0) && -
{ unseenAchievementsCount }
} + { (unseenAchievementCount > 0) && +
{ unseenAchievementCount }
}
{ isInRoom && (
diff --git a/src/views/toolbar/me/ToolbarMeView.tsx b/src/views/toolbar/me/ToolbarMeView.tsx index 790168b3..d1128da7 100644 --- a/src/views/toolbar/me/ToolbarMeView.tsx +++ b/src/views/toolbar/me/ToolbarMeView.tsx @@ -4,7 +4,7 @@ import { ToolbarMeViewProps } from './ToolbarMeView.types'; export const ToolbarMeView: FC = props => { - const { handleToolbarItemClick = null } = props; + const { unseenAchievementCount = 0, handleToolbarItemClick = null } = props; return (
@@ -17,6 +17,8 @@ export const ToolbarMeView: FC = props =>
handleToolbarItemClick(ToolbarViewItems.ACHIEVEMENTS_ITEM) }> + { (unseenAchievementCount > 0) && +
{ unseenAchievementCount }
}
handleToolbarItemClick(ToolbarViewItems.PROFILE_ITEM) }> diff --git a/src/views/toolbar/me/ToolbarMeView.types.ts b/src/views/toolbar/me/ToolbarMeView.types.ts index 002c610b..aeb05a5b 100644 --- a/src/views/toolbar/me/ToolbarMeView.types.ts +++ b/src/views/toolbar/me/ToolbarMeView.types.ts @@ -1,5 +1,6 @@ export interface ToolbarMeViewProps { + unseenAchievementCount: number; handleToolbarItemClick: (item: string) => void; } From f69b40bbfe58d4e2f4c78dd6b44e431f7ddb5959 Mon Sep 17 00:00:00 2001 From: Bill Date: Wed, 29 Sep 2021 22:30:01 -0400 Subject: [PATCH 07/73] Update BadgeImageView --- .../shared/badge-image/BadgeImageView.tsx | 54 ++++++++++++++++--- .../badge-image/BadgeImageView.types.ts | 6 ++- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/views/shared/badge-image/BadgeImageView.tsx b/src/views/shared/badge-image/BadgeImageView.tsx index 37c2a70f..2ca3259f 100644 --- a/src/views/shared/badge-image/BadgeImageView.tsx +++ b/src/views/shared/badge-image/BadgeImageView.tsx @@ -1,16 +1,53 @@ import { BadgeImageReadyEvent, NitroSprite, TextureUtils } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useState } from 'react'; +import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { GetSessionDataManager, LocalizeBadgeDescription, LocalizeBadgeName, LocalizeText } from '../../../api'; +import { NitroLayoutBase } from '../../../layout/base'; import { BadgeInformationView } from './badge-info/BadgeInformationView'; import { BadgeImageViewProps } from './BadgeImageView.types'; export const BadgeImageView: FC = props => { - const { badgeCode = null, isGroup = false, showInfo = false, customTitle = null } = props; - + const { badgeCode = null, isGroup = false, showInfo = false, customTitle = null, isGrayscale = false, scale = 1, className = '', style = null, children = null, ...rest } = props; const [ badgeUrl, setBadgeUrl ] = useState(''); const [ isListening, setIsListening ] = useState(true); + const getScaleClass = useMemo(() => + { + let scaleName = scale.toString(); + + if(scale === .5) scaleName = '0-5'; + + else if(scale === .75) scaleName = '0-75'; + + else if(scale === 1.25) scaleName = '1-25'; + + else if(scale === 1.50) scaleName = '1-50'; + + return `scale-${ scaleName }`; + }, [ scale ]); + + const getClassName = useMemo(() => + { + let newClassName = 'badge-image'; + + if(isGrayscale) newClassName += ' grayscale'; + + if((scale !== 1) && getScaleClass.length) newClassName += ` ${ getScaleClass }`; + + if(className && className.length) newClassName += ' ' + className; + + return newClassName; + }, [ className, isGrayscale, scale, getScaleClass ]); + + const getStyle = useMemo(() => + { + const newStyle = { ...style }; + + if(badgeUrl && badgeUrl.length) newStyle.backgroundImage = `url(${ badgeUrl })`; + + return newStyle; + }, [ style, badgeUrl ]); + const onBadgeImageReadyEvent = useCallback((event: BadgeImageReadyEvent) => { if(event.badgeId !== badgeCode) return; @@ -27,6 +64,8 @@ export const BadgeImageView: FC = props => useEffect(() => { + if(!badgeCode || !badgeCode.length) return; + const existing = (isGroup) ? GetSessionDataManager().loadGroupBadgeImage(badgeCode) : GetSessionDataManager().loadBadgeImage(badgeCode); if(!existing) @@ -51,7 +90,10 @@ export const BadgeImageView: FC = props => const url = `url('${ badgeUrl }')`; - return
- { showInfo && } -
; + return ( + + { showInfo && } + { children } + + ); } diff --git a/src/views/shared/badge-image/BadgeImageView.types.ts b/src/views/shared/badge-image/BadgeImageView.types.ts index 76fba1c5..0fe7726c 100644 --- a/src/views/shared/badge-image/BadgeImageView.types.ts +++ b/src/views/shared/badge-image/BadgeImageView.types.ts @@ -1,7 +1,11 @@ -export interface BadgeImageViewProps +import { NitroLayoutBaseProps } from '../../../layout/base'; + +export interface BadgeImageViewProps extends NitroLayoutBaseProps { badgeCode: string; isGroup?: boolean; showInfo?: boolean; customTitle?: string; + isGrayscale?: boolean; + scale?: number; } From 33aac67bbe3be31544d75da7dd61376242499ae1 Mon Sep 17 00:00:00 2001 From: Bill Date: Wed, 29 Sep 2021 22:30:25 -0400 Subject: [PATCH 08/73] Update AvatarEditor --- src/views/avatar-editor/AvatarEditorView.scss | 99 ++++++++-------- src/views/avatar-editor/AvatarEditorView.tsx | 111 +++++++----------- .../common/AvatarEditorAction.ts | 7 ++ .../AvatarEditorFigureActionsView.tsx | 29 +++++ .../AvatarEditorFigureActionsView.types.ts | 5 + .../AvatarEditorFigurePreviewView.tsx | 30 ++++- .../AvatarEditorFigureSetItemView.tsx | 2 +- .../views/model/AvatarEditorModelView.tsx | 29 ++--- .../wardrobe/AvatarEditorWardrobeView.tsx | 10 +- 9 files changed, 180 insertions(+), 142 deletions(-) create mode 100644 src/views/avatar-editor/common/AvatarEditorAction.ts create mode 100644 src/views/avatar-editor/views/figure-actions/AvatarEditorFigureActionsView.tsx create mode 100644 src/views/avatar-editor/views/figure-actions/AvatarEditorFigureActionsView.types.ts diff --git a/src/views/avatar-editor/AvatarEditorView.scss b/src/views/avatar-editor/AvatarEditorView.scss index d0704723..7e2ee641 100644 --- a/src/views/avatar-editor/AvatarEditorView.scss +++ b/src/views/avatar-editor/AvatarEditorView.scss @@ -75,70 +75,71 @@ transform: scale(2); } } +} - .wardrobe-grid { +.nitro-wardrobe-grid { + --nitro-grid-column-min-width: 80px !important; + + .grid-item { + height: 140px; + max-height: 140px; + background-color: $ghost; + + &:after { + position: absolute; + content: ''; + top: 75%; + bottom: 0; + left: 0; + right: 0; + border-radius: 50%; + background-color: $gray-chateau; + box-shadow: 0 0 8px 2px rgba($white,.6); + transform: scale(2); + } + + .avatar-image { + position: absolute; + bottom: 0; + background-position-y: -23px !important; + z-index: 4; + } + + .figure-button-container { + background-color: $gray-chateau; + z-index: 3; + } + } + + .grid-item-container { + height: 142px !important; + max-height: 142px !important; .grid-item { - height: 140px; - max-height: 140px; background-color: $ghost; - &:after { - position: absolute; - content: ''; - top: 75%; - bottom: 0; - left: 0; - right: 0; - border-radius: 50%; - background-color: $gray-chateau; - box-shadow: 0 0 8px 2px rgba($white,.6); - transform: scale(2); - } - .avatar-image { position: absolute; bottom: 0; background-position-y: -23px !important; - z-index: 4; + z-index: 3; } .figure-button-container { background-color: $gray-chateau; - z-index: 3; + z-index: 2; } - } - .grid-item-container { - height: 142px !important; - max-height: 142px !important; - - .grid-item { - background-color: $ghost; - - .avatar-image { - position: absolute; - bottom: 0; - background-position-y: -23px !important; - z-index: 3; - } - - .figure-button-container { - background-color: $gray-chateau; - z-index: 2; - } - - &:after { - position: absolute; - content: ''; - height: 50%; - bottom: 0; - left: 0; - right: 0; - background-color: $gray-chateau; - box-shadow: 0 0 8px 2px rgba($white,.6); - z-index: 1; - } + &:after { + position: absolute; + content: ''; + height: 50%; + bottom: 0; + left: 0; + right: 0; + background-color: $gray-chateau; + box-shadow: 0 0 8px 2px rgba($white,.6); + z-index: 1; } } } diff --git a/src/views/avatar-editor/AvatarEditorView.tsx b/src/views/avatar-editor/AvatarEditorView.tsx index c840151b..1e27a62e 100644 --- a/src/views/avatar-editor/AvatarEditorView.tsx +++ b/src/views/avatar-editor/AvatarEditorView.tsx @@ -1,11 +1,12 @@ -import { AvatarDirectionAngle, 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 { GetAvatarRenderManager, GetClubMemberLevel, GetSessionDataManager, LocalizeText } from '../../api'; import { AvatarEditorEvent } from '../../events/avatar-editor'; import { CreateMessageHook, SendMessageHook } from '../../hooks'; import { useUiEvent } from '../../hooks/events/ui/ui-event'; -import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../layout'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, NitroLayoutGrid, NitroLayoutGridColumn } from '../../layout'; import { AvatarEditorViewProps } from './AvatarEditorView.types'; +import { AvatarEditorAction } from './common/AvatarEditorAction'; import { AvatarEditorUtilities } from './common/AvatarEditorUtilities'; import { BodyModel } from './common/BodyModel'; import { FigureData } from './common/FigureData'; @@ -14,6 +15,7 @@ import { HeadModel } from './common/HeadModel'; import { IAvatarEditorCategoryModel } from './common/IAvatarEditorCategoryModel'; import { LegModel } from './common/LegModel'; import { TorsoModel } from './common/TorsoModel'; +import { AvatarEditorFigureActionsView } from './views/figure-actions/AvatarEditorFigureActionsView'; import { AvatarEditorFigurePreviewView } from './views/figure-preview/AvatarEditorFigurePreviewView'; import { AvatarEditorModelView } from './views/model/AvatarEditorModelView'; import { AvatarEditorWardrobeView } from './views/wardrobe/AvatarEditorWardrobeView'; @@ -21,6 +23,10 @@ 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_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; +const ACTION_CLEAR = 'action_clear'; +const ACTION_RESET = 'action_reset'; +const ACTION_RANDOMIZE = 'action_randomize'; +const ACTION_SAVE = 'action_save'; export const AvatarEditorView: FC = props => { @@ -157,46 +163,30 @@ export const AvatarEditorView: FC = props => } }, [ figures, figureData ]); - const clearFigure = useCallback(() => + const processAction = useCallback((action: string) => { - loadAvatarInEditor(figureData.getFigureStringWithFace(0, false), figureData.gender, false); - resetCategories(); - }, [ figureData, loadAvatarInEditor, resetCategories ]); - - const resetFigure = useCallback(() => - { - loadAvatarInEditor(lastFigure, lastGender); - resetCategories(); - }, [ lastFigure, lastGender, loadAvatarInEditor, resetCategories ]); - - const randomizeFigure = useCallback(() => - { - const figure = generateRandomFigure(figureData, figureData.gender, GetClubMemberLevel(), figureSetIds, [ FigureData.FACE ]); - - loadAvatarInEditor(figure, figureData.gender, false); - resetCategories(); - }, [ figureData, figureSetIds, loadAvatarInEditor, resetCategories ]); - - const rotateFigure = useCallback((direction: number) => - { - if(direction < AvatarDirectionAngle.MIN_DIRECTION) + switch(action) { - direction = (AvatarDirectionAngle.MAX_DIRECTION + (direction + 1)); + case AvatarEditorAction.ACTION_CLEAR: + loadAvatarInEditor(figureData.getFigureStringWithFace(0, false), figureData.gender, false); + resetCategories(); + return; + case AvatarEditorAction.ACTION_RESET: + loadAvatarInEditor(lastFigure, lastGender); + resetCategories(); + return; + case AvatarEditorAction.ACTION_RANDOMIZE: + const figure = generateRandomFigure(figureData, figureData.gender, GetClubMemberLevel(), figureSetIds, [ FigureData.FACE ]); + + loadAvatarInEditor(figure, figureData.gender, false); + resetCategories(); + return; + case AvatarEditorAction.ACTION_SAVE: + SendMessageHook(new UserFigureComposer(figureData.gender, figureData.getFigureString())); + setIsVisible(false); + return; } - - if(direction > AvatarDirectionAngle.MAX_DIRECTION) - { - direction = (direction - (AvatarDirectionAngle.MAX_DIRECTION + 1)); - } - - figureData.direction = direction; - }, [ figureData ]); - - const saveFigure = useCallback(() => - { - SendMessageHook(new UserFigureComposer(figureData.gender, figureData.getFigureString())); - setIsVisible(false); - }, [ figureData ]); + }, [ figureData, lastFigure, lastGender, figureSetIds, loadAvatarInEditor, resetCategories ]) const setGender = useCallback((gender: string) => { @@ -295,37 +285,18 @@ export const AvatarEditorView: FC = props => -
-
- { (activeCategory && !isWardrobeVisible) && } - { isWardrobeVisible && } -
-
-
- -
-
-
- rotateFigure(figureData.direction + 1) } /> - rotateFigure(figureData.direction - 1) } /> -
-
-
-
- - - -
- -
-
-
+ + + { (activeCategory && !isWardrobeVisible) && + } + { isWardrobeVisible && + } + + + + + + ); diff --git a/src/views/avatar-editor/common/AvatarEditorAction.ts b/src/views/avatar-editor/common/AvatarEditorAction.ts new file mode 100644 index 00000000..064d6dff --- /dev/null +++ b/src/views/avatar-editor/common/AvatarEditorAction.ts @@ -0,0 +1,7 @@ +export class AvatarEditorAction +{ + public static ACTION_SAVE: string = 'AEA_ACTION_SAVE'; + public static ACTION_CLEAR: string = 'AEA_ACTION_CLEAR'; + public static ACTION_RESET: string = 'AEA_ACTION_RESET'; + public static ACTION_RANDOMIZE: string = 'AEA_ACTION_RANDOMIZE'; +} diff --git a/src/views/avatar-editor/views/figure-actions/AvatarEditorFigureActionsView.tsx b/src/views/avatar-editor/views/figure-actions/AvatarEditorFigureActionsView.tsx new file mode 100644 index 00000000..e7ebabe8 --- /dev/null +++ b/src/views/avatar-editor/views/figure-actions/AvatarEditorFigureActionsView.tsx @@ -0,0 +1,29 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../../api'; +import { NitroLayoutButton, NitroLayoutButtonGroup, NitroLayoutFlexColumn } from '../../../../layout'; +import { AvatarEditorAction } from '../../common/AvatarEditorAction'; +import { AvatarEditorFigureActionsViewProps } from './AvatarEditorFigureActionsView.types'; + +export const AvatarEditorFigureActionsView: FC = props => +{ + const { processAction = null } = props; + + return ( + + + processAction(AvatarEditorAction.ACTION_RESET) }> + + + processAction(AvatarEditorAction.ACTION_CLEAR) }> + + + processAction(AvatarEditorAction.ACTION_RANDOMIZE) }> + + + + processAction(AvatarEditorAction.ACTION_SAVE) }> + { LocalizeText('avatareditor.save') } + + + ) +} diff --git a/src/views/avatar-editor/views/figure-actions/AvatarEditorFigureActionsView.types.ts b/src/views/avatar-editor/views/figure-actions/AvatarEditorFigureActionsView.types.ts new file mode 100644 index 00000000..a144113f --- /dev/null +++ b/src/views/avatar-editor/views/figure-actions/AvatarEditorFigureActionsView.types.ts @@ -0,0 +1,5 @@ + +export interface AvatarEditorFigureActionsViewProps +{ + processAction: (action: string) => void; +} diff --git a/src/views/avatar-editor/views/figure-preview/AvatarEditorFigurePreviewView.tsx b/src/views/avatar-editor/views/figure-preview/AvatarEditorFigurePreviewView.tsx index 352f7e58..f29d21e3 100644 --- a/src/views/avatar-editor/views/figure-preview/AvatarEditorFigurePreviewView.tsx +++ b/src/views/avatar-editor/views/figure-preview/AvatarEditorFigurePreviewView.tsx @@ -1,4 +1,7 @@ +import { AvatarDirectionAngle } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useState } from 'react'; +import { NitroLayoutFlexColumn } from '../../../../layout'; +import { NitroLayoutBase } from '../../../../layout/base'; import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView'; import { AvatarEditorFigurePreviewViewProps } from './AvatarEditorFigurePreviewView.types'; @@ -12,6 +15,21 @@ export const AvatarEditorFigurePreviewView: FC (prevValue + 1)); }, []); + const rotateFigure = useCallback((direction: number) => + { + if(direction < AvatarDirectionAngle.MIN_DIRECTION) + { + direction = (AvatarDirectionAngle.MAX_DIRECTION + (direction + 1)); + } + + if(direction > AvatarDirectionAngle.MAX_DIRECTION) + { + direction = (direction - (AvatarDirectionAngle.MAX_DIRECTION + 1)); + } + + figureData.direction = direction; + }, [ figureData ]); + useEffect(() => { if(!figureData) return; @@ -24,5 +42,15 @@ export const AvatarEditorFigurePreviewView: FC + return ( + + + + + + rotateFigure(figureData.direction + 1) } /> + rotateFigure(figureData.direction - 1) } /> + + + ); } diff --git a/src/views/avatar-editor/views/figure-set-item/AvatarEditorFigureSetItemView.tsx b/src/views/avatar-editor/views/figure-set-item/AvatarEditorFigureSetItemView.tsx index c79dc0a7..16ab3ef7 100644 --- a/src/views/avatar-editor/views/figure-set-item/AvatarEditorFigureSetItemView.tsx +++ b/src/views/avatar-editor/views/figure-set-item/AvatarEditorFigureSetItemView.tsx @@ -24,7 +24,7 @@ export const AvatarEditorFigureSetItemView: FC onClick(partItem) }> + onClick(partItem) }> { partItem.isHC && } { partItem.isClear && } { partItem.isSellable && } diff --git a/src/views/avatar-editor/views/model/AvatarEditorModelView.tsx b/src/views/avatar-editor/views/model/AvatarEditorModelView.tsx index 7bce299c..1fe1a562 100644 --- a/src/views/avatar-editor/views/model/AvatarEditorModelView.tsx +++ b/src/views/avatar-editor/views/model/AvatarEditorModelView.tsx @@ -1,4 +1,5 @@ import { FC, useCallback, useEffect, useState } from 'react'; +import { NitroLayoutFlex, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../layout'; import { CategoryData } from '../../common/CategoryData'; import { FigureData } from '../../common/FigureData'; import { AvatarEditorFigureSetView } from '../figure-set/AvatarEditorFigureSetView'; @@ -46,37 +47,37 @@ export const AvatarEditorModelView: FC = props => if(!model || !activeCategory) return null; return ( -
-
+ + { model.canSetGender && <> -
setGender(FigureData.MALE) }> + setGender(FigureData.MALE) }> -
-
setGender(FigureData.FEMALE) }> + + setGender(FigureData.FEMALE) }> -
+ } { !model.canSetGender && model.categories && (model.categories.size > 0) && Array.from(model.categories.keys()).map(name => { const category = model.categories.get(name); return ( -
selectCategory(name) }> + selectCategory(name) }> -
+ ); })} -
-
+ + -
-
+ + { (maxPaletteCount >= 1) && } { (maxPaletteCount === 2) && } -
-
+ + ); } diff --git a/src/views/avatar-editor/views/wardrobe/AvatarEditorWardrobeView.tsx b/src/views/avatar-editor/views/wardrobe/AvatarEditorWardrobeView.tsx index 840736ce..1071bb2e 100644 --- a/src/views/avatar-editor/views/wardrobe/AvatarEditorWardrobeView.tsx +++ b/src/views/avatar-editor/views/wardrobe/AvatarEditorWardrobeView.tsx @@ -65,12 +65,8 @@ export const AvatarEditorWardrobeView: FC = props }, [ savedFigures, saveFigureAtWardrobeIndex, wearFigureAtIndex ]); return ( -
-
- - { figures } - -
-
+ + { figures } + ); } From eadb3d5d1e62aba925564525fa39876c6f7a7bd8 Mon Sep 17 00:00:00 2001 From: Bill Date: Wed, 29 Sep 2021 22:31:31 -0400 Subject: [PATCH 09/73] Update catalog --- src/views/catalog/CatalogView.tsx | 16 +-- .../navigation/CatalogNavigationView.scss | 2 +- .../navigation/CatalogNavigationView.tsx | 20 ++- .../page-details/CatalogPageDetailsView.tsx | 5 +- .../CatalogLayoutBadgeDisplayView.tsx | 20 +-- .../default/CatalogLayoutDefaultView.tsx | 13 +- .../CatalogLayoutFrontpage4View.scss | 34 +++--- .../CatalogLayoutFrontpage4View.tsx | 30 ++--- .../item/CatalogLayoutFrontPageItemView.tsx | 35 ++++++ .../CatalogLayoutFrontPageItemView.types.ts | 7 ++ .../CatalogLayoutGuildCustomFurniView.tsx | 65 ++++------ .../CatalogLayoutGuildForumView.tsx | 49 +++----- .../CatalogLayoutGuildFrontpageView.tsx | 27 +++-- .../page/layout/pets/CatalogLayoutPetView.tsx | 34 +++--- .../layout/pets3/CatalogLayoutPets3View.tsx | 32 ++--- .../CatalogLayoutSingleBundleView.tsx | 14 +-- .../spaces-new/CatalogLayoutSpacesView.tsx | 17 +-- .../trophies/CatalogLayoutTrophiesView.tsx | 13 +- .../vip-buy/CatalogLayoutVipBuyView.tsx | 114 +++++++++--------- .../CatalogProductPreviewView.tsx | 25 ++-- .../CatalogProductPreviewView.types.ts | 2 + .../CatalogRedeemVoucherView.tsx | 2 +- .../select-group/CatalogSelectGroupView.tsx | 40 ++++++ .../CatalogSelectGroupView.types.ts | 7 ++ 24 files changed, 341 insertions(+), 282 deletions(-) create mode 100644 src/views/catalog/views/page/layout/frontpage4/item/CatalogLayoutFrontPageItemView.tsx create mode 100644 src/views/catalog/views/page/layout/frontpage4/item/CatalogLayoutFrontPageItemView.types.ts create mode 100644 src/views/catalog/views/select-group/CatalogSelectGroupView.tsx create mode 100644 src/views/catalog/views/select-group/CatalogSelectGroupView.types.ts diff --git a/src/views/catalog/CatalogView.tsx b/src/views/catalog/CatalogView.tsx index ec06d572..403166bf 100644 --- a/src/views/catalog/CatalogView.tsx +++ b/src/views/catalog/CatalogView.tsx @@ -4,7 +4,7 @@ import { AddEventLinkTracker, GetRoomEngine, LocalizeText, RemoveLinkEventTracke import { CatalogEvent } from '../../events'; import { useUiEvent } from '../../hooks/events/ui/ui-event'; import { SendMessageHook } from '../../hooks/messages/message-event'; -import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../layout'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, NitroLayoutGrid, NitroLayoutGridColumn } from '../../layout'; import { CatalogMessageHandler } from './CatalogMessageHandler'; import { CatalogMode, CatalogViewProps } from './CatalogView.types'; import { BuildCatalogPageTree } from './common/CatalogUtilities'; @@ -184,7 +184,7 @@ export const CatalogView: FC = props => }, []); const currentNavigationPage = ((searchResult && searchResult.page) || currentTab); - const navigationHidden = (pageParser && pageParser.frontPageItems.length); + const navigationHidden = !!(pageParser && pageParser.frontPageItems.length); return ( @@ -203,15 +203,15 @@ export const CatalogView: FC = props => }) } -
+ { currentNavigationPage && !navigationHidden && -
+ -
} -
+ } + -
-
+ +
} diff --git a/src/views/catalog/views/navigation/CatalogNavigationView.scss b/src/views/catalog/views/navigation/CatalogNavigationView.scss index cd6cc820..d76fdbf9 100644 --- a/src/views/catalog/views/navigation/CatalogNavigationView.scss +++ b/src/views/catalog/views/navigation/CatalogNavigationView.scss @@ -1,4 +1,4 @@ -.nitro-catalog-navigation-grid { +.nitro-catalog-navigation-grid-container { border-radius: 0.25rem; border-color: #B6BEC5 !important; background-color: #CDD3D9; diff --git a/src/views/catalog/views/navigation/CatalogNavigationView.tsx b/src/views/catalog/views/navigation/CatalogNavigationView.tsx index cb8efe9c..b7bf41ed 100644 --- a/src/views/catalog/views/navigation/CatalogNavigationView.tsx +++ b/src/views/catalog/views/navigation/CatalogNavigationView.tsx @@ -1,6 +1,6 @@ import { INodeData } from '@nitrots/nitro-renderer'; import { FC, useEffect } from 'react'; -import { NitroCardGridView } from '../../../../layout'; +import { NitroCardGridView, NitroLayoutFlexColumn } from '../../../../layout'; import { CatalogSearchView } from '../search/CatalogSearchView'; import { CatalogNavigationViewProps } from './CatalogNavigationView.types'; import { CatalogNavigationSetView } from './set/CatalogNavigationSetView'; @@ -24,15 +24,13 @@ export const CatalogNavigationView: FC = props => }, [ page ]); return ( -
-
- -
- - - -
-
-
+ + + + + + + + ); } diff --git a/src/views/catalog/views/page-details/CatalogPageDetailsView.tsx b/src/views/catalog/views/page-details/CatalogPageDetailsView.tsx index 390aed62..d5a8ad6c 100644 --- a/src/views/catalog/views/page-details/CatalogPageDetailsView.tsx +++ b/src/views/catalog/views/page-details/CatalogPageDetailsView.tsx @@ -1,4 +1,5 @@ import { FC } from 'react'; +import { NitroLayoutFlexColumn } from '../../../../layout'; import { GetCatalogPageImage, GetCatalogPageText } from '../../common/CatalogUtilities'; import { CatalogPageDetailsViewProps } from './CatalogPageDetailsView.types'; @@ -11,9 +12,9 @@ export const CatalogPageDetailsView: FC = props => const imageUrl = GetCatalogPageImage(pageParser, 1); return ( -
+ { imageUrl && }
-
+
); } diff --git a/src/views/catalog/views/page/layout/badge-display/CatalogLayoutBadgeDisplayView.tsx b/src/views/catalog/views/page/layout/badge-display/CatalogLayoutBadgeDisplayView.tsx index 1826c10a..38156bd6 100644 --- a/src/views/catalog/views/page/layout/badge-display/CatalogLayoutBadgeDisplayView.tsx +++ b/src/views/catalog/views/page/layout/badge-display/CatalogLayoutBadgeDisplayView.tsx @@ -4,6 +4,8 @@ import { LocalizeText } from '../../../../../../api'; import { InventoryBadgesUpdatedEvent, SetRoomPreviewerStuffDataEvent } from '../../../../../../events'; import { InventoryBadgesRequestEvent } from '../../../../../../events/inventory/InventoryBadgesRequestEvent'; import { dispatchUiEvent, useUiEvent } from '../../../../../../hooks'; +import { NitroLayoutFlexColumn, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout'; +import { NitroLayoutBase } from '../../../../../../layout/base'; import { NitroCardGridItemView } from '../../../../../../layout/card/grid/item/NitroCardGridItemView'; import { NitroCardGridView } from '../../../../../../layout/card/grid/NitroCardGridView'; import { BadgeImageView } from '../../../../../shared/badge-image/BadgeImageView'; @@ -53,11 +55,11 @@ export const CatalogLayoutBadgeDisplayView: FC -
+ + -
-
{ LocalizeText('catalog_selectbadge') }
+ + { LocalizeText('catalog_selectbadge') } { badges && (badges.length > 0) && badges.map(code => { @@ -68,11 +70,11 @@ export const CatalogLayoutBadgeDisplayView: FC -
-
-
+ + + -
-
+ + ); } diff --git a/src/views/catalog/views/page/layout/default/CatalogLayoutDefaultView.tsx b/src/views/catalog/views/page/layout/default/CatalogLayoutDefaultView.tsx index 86d2bd1b..1ec8d867 100644 --- a/src/views/catalog/views/page/layout/default/CatalogLayoutDefaultView.tsx +++ b/src/views/catalog/views/page/layout/default/CatalogLayoutDefaultView.tsx @@ -1,4 +1,5 @@ import { FC } from 'react'; +import { NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout'; import { useCatalogContext } from '../../../../context/CatalogContext'; import { CatalogPageOffersView } from '../../offers/CatalogPageOffersView'; import { CatalogProductPreviewView } from '../../product-preview/CatalogProductPreviewView'; @@ -13,13 +14,13 @@ export const CatalogLayoutDefaultView: FC = props const product = ((activeOffer && activeOffer.products[0]) || null); return ( -
-
+ + -
-
+ + -
-
+ + ); } diff --git a/src/views/catalog/views/page/layout/frontpage4/CatalogLayoutFrontpage4View.scss b/src/views/catalog/views/page/layout/frontpage4/CatalogLayoutFrontpage4View.scss index a08568d7..4d79a2c7 100644 --- a/src/views/catalog/views/page/layout/frontpage4/CatalogLayoutFrontpage4View.scss +++ b/src/views/catalog/views/page/layout/frontpage4/CatalogLayoutFrontpage4View.scss @@ -1,23 +1,17 @@ -.nitro-catalog-layout-frontpage4 { - max-height:600px; +.nitro-front-page-item { + background-position: center; + background-repeat: no-repeat; + cursor: pointer; - .front-page-item { - position: relative; - border-radius: $border-radius; - background-position: center; - background-repeat: no-repeat; - cursor: pointer; - - .front-page-item-caption { - position: absolute; - background: rgba(0, 0, 0, .5); - color: #fff; - font-size: 16px; - border-radius: 5px; - margin: 10px; - padding: 5px 15px; - bottom: 0; - text-shadow: 2px 2px rgba(0, 0, 0, .2); - } + .front-page-item-caption { + position: absolute; + background: rgba(0, 0, 0, .5); + color: #fff; + font-size: 16px; + border-radius: 5px; + margin: 10px; + padding: 5px 15px; + bottom: 0; + text-shadow: 2px 2px rgba(0, 0, 0, .2); } } diff --git a/src/views/catalog/views/page/layout/frontpage4/CatalogLayoutFrontpage4View.tsx b/src/views/catalog/views/page/layout/frontpage4/CatalogLayoutFrontpage4View.tsx index 7a2c67a3..9f04e00e 100644 --- a/src/views/catalog/views/page/layout/frontpage4/CatalogLayoutFrontpage4View.tsx +++ b/src/views/catalog/views/page/layout/frontpage4/CatalogLayoutFrontpage4View.tsx @@ -1,9 +1,11 @@ import { FrontPageItem } from '@nitrots/nitro-renderer'; import { FC, useCallback, useMemo } from 'react'; import { CreateLinkEvent, GetConfiguration } from '../../../../../../api'; +import { NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout'; import { GetCatalogPageText } from '../../../../common/CatalogUtilities'; import { CatalogRedeemVoucherView } from '../../redeem-voucher/CatalogRedeemVoucherView'; import { CatalogLayoutFrontpage4ViewProps } from './CatalogLayoutFrontpage4View.types'; +import { CatalogLayoutFrontPageItemView } from './item/CatalogLayoutFrontPageItemView'; export const CatalogLayoutFrontpage4View: FC = props => { @@ -31,28 +33,20 @@ export const CatalogLayoutFrontpage4View: FC = if(!pageParser) return null; return ( -
-
+ + { pageParser.frontPageItems[0] && -
selectItem(pageParser.frontPageItems[0]) }> -
{ pageParser.frontPageItems[0].itemName }
-
} -
-
+ selectItem(pageParser.frontPageItems[0]) } /> } + + { pageParser.frontPageItems[1] && -
selectItem(pageParser.frontPageItems[1]) }> -
{ pageParser.frontPageItems[1].itemName }
-
} + selectItem(pageParser.frontPageItems[1]) } /> } { pageParser.frontPageItems[2] && -
selectItem(pageParser.frontPageItems[2]) }> -
{ pageParser.frontPageItems[2].itemName }
-
} + selectItem(pageParser.frontPageItems[2]) } /> } { pageParser.frontPageItems[3] && -
selectItem(pageParser.frontPageItems[3]) }> -
{ pageParser.frontPageItems[3].itemName }
-
} + selectItem(pageParser.frontPageItems[3]) } /> } -
-
+ + ); } diff --git a/src/views/catalog/views/page/layout/frontpage4/item/CatalogLayoutFrontPageItemView.tsx b/src/views/catalog/views/page/layout/frontpage4/item/CatalogLayoutFrontPageItemView.tsx new file mode 100644 index 00000000..9ead2796 --- /dev/null +++ b/src/views/catalog/views/page/layout/frontpage4/item/CatalogLayoutFrontPageItemView.tsx @@ -0,0 +1,35 @@ +import { FC, useMemo } from 'react'; +import { GetConfiguration } from '../../../../../../../api'; +import { NitroLayoutBase } from '../../../../../../../layout/base'; +import { CatalogLayoutFrontPageItemViewProps } from './CatalogLayoutFrontPageItemView.types'; + +export const CatalogLayoutFrontPageItemView: FC = props => +{ + const { item = null, className = '', style = null, ...rest } = props; + + const getClassName = useMemo(() => + { + let newClassName = 'position-relative rounded h-100 nitro-front-page-item'; + + if(className && className.length) newClassName += ' ' + className; + + return newClassName; + }, [ className ]); + + const getStyle = useMemo(() => + { + const newStyle = { ...style }; + + newStyle.backgroundImage = `url('${ GetConfiguration('image.library.url') }${ item.itemPromoImage }')`; + + return newStyle; + }, [ style, item ]); + + if(!item) return null; + + return ( + +
{ item.itemName }
+
+ ); +} diff --git a/src/views/catalog/views/page/layout/frontpage4/item/CatalogLayoutFrontPageItemView.types.ts b/src/views/catalog/views/page/layout/frontpage4/item/CatalogLayoutFrontPageItemView.types.ts new file mode 100644 index 00000000..6f6135d1 --- /dev/null +++ b/src/views/catalog/views/page/layout/frontpage4/item/CatalogLayoutFrontPageItemView.types.ts @@ -0,0 +1,7 @@ +import { FrontPageItem } from '@nitrots/nitro-renderer'; +import { DetailedHTMLProps, HTMLAttributes } from 'react'; + +export interface CatalogLayoutFrontPageItemViewProps extends DetailedHTMLProps, HTMLDivElement> +{ + item: FrontPageItem; +} diff --git a/src/views/catalog/views/page/layout/guild-custom-furni/CatalogLayoutGuildCustomFurniView.tsx b/src/views/catalog/views/page/layout/guild-custom-furni/CatalogLayoutGuildCustomFurniView.tsx index 7d60016a..2ee74233 100644 --- a/src/views/catalog/views/page/layout/guild-custom-furni/CatalogLayoutGuildCustomFurniView.tsx +++ b/src/views/catalog/views/page/layout/guild-custom-furni/CatalogLayoutGuildCustomFurniView.tsx @@ -1,25 +1,28 @@ import { CatalogGroupsComposer, StringDataType } from '@nitrots/nitro-renderer'; -import { FC, useEffect, useState } from 'react'; -import { LocalizeText } from '../../../../../../api'; +import { FC, useEffect, useMemo, useState } from 'react'; import { SetRoomPreviewerStuffDataEvent } from '../../../../../../events'; import { dispatchUiEvent } from '../../../../../../hooks'; import { SendMessageHook } from '../../../../../../hooks/messages'; -import { BadgeImageView } from '../../../../../shared/badge-image/BadgeImageView'; -import { GetOfferName } from '../../../../common/CatalogUtilities'; +import { NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout'; import { useCatalogContext } from '../../../../context/CatalogContext'; -import { CatalogRoomPreviewerView } from '../../../catalog-room-previewer/CatalogRoomPreviewerView'; +import { CatalogSelectGroupView } from '../../../select-group/CatalogSelectGroupView'; import { CatalogPageOffersView } from '../../offers/CatalogPageOffersView'; -import { CatalogPurchaseView } from '../../purchase/CatalogPurchaseView'; +import { CatalogProductPreviewView } from '../../product-preview/CatalogProductPreviewView'; import { CatalogLayoutGuildCustomFurniViewProps } from './CatalogLayoutGuildCustomFurniView.types'; export const CatalogLayouGuildCustomFurniView: FC = props => { const { roomPreviewer = null, pageParser = null } = props; - + const [ selectedGroupIndex, setSelectedGroupIndex ] = useState(0); const { catalogState = null } = useCatalogContext(); const { activeOffer = null, groups = null } = catalogState; - - const [ selectedGroupIndex, setSelectedGroupIndex ] = useState(0); + + const selectedGroup = useMemo(() => + { + if(!groups || !groups.length) return; + + return groups[selectedGroupIndex]; + }, [ groups, selectedGroupIndex ]); useEffect(() => { @@ -44,41 +47,17 @@ export const CatalogLayouGuildCustomFurniView: FC -
+ + -
- { product && -
- { groups[selectedGroupIndex] &&
- -
} - -
{ GetOfferName(activeOffer) }
- { groups.length === 0 &&
- { LocalizeText('catalog.guild_selector.members_only') } - -
} - { groups.length > 0 && <> -
-
-
-
-
- -
- - } -
} -
- ); + + + + + + + + ); } diff --git a/src/views/catalog/views/page/layout/guild-forum/CatalogLayoutGuildForumView.tsx b/src/views/catalog/views/page/layout/guild-forum/CatalogLayoutGuildForumView.tsx index add86a9c..c7c3204f 100644 --- a/src/views/catalog/views/page/layout/guild-forum/CatalogLayoutGuildForumView.tsx +++ b/src/views/catalog/views/page/layout/guild-forum/CatalogLayoutGuildForumView.tsx @@ -1,12 +1,13 @@ import { CatalogGroupsComposer } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; -import { LocalizeText } from '../../../../../../api'; import { SendMessageHook } from '../../../../../../hooks/messages'; -import { BadgeImageView } from '../../../../../shared/badge-image/BadgeImageView'; +import { NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout'; +import { NitroLayoutBase } from '../../../../../../layout/base'; import { GetCatalogPageText } from '../../../../common/CatalogUtilities'; import { useCatalogContext } from '../../../../context/CatalogContext'; import { CatalogActions } from '../../../../reducers/CatalogReducer'; -import { CatalogPurchaseView } from '../../purchase/CatalogPurchaseView'; +import { CatalogSelectGroupView } from '../../../select-group/CatalogSelectGroupView'; +import { CatalogProductPreviewView } from '../../product-preview/CatalogProductPreviewView'; import { CatalogLayoutGuildForumViewProps } from './CatalogLayoutGuildForumView.types'; export const CatalogLayouGuildForumView: FC = props => @@ -36,35 +37,15 @@ export const CatalogLayouGuildForumView: FC = }, [ dispatchCatalogState, pageParser ]); return ( -
-
-
-
- { product &&
- { groups.length === 0 &&
- { LocalizeText('catalog.guild_selector.members_only') } - -
} - { groups[selectedGroupIndex] &&
- -
} - { groups.length > 0 && <> -
-
-
-
-
- -
- { groups[selectedGroupIndex].hasForum &&
{ LocalizeText('catalog.alert.group_has_forum') }
} - - } -
} -
- ); + + + + + + + + + + + ); } diff --git a/src/views/catalog/views/page/layout/guild-frontpage/CatalogLayoutGuildFrontpageView.tsx b/src/views/catalog/views/page/layout/guild-frontpage/CatalogLayoutGuildFrontpageView.tsx index 06dbb7f9..2ad9bd2e 100644 --- a/src/views/catalog/views/page/layout/guild-frontpage/CatalogLayoutGuildFrontpageView.tsx +++ b/src/views/catalog/views/page/layout/guild-frontpage/CatalogLayoutGuildFrontpageView.tsx @@ -1,28 +1,29 @@ import { FC } from 'react'; import { CreateLinkEvent, LocalizeText } from '../../../../../../api'; +import { NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout'; +import { NitroLayoutBase } from '../../../../../../layout/base'; import { GetCatalogPageImage, GetCatalogPageText } from '../../../../common/CatalogUtilities'; -import { useCatalogContext } from '../../../../context/CatalogContext'; import { CatalogLayoutGuildFrontpageViewProps } from './CatalogLayoutGuildFrontpageView.types'; export const CatalogLayouGuildFrontpageView: FC = props => { const { pageParser = null } = props; - - const { catalogState = null, dispatchCatalogState = null } = useCatalogContext(); return ( -
-
-
-
-
+ + + + + + +
-
-
CreateLinkEvent('groups/create') }> - -
-
+
CreateLinkEvent('groups/create') }> + +
+ + ); } diff --git a/src/views/catalog/views/page/layout/pets/CatalogLayoutPetView.tsx b/src/views/catalog/views/page/layout/pets/CatalogLayoutPetView.tsx index f68c2c50..1d4b2ad3 100644 --- a/src/views/catalog/views/page/layout/pets/CatalogLayoutPetView.tsx +++ b/src/views/catalog/views/page/layout/pets/CatalogLayoutPetView.tsx @@ -2,7 +2,8 @@ import { ColorConverter, GetSellablePetPalettesComposer, SellablePetPaletteData import { FC, useEffect, useMemo, useState } from 'react'; import { GetProductDataForLocalization, LocalizeText } from '../../../../../../api'; import { SendMessageHook } from '../../../../../../hooks/messages/message-event'; -import { NitroCardGridItemView, NitroCardGridView } from '../../../../../../layout'; +import { NitroCardGridItemView, NitroCardGridView, NitroLayoutFlexColumn, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout'; +import { NitroLayoutBase } from '../../../../../../layout/base'; import { PetImageView } from '../../../../../shared/pet-image/PetImageView'; import { GetPetAvailableColors, GetPetIndexFromLocalization } from '../../../../common/CatalogUtilities'; import { useCatalogContext } from '../../../../context/CatalogContext'; @@ -141,8 +142,8 @@ export const CatalogLayoutPetView: FC = props => if(!activeOffer) return null; return ( -
-
+ + { !colorsShowing && (sellablePalettes.length > 0) && sellablePalettes.map((palette, index) => { @@ -159,22 +160,27 @@ export const CatalogLayoutPetView: FC = props => ); })} -
-
+ + { (petIndex === -1) && } { (petIndex >= 0) && <> - + + { roomPreviewer && } { (petIndex > -1 && petIndex <= 7) && - } - -
{ petBreedName }
- + + + } +
+ + { petBreedName } + + } -
-
+ + ); } diff --git a/src/views/catalog/views/page/layout/pets3/CatalogLayoutPets3View.tsx b/src/views/catalog/views/page/layout/pets3/CatalogLayoutPets3View.tsx index 94ab0a81..8e1977f5 100644 --- a/src/views/catalog/views/page/layout/pets3/CatalogLayoutPets3View.tsx +++ b/src/views/catalog/views/page/layout/pets3/CatalogLayoutPets3View.tsx @@ -1,4 +1,6 @@ import { FC } from 'react'; +import { NitroLayoutFlex, NitroLayoutFlexColumn, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout'; +import { NitroLayoutBase } from '../../../../../../layout/base'; import { GetCatalogPageImage, GetCatalogPageText } from '../../../../common/CatalogUtilities'; import { CatalogLayoutPets3ViewProps } from './CatalogLayoutPets3View.types'; @@ -9,21 +11,19 @@ export const CatalogLayoutPets3View: FC = props => const imageUrl = GetCatalogPageImage(pageParser, 1); return ( -
-
-
-
- { imageUrl && } -
-
-
-
-
-
-
-
-
-
-
+ + + + { imageUrl && } + + + + + + + + + + ); } diff --git a/src/views/catalog/views/page/layout/single-bundle/CatalogLayoutSingleBundleView.tsx b/src/views/catalog/views/page/layout/single-bundle/CatalogLayoutSingleBundleView.tsx index fe5eaf41..e826290c 100644 --- a/src/views/catalog/views/page/layout/single-bundle/CatalogLayoutSingleBundleView.tsx +++ b/src/views/catalog/views/page/layout/single-bundle/CatalogLayoutSingleBundleView.tsx @@ -1,5 +1,5 @@ import { FC } from 'react'; -import { NitroCardGridView } from '../../../../../../layout'; +import { NitroCardGridView, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout'; import { useCatalogContext } from '../../../../context/CatalogContext'; import { CatalogPageDetailsView } from '../../../page-details/CatalogPageDetailsView'; import { CatalogProductView } from '../../product/CatalogProductView'; @@ -13,19 +13,19 @@ export const CatalogLayoutSingleBundleView: FC -
+ + { activeOffer && activeOffer.products && (activeOffer.products.length > 0) && activeOffer.products.map((product, index) => { return }) } -
-
+ + { activeOffer && } -
-
+ + ); } diff --git a/src/views/catalog/views/page/layout/spaces-new/CatalogLayoutSpacesView.tsx b/src/views/catalog/views/page/layout/spaces-new/CatalogLayoutSpacesView.tsx index 65da4622..2a04a80b 100644 --- a/src/views/catalog/views/page/layout/spaces-new/CatalogLayoutSpacesView.tsx +++ b/src/views/catalog/views/page/layout/spaces-new/CatalogLayoutSpacesView.tsx @@ -1,6 +1,7 @@ import { CatalogPageMessageOfferData, IFurnitureData } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; import { GetSessionDataManager, LocalizeText } from '../../../../../../api'; +import { NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout'; import { ProductTypeEnum } from '../../../../common/ProductTypeEnum'; import { useCatalogContext } from '../../../../context/CatalogContext'; import { CatalogPageOffersView } from '../../offers/CatalogPageOffersView'; @@ -10,10 +11,10 @@ import { CatalogLayoutSpacesViewProps } from './CatalogLayoutSpacesView.types'; export const CatalogLayoutSpacesView: FC = props => { const { roomPreviewer = null, pageParser = null } = props; - const { catalogState } = useCatalogContext(); - const { activeOffer = null } = catalogState; const [ groups, setGroups ] = useState([]); const [ activeGroupIndex, setActiveGroupIndex ] = useState(-1); + const { catalogState } = useCatalogContext(); + const { activeOffer = null } = catalogState; const groupNames = [ 'floors', 'walls', 'views' ]; @@ -66,8 +67,8 @@ export const CatalogLayoutSpacesView: FC = props = const product = ((activeOffer && activeOffer.products[0]) || null); return ( -
-
+ +
{ groupNames.map((name, index) => { @@ -75,10 +76,10 @@ export const CatalogLayoutSpacesView: FC = props = })}
-
-
+ + -
-
+ + ); } diff --git a/src/views/catalog/views/page/layout/trophies/CatalogLayoutTrophiesView.tsx b/src/views/catalog/views/page/layout/trophies/CatalogLayoutTrophiesView.tsx index 03379cf0..86d0e7ba 100644 --- a/src/views/catalog/views/page/layout/trophies/CatalogLayoutTrophiesView.tsx +++ b/src/views/catalog/views/page/layout/trophies/CatalogLayoutTrophiesView.tsx @@ -1,4 +1,5 @@ import { FC, useState } from 'react'; +import { NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout'; import { useCatalogContext } from '../../../../context/CatalogContext'; import { CatalogPageOffersView } from '../../offers/CatalogPageOffersView'; import { CatalogProductPreviewView } from '../../product-preview/CatalogProductPreviewView'; @@ -14,14 +15,14 @@ export const CatalogLayoutTrophiesView: FC = pro const product = ((activeOffer && activeOffer.products[0]) || null); return ( -
-
+ + +
- - + +
diff --git a/src/views/mod-tools/views/room/ModToolsRoomView.types.ts b/src/views/mod-tools/views/room/room-tools/ModToolsRoomView.types.ts similarity index 100% rename from src/views/mod-tools/views/room/ModToolsRoomView.types.ts rename to src/views/mod-tools/views/room/room-tools/ModToolsRoomView.types.ts diff --git a/src/views/mod-tools/views/user/ModToolsUserView.scss b/src/views/mod-tools/views/user/ModToolsUserView.scss deleted file mode 100644 index 05757d6c..00000000 --- a/src/views/mod-tools/views/user/ModToolsUserView.scss +++ /dev/null @@ -1,8 +0,0 @@ -.nitro-mod-tools-user { - width: 240px; - - .username { - color: #1E7295; - text-decoration: underline; - } -} diff --git a/src/views/mod-tools/views/user/ModToolsUserView.tsx b/src/views/mod-tools/views/user/ModToolsUserView.tsx deleted file mode 100644 index 373bd2df..00000000 --- a/src/views/mod-tools/views/user/ModToolsUserView.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { ModeratorUserInfoData, ModtoolRequestUserInfoComposer, ModtoolUserInfoEvent } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useState } from 'react'; -import { ModToolsOpenUserChatlogEvent } from '../../../../events/mod-tools/ModToolsOpenUserChatlogEvent'; -import { CreateMessageHook, dispatchUiEvent, SendMessageHook } from '../../../../hooks'; -import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout'; -import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView'; -import { ModToolsUserViewProps } from './ModToolsUserView.types'; - -export const ModToolsUserView: FC = props => -{ - const { onCloseClick = null, userId = null, simple = true } = props; - const [userInfo, setUserInfo] = useState(null); - - useEffect(() => - { - SendMessageHook(new ModtoolRequestUserInfoComposer(userId)); - }, [userId]); - - - const onModtoolUserInfoEvent = useCallback((event: ModtoolUserInfoEvent) => - { - const parser = event.getParser(); - - if(!parser || parser.data.userId !== userId) return; - - setUserInfo(parser.data); - }, [setUserInfo, userId]); - - CreateMessageHook(ModtoolUserInfoEvent, onModtoolUserInfoEvent); - - return ( - - onCloseClick()} /> - - {userInfo && - <> - {!simple && -
- -
- } -
-
- Name: {userInfo.userName} -
- -
-
-
- CFHs: {userInfo.cfhCount} -
-
- - } -
-
- ); -} diff --git a/src/views/mod-tools/views/user-chatlog/ModToolsUserChatlogView.tsx b/src/views/mod-tools/views/user/user-chatlog/ModToolsUserChatlogView.tsx similarity index 94% rename from src/views/mod-tools/views/user-chatlog/ModToolsUserChatlogView.tsx rename to src/views/mod-tools/views/user/user-chatlog/ModToolsUserChatlogView.tsx index d2ca9bd0..0da179cb 100644 --- a/src/views/mod-tools/views/user-chatlog/ModToolsUserChatlogView.tsx +++ b/src/views/mod-tools/views/user/user-chatlog/ModToolsUserChatlogView.tsx @@ -1,8 +1,8 @@ import { ChatRecordData, ModtoolRequestUserChatlogComposer, ModtoolUserChatlogEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useState } from 'react'; -import { CreateMessageHook, SendMessageHook } from '../../../../hooks'; -import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout'; -import { ChatlogView } from '../chatlog/ChatlogView'; +import { CreateMessageHook, SendMessageHook } from '../../../../../hooks'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout'; +import { ChatlogView } from '../../chatlog/ChatlogView'; import { ModToolsUserChatlogViewProps } from './ModToolsUserChatlogView.types'; export const ModToolsUserChatlogView: FC = props => diff --git a/src/views/mod-tools/views/user-chatlog/ModToolsUserChatlogView.types.ts b/src/views/mod-tools/views/user/user-chatlog/ModToolsUserChatlogView.types.ts similarity index 100% rename from src/views/mod-tools/views/user-chatlog/ModToolsUserChatlogView.types.ts rename to src/views/mod-tools/views/user/user-chatlog/ModToolsUserChatlogView.types.ts diff --git a/src/views/mod-tools/views/user/user-info/ModToolsUserView.scss b/src/views/mod-tools/views/user/user-info/ModToolsUserView.scss new file mode 100644 index 00000000..202b92fa --- /dev/null +++ b/src/views/mod-tools/views/user/user-info/ModToolsUserView.scss @@ -0,0 +1,27 @@ +.nitro-mod-tools-user { + width: 350px; + height: 400px; + + .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); + } + + td { + padding: 0px 5px; + } + } +} diff --git a/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx b/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx new file mode 100644 index 00000000..762dcc3d --- /dev/null +++ b/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx @@ -0,0 +1,161 @@ +import { FriendlyTime, ModeratorUserInfoData, ModtoolRequestUserInfoComposer, ModtoolUserInfoEvent } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useState } from 'react'; +import { ModToolsOpenUserChatlogEvent } from '../../../../../events/mod-tools/ModToolsOpenUserChatlogEvent'; +import { CreateMessageHook, dispatchUiEvent, SendMessageHook } from '../../../../../hooks'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout'; +import { AvatarImageView } from '../../../../shared/avatar-image/AvatarImageView'; +import { IUserInfo } from '../../../utils/IUserInfo'; +import { ModToolsUserViewProps } from './ModToolsUserView.types'; + + +export const ModToolsUserView: FC = props => +{ + const { onCloseClick = null, userId = null, simple = true } = props; + const [userInfo, setUserInfo] = useState(null); + + useEffect(() => + { + SendMessageHook(new ModtoolRequestUserInfoComposer(userId)); + }, [userId]); + + + const onModtoolUserInfoEvent = useCallback((event: ModtoolUserInfoEvent) => + { + const parser = event.getParser(); + + if(!parser || parser.data.userId !== userId) return; + + setUserInfo(parser.data); + }, [setUserInfo, userId]); + + CreateMessageHook(ModtoolUserInfoEvent, onModtoolUserInfoEvent); + + const OnlineIcon = useCallback(() => + { + if(userInfo.online) return (); + else return (); + }, [userInfo]); + + const userProperties = useCallback(() => + { + let properties: IUserInfo[] = []; + + if(!userInfo) return properties; + + properties = [ + { + nameKey: 'name', + nameKeyFallback: 'Name', + value: userInfo.userName + }, + { + nameKey: 'cfhs', + nameKeyFallback: 'CFHs', + value: userInfo.cfhCount.toString() + }, + { + nameKey: 'abusive_cfhs', + nameKeyFallback: 'Abusive CFHs', + value: userInfo.abusiveCfhCount.toString() + }, + { + nameKey: 'cautions', + nameKeyFallback: 'Cautions', + value: userInfo.cautionCount.toString() + }, + { + nameKey: 'bans', + nameKeyFallback: 'Bans', + value: userInfo.banCount.toString() + }, + { + nameKey: 'last_sanction', + nameKeyFallback: 'Last sanction', + value: userInfo.lastSanctionTime + }, + { + nameKey: 'trade_locks', + nameKeyFallback: 'Trade locks', + value: userInfo.tradingLockCount.toString() + }, + { + nameKey: 'lock_expires', + nameKeyFallback: 'Lock expires', + value: userInfo.tradingExpiryDate + }, + { + nameKey: 'last_login', + nameKeyFallback: 'Last login', + value: FriendlyTime.format(userInfo.minutesSinceLastLogin * 60, '.ago', 2) + }, + { + nameKey: 'purchase', + nameKeyFallback: 'Purchases', + value: userInfo.lastPurchaseDate + }, + { + nameKey: 'email', + nameKeyFallback: 'Email', + value: userInfo.primaryEmailAddress + }, + { + nameKey: 'acc_bans', + nameKeyFallback: 'Banned Accs.', + value: userInfo.identityRelatedBanCount.toString() + }, + { + nameKey: 'registered', + nameKeyFallback: 'Registered', + value: FriendlyTime.format(userInfo.registrationAgeInMinutes * 60, '.ago', 2) + }, + { + nameKey: 'rank', + nameKeyFallback: 'Rank', + value: userInfo.userClassification + } + ]; + + return properties; + + }, [userInfo]); + + return ( + + onCloseClick()} /> + + {userInfo && +
+ {!simple && +
+ +
+ } +
+ + + {userProperties().map(property => + { + return ( + + + + + ) + })} + +
{property.nameKeyFallback} + {property.value} {(property.nameKey === 'name') && } +
+
+
+ + + + +
+
+ } +
+
+ ); +} diff --git a/src/views/mod-tools/views/user/ModToolsUserView.types.ts b/src/views/mod-tools/views/user/user-info/ModToolsUserView.types.ts similarity index 100% rename from src/views/mod-tools/views/user/ModToolsUserView.types.ts rename to src/views/mod-tools/views/user/user-info/ModToolsUserView.types.ts From 59dfb8d9b4d58918c2fa1d7ae0521c74e7580689 Mon Sep 17 00:00:00 2001 From: dank074 Date: Tue, 19 Oct 2021 21:48:43 -0500 Subject: [PATCH 46/73] added user room visits view --- src/views/mod-tools/ModToolsView.scss | 1 + src/views/mod-tools/ModToolsView.tsx | 19 +++++- .../mod-tools/reducers/ModToolsReducer.tsx | 10 +++ .../user/user-info/ModToolsUserView.scss | 2 +- .../views/user/user-info/ModToolsUserView.tsx | 17 ++++- .../ModToolsUserModActionView.tsx | 17 +++++ .../ModToolsUserModActionView.types.ts | 7 ++ .../ModToolsUserRoomVisitsView.scss | 14 ++++ .../ModToolsUserRoomVisitsView.tsx | 66 +++++++++++++++++++ .../ModToolsUserRoomVisitsView.types.ts | 5 ++ .../ModToolsSendUserMessage.types.ts | 7 ++ .../ModToolsSendUserMessageView.tsx | 37 +++++++++++ 12 files changed, 197 insertions(+), 5 deletions(-) create mode 100644 src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.tsx create mode 100644 src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.types.ts create mode 100644 src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.scss create mode 100644 src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.tsx create mode 100644 src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.types.ts create mode 100644 src/views/mod-tools/views/user/user-sendmessage/ModToolsSendUserMessage.types.ts create mode 100644 src/views/mod-tools/views/user/user-sendmessage/ModToolsSendUserMessageView.tsx diff --git a/src/views/mod-tools/ModToolsView.scss b/src/views/mod-tools/ModToolsView.scss index c340a20f..be84aaef 100644 --- a/src/views/mod-tools/ModToolsView.scss +++ b/src/views/mod-tools/ModToolsView.scss @@ -5,3 +5,4 @@ @import './views/room/room-tools/ModToolsRoomView'; @import './views/chatlog/ChatlogView'; @import './views/user/user-info/ModToolsUserView'; +@import './views/user/user-room-visits/ModToolsUserRoomVisitsView'; diff --git a/src/views/mod-tools/ModToolsView.tsx b/src/views/mod-tools/ModToolsView.tsx index 71964dc0..9df029de 100644 --- a/src/views/mod-tools/ModToolsView.tsx +++ b/src/views/mod-tools/ModToolsView.tsx @@ -1,4 +1,4 @@ -import { RoomEngineEvent, RoomEngineObjectEvent, RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { ModeratorInitMessageEvent, RoomEngineEvent, RoomEngineObjectEvent, RoomObjectCategory } from '@nitrots/nitro-renderer'; import { FC, useCallback, useReducer, useState } from 'react'; import { GetRoomSession } from '../../api'; import { ModToolsEvent } from '../../events/mod-tools/ModToolsEvent'; @@ -6,6 +6,7 @@ import { ModToolsOpenRoomChatlogEvent } from '../../events/mod-tools/ModToolsOpe import { ModToolsOpenRoomInfoEvent } from '../../events/mod-tools/ModToolsOpenRoomInfoEvent'; import { ModToolsOpenUserChatlogEvent } from '../../events/mod-tools/ModToolsOpenUserChatlogEvent'; import { ModToolsOpenUserInfoEvent } from '../../events/mod-tools/ModToolsOpenUserInfoEvent'; +import { CreateMessageHook } from '../../hooks'; import { useRoomEngineEvent } from '../../hooks/events'; import { dispatchUiEvent, useUiEvent } from '../../hooks/events/ui/ui-event'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout'; @@ -154,6 +155,22 @@ export const ModToolsView: FC = props => useRoomEngineEvent(RoomEngineObjectEvent.SELECTED, onRoomEngineObjectEvent); + const onModeratorInitMessageEvent = useCallback((event: ModeratorInitMessageEvent) => + { + const parser = event.getParser(); + + if(!parser) return; + + dispatchModToolsState({ + type: ModToolsActions.SET_INIT_DATA, + payload: { + settings: parser.data + } + }); + }, []); + + CreateMessageHook(ModeratorInitMessageEvent, onModeratorInitMessageEvent); + const handleClick = useCallback((action: string, value?: string) => { if(!action) return; diff --git a/src/views/mod-tools/reducers/ModToolsReducer.tsx b/src/views/mod-tools/reducers/ModToolsReducer.tsx index 9301e941..c2c9df87 100644 --- a/src/views/mod-tools/reducers/ModToolsReducer.tsx +++ b/src/views/mod-tools/reducers/ModToolsReducer.tsx @@ -1,7 +1,9 @@ +import { ModeratorInitData } from '@nitrots/nitro-renderer'; import { Reducer } from 'react'; export interface IModToolsState { + settings: ModeratorInitData; currentRoomId: number; openRooms: number[]; openRoomChatlogs: number[]; @@ -13,6 +15,7 @@ export interface IModToolsAction { type: string; payload: { + settings?: ModeratorInitData; currentRoomId?: number; openRooms?: number[]; openRoomChatlogs?: number[]; @@ -23,6 +26,7 @@ export interface IModToolsAction export class ModToolsActions { + public static SET_INIT_DATA: string = 'MTA_SET_INIT_DATA'; public static SET_CURRENT_ROOM_ID: string = 'MTA_SET_CURRENT_ROOM_ID'; public static SET_OPEN_ROOMS: string = 'MTA_SET_OPEN_ROOMS'; public static SET_OPEN_USERINFO: string = 'MTA_SET_OPEN_USERINFO'; @@ -32,6 +36,7 @@ export class ModToolsActions } export const initialModTools: IModToolsState = { + settings: null, currentRoomId: null, openRooms: null, openRoomChatlogs: null, @@ -43,6 +48,11 @@ export const ModToolsReducer: Reducer = (state, { switch(action.type) { + case ModToolsActions.SET_INIT_DATA: { + const data = (action.payload.settings || state.settings || null); + + return { ...state, data }; + } case ModToolsActions.SET_CURRENT_ROOM_ID: { const currentRoomId = (action.payload.currentRoomId || state.currentRoomId || null); diff --git a/src/views/mod-tools/views/user/user-info/ModToolsUserView.scss b/src/views/mod-tools/views/user/user-info/ModToolsUserView.scss index 202b92fa..cb138384 100644 --- a/src/views/mod-tools/views/user/user-info/ModToolsUserView.scss +++ b/src/views/mod-tools/views/user/user-info/ModToolsUserView.scss @@ -1,6 +1,6 @@ .nitro-mod-tools-user { width: 350px; - height: 400px; + height: 370px; .username { color: #1E7295; diff --git a/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx b/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx index 762dcc3d..b056553b 100644 --- a/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx +++ b/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx @@ -5,6 +5,9 @@ import { CreateMessageHook, dispatchUiEvent, SendMessageHook } from '../../../.. import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout'; import { AvatarImageView } from '../../../../shared/avatar-image/AvatarImageView'; import { IUserInfo } from '../../../utils/IUserInfo'; +import { ModToolsUserModActionView } from '../user-mod-action/ModToolsUserModActionView'; +import { ModToolsUserRoomVisitsView } from '../user-room-visits/ModToolsUserRoomVisitsView'; +import { ModToolsSendUserMessageView } from '../user-sendmessage/ModToolsSendUserMessageView'; import { ModToolsUserViewProps } from './ModToolsUserView.types'; @@ -12,6 +15,9 @@ export const ModToolsUserView: FC = props => { const { onCloseClick = null, userId = null, simple = true } = props; const [userInfo, setUserInfo] = useState(null); + const [sendMessageVisible, setSendMessageVisible] = useState(false); + const [modActionVisible, setModActionVisible] = useState(false); + const [roomVisitsVisible, setRoomVisitsVisible] = useState(false); useEffect(() => { @@ -120,6 +126,7 @@ export const ModToolsUserView: FC = props => }, [userInfo]); return ( + <> onCloseClick()} /> @@ -149,13 +156,17 @@ export const ModToolsUserView: FC = props =>
- - - + + +
} + {(sendMessageVisible && userInfo) && setSendMessageVisible(false)}/>} + {(userInfo && modActionVisible) && setModActionVisible(false)}/>} + {(userInfo && roomVisitsVisible) && setRoomVisitsVisible(false)}/>} + ); } diff --git a/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.tsx b/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.tsx new file mode 100644 index 00000000..c78f12f6 --- /dev/null +++ b/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.tsx @@ -0,0 +1,17 @@ +import { FC } from 'react'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout'; +import { ModToolsUserModActionViewProps } from './ModToolsUserModActionView.types'; + +export const ModToolsUserModActionView: FC = props => +{ + const { user = null, onCloseClick = null } = props; + + return ( + + onCloseClick()} /> + + {user &&
} +
+
+ ); +} diff --git a/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.types.ts b/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.types.ts new file mode 100644 index 00000000..2025d10d --- /dev/null +++ b/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.types.ts @@ -0,0 +1,7 @@ +import { ISelectedUser } from '../../../utils/ISelectedUser'; + +export interface ModToolsUserModActionViewProps +{ + user: ISelectedUser; + onCloseClick: () => void; +} diff --git a/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.scss b/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.scss new file mode 100644 index 00000000..46efbaa5 --- /dev/null +++ b/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.scss @@ -0,0 +1,14 @@ +.nitro-mod-tools-user-visits { + min-width: 300px; + + .user-visits { + min-height: 200px; + + .roomvisits-container { + div.room-visit { + border-bottom: 1px solid rgba(0, 0, 0, 0.2); + } + } + + } +} diff --git a/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.tsx b/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.tsx new file mode 100644 index 00000000..8d1a709e --- /dev/null +++ b/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.tsx @@ -0,0 +1,66 @@ +import { ModtoolReceivedRoomsUserEvent, ModtoolRequestUserRoomsComposer, ModtoolRoomVisitedData } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useState } from 'react'; +import { AutoSizer, List, ListRowProps, ListRowRenderer } from 'react-virtualized'; +import { CreateMessageHook, SendMessageHook } from '../../../../../hooks'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout'; +import { ModToolsUserRoomVisitsViewProps } from './ModToolsUserRoomVisitsView.types'; + +export const ModToolsUserRoomVisitsView: FC = props => +{ + const { userId = null, onCloseClick = null } = props; + + const [roomVisitData, setRoomVisitData] = useState(null); + + useEffect(() => + { + SendMessageHook(new ModtoolRequestUserRoomsComposer(userId)); + }, [userId]); + + const onModtoolReceivedRoomsUserEvent = useCallback((event: ModtoolReceivedRoomsUserEvent) => + { + const parser = event.getParser(); + + if(!parser || parser.data.userId !== userId) return; + + setRoomVisitData(parser.data); + }, [userId]); + + CreateMessageHook(ModtoolReceivedRoomsUserEvent, onModtoolReceivedRoomsUserEvent); + + const RowRenderer: ListRowRenderer = (props: ListRowProps) => + { + const item = roomVisitData.rooms[props.index]; + + return ( +
+ {item.enterHour}:{item.enterMinute} Room: {item.roomName} +
); + } + + return ( + + onCloseClick()} /> + + {roomVisitData && +
+ + {({ height, width }) => + { + return ( + + ) + }} + +
+ } +
+
+ ); +} diff --git a/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.types.ts b/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.types.ts new file mode 100644 index 00000000..8aaebec5 --- /dev/null +++ b/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.types.ts @@ -0,0 +1,5 @@ +export interface ModToolsUserRoomVisitsViewProps +{ + userId: number; + onCloseClick: () => void; +} diff --git a/src/views/mod-tools/views/user/user-sendmessage/ModToolsSendUserMessage.types.ts b/src/views/mod-tools/views/user/user-sendmessage/ModToolsSendUserMessage.types.ts new file mode 100644 index 00000000..617945f0 --- /dev/null +++ b/src/views/mod-tools/views/user/user-sendmessage/ModToolsSendUserMessage.types.ts @@ -0,0 +1,7 @@ +import { ISelectedUser } from '../../../utils/ISelectedUser'; + +export interface ModToolsSendUserMessageViewProps +{ + user: ISelectedUser; + onCloseClick: () => void; +} diff --git a/src/views/mod-tools/views/user/user-sendmessage/ModToolsSendUserMessageView.tsx b/src/views/mod-tools/views/user/user-sendmessage/ModToolsSendUserMessageView.tsx new file mode 100644 index 00000000..7305bf93 --- /dev/null +++ b/src/views/mod-tools/views/user/user-sendmessage/ModToolsSendUserMessageView.tsx @@ -0,0 +1,37 @@ +import { ModMessageMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useState } from 'react'; +import { SendMessageHook } from '../../../../../hooks'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout'; +import { ModToolsSendUserMessageViewProps } from './ModToolsSendUserMessage.types'; + +export const ModToolsSendUserMessageView: FC = props => +{ + const { user = null, onCloseClick = null } = props; + const [message, setMessage] = useState(''); + + + const sendMessage = useCallback(() => + { + if(message.trim().length === 0) return; + + SendMessageHook(new ModMessageMessageComposer(user.userId, message, -999)); + }, [message, user.userId]); + + return ( + + onCloseClick()} /> + + {user && <> +
Message To: {user.username}
+
+ +
+ +
+ +
+ } +
+
+ ); +} From 0f7b13a8fc5d6e056fcaff55868ced85077fb6b9 Mon Sep 17 00:00:00 2001 From: dank074 Date: Wed, 20 Oct 2021 01:01:26 -0500 Subject: [PATCH 47/73] style changes to room visits --- src/views/mod-tools/ModToolsView.tsx | 12 ++---- .../ModToolsUserRoomVisitsView.tsx | 41 ++++++++++--------- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/views/mod-tools/ModToolsView.tsx b/src/views/mod-tools/ModToolsView.tsx index 9df029de..45ea7b46 100644 --- a/src/views/mod-tools/ModToolsView.tsx +++ b/src/views/mod-tools/ModToolsView.tsx @@ -184,9 +184,7 @@ export const ModToolsView: FC = props => return; } - const itemIndex = openRooms.indexOf(currentRoomId); - - if(itemIndex > -1) + if(openRooms.indexOf(currentRoomId) > -1) { handleClick('close_room', currentRoomId.toString()); } @@ -217,9 +215,7 @@ export const ModToolsView: FC = props => return; } - const itemIndex = openRoomChatlogs.indexOf(currentRoomId); - - if(itemIndex > -1) + if(openRoomChatlogs.indexOf(currentRoomId) > -1) { handleClick('close_room_chatlog', currentRoomId.toString()); } @@ -255,9 +251,7 @@ export const ModToolsView: FC = props => return; } - const itemIndex = openUserInfo.indexOf(userId); - - if(itemIndex > -1) + if(openUserInfo.indexOf(userId) > -1) { handleClick('close_user_info', userId.toString()); } diff --git a/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.tsx b/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.tsx index 8d1a709e..1bc05a91 100644 --- a/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.tsx +++ b/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.tsx @@ -1,6 +1,7 @@ import { ModtoolReceivedRoomsUserEvent, ModtoolRequestUserRoomsComposer, ModtoolRoomVisitedData } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useState } from 'react'; import { AutoSizer, List, ListRowProps, ListRowRenderer } from 'react-virtualized'; +import { TryVisitRoom } from '../../../../../api'; import { CreateMessageHook, SendMessageHook } from '../../../../../hooks'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout'; import { ModToolsUserRoomVisitsViewProps } from './ModToolsUserRoomVisitsView.types'; @@ -32,9 +33,11 @@ export const ModToolsUserRoomVisitsView: FC = p const item = roomVisitData.rooms[props.index]; return ( -
- {item.enterHour}:{item.enterMinute} Room: {item.roomName} -
); +
+
{item.enterHour.toString().padStart(2, '0')}:{item.enterMinute.toString().padStart(2, '0')}
+
Room: {item.roomName}
+ +
); } return ( @@ -42,22 +45,22 @@ export const ModToolsUserRoomVisitsView: FC = p onCloseClick()} /> {roomVisitData && -
- - {({ height, width }) => - { - return ( - - ) - }} - +
+ + {({ height, width }) => + { + return ( + + ) + }} +
} From fe6f27737cb71d299f1b9822f934b375041a5c3c Mon Sep 17 00:00:00 2001 From: Bill Date: Wed, 20 Oct 2021 02:45:04 -0400 Subject: [PATCH 48/73] Update mod tools user info --- .../user/user-info/ModToolsUserView.scss | 4 - .../views/user/user-info/ModToolsUserView.tsx | 165 ++++++++---------- .../user/user-info/ModToolsUserView.types.ts | 1 - .../ModToolsUserModActionView.tsx | 2 +- .../ModToolsUserModActionView.types.ts | 3 +- .../ModToolsUserRoomVisitsView.tsx | 2 +- .../ModToolsUserRoomVisitsView.types.ts | 4 +- .../ModToolsSendUserMessage.types.ts | 3 +- .../ModToolsSendUserMessageView.tsx | 2 +- 9 files changed, 83 insertions(+), 103 deletions(-) diff --git a/src/views/mod-tools/views/user/user-info/ModToolsUserView.scss b/src/views/mod-tools/views/user/user-info/ModToolsUserView.scss index cb138384..ddc24350 100644 --- a/src/views/mod-tools/views/user/user-info/ModToolsUserView.scss +++ b/src/views/mod-tools/views/user/user-info/ModToolsUserView.scss @@ -19,9 +19,5 @@ color: $black; background: rgba(0, 0, 0, .05); } - - td { - padding: 0px 5px; - } } } diff --git a/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx b/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx index b056553b..5981f666 100644 --- a/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx +++ b/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx @@ -1,29 +1,26 @@ import { FriendlyTime, ModeratorUserInfoData, ModtoolRequestUserInfoComposer, ModtoolUserInfoEvent } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useState } from 'react'; +import { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { LocalizeText } from '../../../../../api'; import { ModToolsOpenUserChatlogEvent } from '../../../../../events/mod-tools/ModToolsOpenUserChatlogEvent'; import { CreateMessageHook, dispatchUiEvent, SendMessageHook } from '../../../../../hooks'; -import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout'; -import { AvatarImageView } from '../../../../shared/avatar-image/AvatarImageView'; -import { IUserInfo } from '../../../utils/IUserInfo'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutButton, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../layout'; import { ModToolsUserModActionView } from '../user-mod-action/ModToolsUserModActionView'; import { ModToolsUserRoomVisitsView } from '../user-room-visits/ModToolsUserRoomVisitsView'; import { ModToolsSendUserMessageView } from '../user-sendmessage/ModToolsSendUserMessageView'; import { ModToolsUserViewProps } from './ModToolsUserView.types'; - export const ModToolsUserView: FC = props => { - const { onCloseClick = null, userId = null, simple = true } = props; - const [userInfo, setUserInfo] = useState(null); - const [sendMessageVisible, setSendMessageVisible] = useState(false); - const [modActionVisible, setModActionVisible] = useState(false); - const [roomVisitsVisible, setRoomVisitsVisible] = useState(false); + const { onCloseClick = null, userId = null } = props; + const [ userInfo, setUserInfo ] = useState(null); + const [ sendMessageVisible, setSendMessageVisible ] = useState(false); + const [ modActionVisible, setModActionVisible ] = useState(false); + const [ roomVisitsVisible, setRoomVisitsVisible ] = useState(false); useEffect(() => { SendMessageHook(new ModtoolRequestUserInfoComposer(userId)); - }, [userId]); - + }, [ userId ]); const onModtoolUserInfoEvent = useCallback((event: ModtoolUserInfoEvent) => { @@ -36,137 +33,121 @@ export const ModToolsUserView: FC = props => CreateMessageHook(ModtoolUserInfoEvent, onModtoolUserInfoEvent); - const OnlineIcon = useCallback(() => + const userProperties = useMemo(() => { - if(userInfo.online) return (); - else return (); - }, [userInfo]); + if(!userInfo) return null; - const userProperties = useCallback(() => - { - let properties: IUserInfo[] = []; - - if(!userInfo) return properties; - - properties = [ + return [ { - nameKey: 'name', - nameKeyFallback: 'Name', - value: userInfo.userName + localeKey: 'modtools.userinfo.userName', + value: userInfo.userName, + showOnline: true }, { - nameKey: 'cfhs', - nameKeyFallback: 'CFHs', + localeKey: 'modtools.userinfo.cfhCount', value: userInfo.cfhCount.toString() }, { - nameKey: 'abusive_cfhs', - nameKeyFallback: 'Abusive CFHs', + localeKey: 'modtools.userinfo.abusiveCfhCount', value: userInfo.abusiveCfhCount.toString() }, { - nameKey: 'cautions', - nameKeyFallback: 'Cautions', + localeKey: 'modtools.userinfo.cautionCount', value: userInfo.cautionCount.toString() }, { - nameKey: 'bans', - nameKeyFallback: 'Bans', + localeKey: 'modtools.userinfo.banCount', value: userInfo.banCount.toString() }, { - nameKey: 'last_sanction', - nameKeyFallback: 'Last sanction', + localeKey: 'modtools.userinfo.lastSanctionTime', value: userInfo.lastSanctionTime }, { - nameKey: 'trade_locks', - nameKeyFallback: 'Trade locks', + localeKey: 'modtools.userinfo.tradingLockCount', value: userInfo.tradingLockCount.toString() }, { - nameKey: 'lock_expires', - nameKeyFallback: 'Lock expires', + localeKey: 'modtools.userinfo.tradingExpiryDate', value: userInfo.tradingExpiryDate }, { - nameKey: 'last_login', - nameKeyFallback: 'Last login', + localeKey: 'modtools.userinfo.minutesSinceLastLogin', value: FriendlyTime.format(userInfo.minutesSinceLastLogin * 60, '.ago', 2) }, { - nameKey: 'purchase', - nameKeyFallback: 'Purchases', + localeKey: 'modtools.userinfo.lastPurchaseDate', value: userInfo.lastPurchaseDate }, { - nameKey: 'email', - nameKeyFallback: 'Email', + localeKey: 'modtools.userinfo.primaryEmailAddress', value: userInfo.primaryEmailAddress }, { - nameKey: 'acc_bans', - nameKeyFallback: 'Banned Accs.', + localeKey: 'modtools.userinfo.identityRelatedBanCount', value: userInfo.identityRelatedBanCount.toString() }, { - nameKey: 'registered', - nameKeyFallback: 'Registered', + localeKey: 'modtools.userinfo.registrationAgeInMinutes', value: FriendlyTime.format(userInfo.registrationAgeInMinutes * 60, '.ago', 2) }, { - nameKey: 'rank', - nameKeyFallback: 'Rank', + localeKey: 'modtools.userinfo.userClassification', value: userInfo.userClassification } ]; + }, [ userInfo ]); - return properties; - - }, [userInfo]); + if(!userInfo) return null; return ( <> - - onCloseClick()} /> - - {userInfo && -
- {!simple && -
- -
- } -
- + + + + + +
- {userProperties().map(property => - { - return ( - - - - - ) - })} + { userProperties.map(property => + { + + return ( + + + + + ); + }) }
{property.nameKeyFallback} - {property.value} {(property.nameKey === 'name') && } -
{ LocalizeText(property.localeKey) } + { property.value } + { property.showOnline && } +
-
-
- - - - -
-
- } -
-
- {(sendMessageVisible && userInfo) && setSendMessageVisible(false)}/>} - {(userInfo && modActionVisible) && setModActionVisible(false)}/>} - {(userInfo && roomVisitsVisible) && setRoomVisitsVisible(false)}/>} + + + dispatchUiEvent(new ModToolsOpenUserChatlogEvent(userId)) }> + Room Chat + + setSendMessageVisible(!sendMessageVisible) }> + Send Message + + setRoomVisitsVisible(!roomVisitsVisible) }> + Room Visits + + setModActionVisible(!modActionVisible) }> + Mod Action + + + + + + { sendMessageVisible && + setSendMessageVisible(false) } /> } + { modActionVisible && + setModActionVisible(false) } /> } + { roomVisitsVisible && + setRoomVisitsVisible(false) } /> } ); } diff --git a/src/views/mod-tools/views/user/user-info/ModToolsUserView.types.ts b/src/views/mod-tools/views/user/user-info/ModToolsUserView.types.ts index 21d30670..534d647d 100644 --- a/src/views/mod-tools/views/user/user-info/ModToolsUserView.types.ts +++ b/src/views/mod-tools/views/user/user-info/ModToolsUserView.types.ts @@ -2,5 +2,4 @@ export interface ModToolsUserViewProps { userId: number; onCloseClick: () => void; - simple?: boolean; } diff --git a/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.tsx b/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.tsx index c78f12f6..2a8495a2 100644 --- a/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.tsx +++ b/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.tsx @@ -8,7 +8,7 @@ export const ModToolsUserModActionView: FC = pro return ( - onCloseClick()} /> + {user &&
}
diff --git a/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.types.ts b/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.types.ts index 2025d10d..28714abc 100644 --- a/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.types.ts +++ b/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.types.ts @@ -1,7 +1,8 @@ +import { MouseEvent } from 'react'; import { ISelectedUser } from '../../../utils/ISelectedUser'; export interface ModToolsUserModActionViewProps { user: ISelectedUser; - onCloseClick: () => void; + onCloseClick: (event: MouseEvent) => void; } diff --git a/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.tsx b/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.tsx index 8d1a709e..76aff1fd 100644 --- a/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.tsx +++ b/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.tsx @@ -39,7 +39,7 @@ export const ModToolsUserRoomVisitsView: FC = p return ( - onCloseClick()} /> + {roomVisitData &&
diff --git a/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.types.ts b/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.types.ts index 8aaebec5..4bbcaf40 100644 --- a/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.types.ts +++ b/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.types.ts @@ -1,5 +1,7 @@ +import { MouseEvent } from 'react'; + export interface ModToolsUserRoomVisitsViewProps { userId: number; - onCloseClick: () => void; + onCloseClick: (event: MouseEvent) => void; } diff --git a/src/views/mod-tools/views/user/user-sendmessage/ModToolsSendUserMessage.types.ts b/src/views/mod-tools/views/user/user-sendmessage/ModToolsSendUserMessage.types.ts index 617945f0..e8f071d8 100644 --- a/src/views/mod-tools/views/user/user-sendmessage/ModToolsSendUserMessage.types.ts +++ b/src/views/mod-tools/views/user/user-sendmessage/ModToolsSendUserMessage.types.ts @@ -1,7 +1,8 @@ +import { MouseEvent } from 'react'; import { ISelectedUser } from '../../../utils/ISelectedUser'; export interface ModToolsSendUserMessageViewProps { user: ISelectedUser; - onCloseClick: () => void; + onCloseClick: (event: MouseEvent) => void; } diff --git a/src/views/mod-tools/views/user/user-sendmessage/ModToolsSendUserMessageView.tsx b/src/views/mod-tools/views/user/user-sendmessage/ModToolsSendUserMessageView.tsx index 7305bf93..f1e5f01f 100644 --- a/src/views/mod-tools/views/user/user-sendmessage/ModToolsSendUserMessageView.tsx +++ b/src/views/mod-tools/views/user/user-sendmessage/ModToolsSendUserMessageView.tsx @@ -19,7 +19,7 @@ export const ModToolsSendUserMessageView: FC = return ( - onCloseClick()} /> + {user && <>
Message To: {user.username}
From e0fa8912661e1383e465cbc108482b32c37b8a16 Mon Sep 17 00:00:00 2001 From: dank074 Date: Wed, 20 Oct 2021 19:28:50 -0500 Subject: [PATCH 49/73] updated packets --- .../room/room-chatlog/ModToolsChatlogView.tsx | 4 ++-- .../room/room-tools/ModToolsRoomView.tsx | 22 +++++++++---------- .../tickets/ModToolsOpenIssuesTabView.tsx | 6 +++++ .../views/tickets/ModToolsTicketsView.tsx | 16 ++++++++++++-- .../user-chatlog/ModToolsUserChatlogView.tsx | 14 ++++++------ .../views/user/user-info/ModToolsUserView.tsx | 8 +++---- .../ModToolsUserRoomVisitsView.tsx | 10 ++++----- 7 files changed, 49 insertions(+), 31 deletions(-) create mode 100644 src/views/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx diff --git a/src/views/mod-tools/views/room/room-chatlog/ModToolsChatlogView.tsx b/src/views/mod-tools/views/room/room-chatlog/ModToolsChatlogView.tsx index 77573e30..4097d8c9 100644 --- a/src/views/mod-tools/views/room/room-chatlog/ModToolsChatlogView.tsx +++ b/src/views/mod-tools/views/room/room-chatlog/ModToolsChatlogView.tsx @@ -1,4 +1,4 @@ -import { ChatRecordData, ModtoolRequestRoomChatlogComposer, ModtoolRoomChatlogEvent } from '@nitrots/nitro-renderer'; +import { ChatRecordData, GetRoomChatlogMessageComposer, ModtoolRoomChatlogEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useState } from 'react'; import { CreateMessageHook, SendMessageHook } from '../../../../../hooks/messages'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout'; @@ -13,7 +13,7 @@ export const ModToolsChatlogView: FC = props => useEffect(() => { - SendMessageHook(new ModtoolRequestRoomChatlogComposer(roomId)); + SendMessageHook(new GetRoomChatlogMessageComposer(roomId)); }, [roomId]); const onModtoolRoomChatlogEvent = useCallback((event: ModtoolRoomChatlogEvent) => diff --git a/src/views/mod-tools/views/room/room-tools/ModToolsRoomView.tsx b/src/views/mod-tools/views/room/room-tools/ModToolsRoomView.tsx index 6a5c69e3..d169c1fb 100644 --- a/src/views/mod-tools/views/room/room-tools/ModToolsRoomView.tsx +++ b/src/views/mod-tools/views/room/room-tools/ModToolsRoomView.tsx @@ -1,4 +1,4 @@ -import { ModerateRoomMessageComposer, ModeratorActionMessageComposer, ModtoolRequestRoomInfoComposer, ModtoolRoomInfoEvent } from '@nitrots/nitro-renderer'; +import { GetModeratorRoomInfoMessageComposer, ModerateRoomMessageComposer, ModeratorActionMessageComposer, ModeratorRoomInfoEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useState } from 'react'; import { TryVisitRoom } from '../../../../../api'; import { ModToolsOpenRoomChatlogEvent } from '../../../../../events/mod-tools/ModToolsOpenRoomChatlogEvent'; @@ -30,25 +30,25 @@ export const ModToolsRoomView: FC = props => { if(infoRequested) return; - SendMessageHook(new ModtoolRequestRoomInfoComposer(roomId)); + SendMessageHook(new GetModeratorRoomInfoMessageComposer(roomId)); setInfoRequested(true); }, [ roomId, infoRequested, setInfoRequested ]); - const onModtoolRoomInfoEvent = useCallback((event: ModtoolRoomInfoEvent) => + const onModtoolRoomInfoEvent = useCallback((event: ModeratorRoomInfoEvent) => { const parser = event.getParser(); - if(!parser || parser.id !== roomId) return; + if(!parser || parser.data.flatId !== roomId) return; - setLoadedRoomId(parser.id); - setName(parser.name); - setOwnerId(parser.ownerId); - setOwnerName(parser.ownerName); - setOwnerInRoom(parser.ownerInRoom); - setUsersInRoom(parser.playerAmount); + 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); }, [ setLoadedRoomId, setName, setOwnerId, setOwnerName, setOwnerInRoom, setUsersInRoom, roomId ]); - CreateMessageHook(ModtoolRoomInfoEvent, onModtoolRoomInfoEvent); + CreateMessageHook(ModeratorRoomInfoEvent, onModtoolRoomInfoEvent); const handleClick = useCallback((action: string, value?: string) => { diff --git a/src/views/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx b/src/views/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx new file mode 100644 index 00000000..6d4392b7 --- /dev/null +++ b/src/views/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx @@ -0,0 +1,6 @@ +import { FC } from 'react'; + +export const ModToolsOpenIssuesTabView: FC<{}> = props => +{ + return null; +} diff --git a/src/views/mod-tools/views/tickets/ModToolsTicketsView.tsx b/src/views/mod-tools/views/tickets/ModToolsTicketsView.tsx index 5d2ebf47..60651532 100644 --- a/src/views/mod-tools/views/tickets/ModToolsTicketsView.tsx +++ b/src/views/mod-tools/views/tickets/ModToolsTicketsView.tsx @@ -1,5 +1,6 @@ -import { FC, useState } from 'react'; +import { FC, useCallback, useState } from 'react'; import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../layout'; +import { ModToolsOpenIssuesTabView } from './ModToolsOpenIssuesTabView'; import { ModToolsTicketsViewProps } from './ModToolsTicketsView.types'; const TABS: string[] = [ @@ -14,6 +15,15 @@ export const ModToolsTicketsView: FC = props => const [ currentTab, setCurrentTab ] = useState(0); + const CurrentTabComponent = useCallback(() => + { + switch(currentTab) + { + case 0: return ; + default: return null; + } + }, [currentTab]); + return ( @@ -26,7 +36,9 @@ export const ModToolsTicketsView: FC = props => ); }) } -
+
+ +
); diff --git a/src/views/mod-tools/views/user/user-chatlog/ModToolsUserChatlogView.tsx b/src/views/mod-tools/views/user/user-chatlog/ModToolsUserChatlogView.tsx index 0da179cb..705cc690 100644 --- a/src/views/mod-tools/views/user/user-chatlog/ModToolsUserChatlogView.tsx +++ b/src/views/mod-tools/views/user/user-chatlog/ModToolsUserChatlogView.tsx @@ -1,4 +1,4 @@ -import { ChatRecordData, ModtoolRequestUserChatlogComposer, ModtoolUserChatlogEvent } from '@nitrots/nitro-renderer'; +import { ChatRecordData, GetUserChatlogMessageComposer, UserChatlogEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useState } from 'react'; import { CreateMessageHook, SendMessageHook } from '../../../../../hooks'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout'; @@ -13,20 +13,20 @@ export const ModToolsUserChatlogView: FC = props = useEffect(() => { - SendMessageHook(new ModtoolRequestUserChatlogComposer(userId)); + SendMessageHook(new GetUserChatlogMessageComposer(userId)); }, [userId]); - const onModtoolUserChatlogEvent = useCallback((event: ModtoolUserChatlogEvent) => + const onModtoolUserChatlogEvent = useCallback((event: UserChatlogEvent) => { const parser = event.getParser(); - if(!parser || parser.userId !== userId) return; + if(!parser || parser.data.userId !== userId) return; - setUsername(parser.username); - setUserChatlog(parser.roomVisits); + setUsername(parser.data.username); + setUserChatlog(parser.data.roomChatlogs); }, [setUsername, setUserChatlog, userId]); - CreateMessageHook(ModtoolUserChatlogEvent, onModtoolUserChatlogEvent); + CreateMessageHook(UserChatlogEvent, onModtoolUserChatlogEvent); return ( diff --git a/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx b/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx index 5981f666..fa1a3214 100644 --- a/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx +++ b/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx @@ -1,4 +1,4 @@ -import { FriendlyTime, ModeratorUserInfoData, ModtoolRequestUserInfoComposer, ModtoolUserInfoEvent } from '@nitrots/nitro-renderer'; +import { FriendlyTime, GetModeratorUserInfoMessageComposer, ModeratorUserInfoData, ModtoolUserInfoEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { LocalizeText } from '../../../../../api'; import { ModToolsOpenUserChatlogEvent } from '../../../../../events/mod-tools/ModToolsOpenUserChatlogEvent'; @@ -19,7 +19,7 @@ export const ModToolsUserView: FC = props => useEffect(() => { - SendMessageHook(new ModtoolRequestUserInfoComposer(userId)); + SendMessageHook(new GetModeratorUserInfoMessageComposer(userId)); }, [ userId ]); const onModtoolUserInfoEvent = useCallback((event: ModtoolUserInfoEvent) => @@ -109,11 +109,11 @@ export const ModToolsUserView: FC = props => - { userProperties.map(property => + { userProperties.map( (property, index) => { return ( - +
{ LocalizeText(property.localeKey) } { property.value } diff --git a/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.tsx b/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.tsx index 0faec1ef..c79117dc 100644 --- a/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.tsx +++ b/src/views/mod-tools/views/user/user-room-visits/ModToolsUserRoomVisitsView.tsx @@ -1,4 +1,4 @@ -import { ModtoolReceivedRoomsUserEvent, ModtoolRequestUserRoomsComposer, ModtoolRoomVisitedData } from '@nitrots/nitro-renderer'; +import { GetRoomVisitsMessageComposer, RoomVisitsData, RoomVisitsEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useState } from 'react'; import { AutoSizer, List, ListRowProps, ListRowRenderer } from 'react-virtualized'; import { TryVisitRoom } from '../../../../../api'; @@ -10,14 +10,14 @@ export const ModToolsUserRoomVisitsView: FC = p { const { userId = null, onCloseClick = null } = props; - const [roomVisitData, setRoomVisitData] = useState(null); + const [roomVisitData, setRoomVisitData] = useState(null); useEffect(() => { - SendMessageHook(new ModtoolRequestUserRoomsComposer(userId)); + SendMessageHook(new GetRoomVisitsMessageComposer(userId)); }, [userId]); - const onModtoolReceivedRoomsUserEvent = useCallback((event: ModtoolReceivedRoomsUserEvent) => + const onModtoolReceivedRoomsUserEvent = useCallback((event: RoomVisitsEvent) => { const parser = event.getParser(); @@ -26,7 +26,7 @@ export const ModToolsUserRoomVisitsView: FC = p setRoomVisitData(parser.data); }, [userId]); - CreateMessageHook(ModtoolReceivedRoomsUserEvent, onModtoolReceivedRoomsUserEvent); + CreateMessageHook(RoomVisitsEvent, onModtoolReceivedRoomsUserEvent); const RowRenderer: ListRowRenderer = (props: ListRowProps) => { From e268a5a459e50edf1e7c2a3455592b32aceae212 Mon Sep 17 00:00:00 2001 From: dank074 Date: Wed, 20 Oct 2021 19:50:23 -0500 Subject: [PATCH 50/73] updated more packets --- src/views/mod-tools/views/chatlog/ChatlogView.tsx | 4 ++-- .../views/room/room-chatlog/ModToolsChatlogView.tsx | 6 +++--- .../mod-tools/views/user/user-info/ModToolsUserView.tsx | 6 +++--- .../NotificationCenterMessageHandler.tsx | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/views/mod-tools/views/chatlog/ChatlogView.tsx b/src/views/mod-tools/views/chatlog/ChatlogView.tsx index 426fd5a6..1eedefe8 100644 --- a/src/views/mod-tools/views/chatlog/ChatlogView.tsx +++ b/src/views/mod-tools/views/chatlog/ChatlogView.tsx @@ -1,4 +1,4 @@ -import { ChatRecordData, ModtoolRoomChatlogLine, UserProfileComposer } from '@nitrots/nitro-renderer'; +import { ChatlineData, ChatRecordData, UserProfileComposer } from '@nitrots/nitro-renderer'; import { FC, useCallback } from 'react'; import { AutoSizer, CellMeasurer, CellMeasurerCache, List, ListRowProps, ListRowRenderer } from 'react-virtualized'; import { TryVisitRoom } from '../../../../api'; @@ -33,7 +33,7 @@ export const ChatlogView: FC = props => const advancedRowRenderer: ListRowRenderer = (props: ListRowProps) => { - let chatlogEntry: ModtoolRoomChatlogLine; + let chatlogEntry: ChatlineData; let currentRecord: ChatRecordData; let isRoomInfo = false; diff --git a/src/views/mod-tools/views/room/room-chatlog/ModToolsChatlogView.tsx b/src/views/mod-tools/views/room/room-chatlog/ModToolsChatlogView.tsx index 4097d8c9..96d11aba 100644 --- a/src/views/mod-tools/views/room/room-chatlog/ModToolsChatlogView.tsx +++ b/src/views/mod-tools/views/room/room-chatlog/ModToolsChatlogView.tsx @@ -1,4 +1,4 @@ -import { ChatRecordData, GetRoomChatlogMessageComposer, ModtoolRoomChatlogEvent } from '@nitrots/nitro-renderer'; +import { ChatRecordData, GetRoomChatlogMessageComposer, RoomChatlogEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useState } from 'react'; import { CreateMessageHook, SendMessageHook } from '../../../../../hooks/messages'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout'; @@ -16,7 +16,7 @@ export const ModToolsChatlogView: FC = props => SendMessageHook(new GetRoomChatlogMessageComposer(roomId)); }, [roomId]); - const onModtoolRoomChatlogEvent = useCallback((event: ModtoolRoomChatlogEvent) => + const onModtoolRoomChatlogEvent = useCallback((event: RoomChatlogEvent) => { const parser = event.getParser(); @@ -25,7 +25,7 @@ export const ModToolsChatlogView: FC = props => setRoomChatlog(parser.data); }, [roomId, setRoomChatlog]); - CreateMessageHook(ModtoolRoomChatlogEvent, onModtoolRoomChatlogEvent); + CreateMessageHook(RoomChatlogEvent, onModtoolRoomChatlogEvent); return ( diff --git a/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx b/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx index fa1a3214..ab1fc54e 100644 --- a/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx +++ b/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx @@ -1,4 +1,4 @@ -import { FriendlyTime, GetModeratorUserInfoMessageComposer, ModeratorUserInfoData, ModtoolUserInfoEvent } from '@nitrots/nitro-renderer'; +import { FriendlyTime, GetModeratorUserInfoMessageComposer, ModeratorUserInfoData, ModeratorUserInfoEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { LocalizeText } from '../../../../../api'; import { ModToolsOpenUserChatlogEvent } from '../../../../../events/mod-tools/ModToolsOpenUserChatlogEvent'; @@ -22,7 +22,7 @@ export const ModToolsUserView: FC = props => SendMessageHook(new GetModeratorUserInfoMessageComposer(userId)); }, [ userId ]); - const onModtoolUserInfoEvent = useCallback((event: ModtoolUserInfoEvent) => + const onModtoolUserInfoEvent = useCallback((event: ModeratorUserInfoEvent) => { const parser = event.getParser(); @@ -31,7 +31,7 @@ export const ModToolsUserView: FC = props => setUserInfo(parser.data); }, [setUserInfo, userId]); - CreateMessageHook(ModtoolUserInfoEvent, onModtoolUserInfoEvent); + CreateMessageHook(ModeratorUserInfoEvent, onModtoolUserInfoEvent); const userProperties = useMemo(() => { diff --git a/src/views/notification-center/NotificationCenterMessageHandler.tsx b/src/views/notification-center/NotificationCenterMessageHandler.tsx index 9afc098d..76c68e65 100644 --- a/src/views/notification-center/NotificationCenterMessageHandler.tsx +++ b/src/views/notification-center/NotificationCenterMessageHandler.tsx @@ -60,7 +60,7 @@ export const NotificationCenterMessageHandler: FC Date: Wed, 20 Oct 2021 20:31:26 -0500 Subject: [PATCH 51/73] bill fix this --- src/views/mod-tools/ModToolsView.tsx | 7 ++++-- .../tickets/ModToolsOpenIssuesTabView.tsx | 6 ----- .../views/tickets/ModToolsTicketsView.tsx | 24 ++++++++++++++----- .../open-issues/ModToolsOpenIssuesTabView.tsx | 11 +++++++++ .../ModToolsOpenIssuesTabView.types.ts | 6 +++++ 5 files changed, 40 insertions(+), 14 deletions(-) delete mode 100644 src/views/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx create mode 100644 src/views/mod-tools/views/tickets/open-issues/ModToolsOpenIssuesTabView.tsx create mode 100644 src/views/mod-tools/views/tickets/open-issues/ModToolsOpenIssuesTabView.types.ts diff --git a/src/views/mod-tools/ModToolsView.tsx b/src/views/mod-tools/ModToolsView.tsx index 45ea7b46..4f73d340 100644 --- a/src/views/mod-tools/ModToolsView.tsx +++ b/src/views/mod-tools/ModToolsView.tsx @@ -161,12 +161,15 @@ export const ModToolsView: FC = props => if(!parser) return; + const data = parser.data; + dispatchModToolsState({ type: ModToolsActions.SET_INIT_DATA, payload: { - settings: parser.data + settings: data } - }); + }); + console.log(parser); }, []); CreateMessageHook(ModeratorInitMessageEvent, onModeratorInitMessageEvent); diff --git a/src/views/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx b/src/views/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx deleted file mode 100644 index 6d4392b7..00000000 --- a/src/views/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { FC } from 'react'; - -export const ModToolsOpenIssuesTabView: FC<{}> = props => -{ - return null; -} diff --git a/src/views/mod-tools/views/tickets/ModToolsTicketsView.tsx b/src/views/mod-tools/views/tickets/ModToolsTicketsView.tsx index 60651532..6f9471ae 100644 --- a/src/views/mod-tools/views/tickets/ModToolsTicketsView.tsx +++ b/src/views/mod-tools/views/tickets/ModToolsTicketsView.tsx @@ -1,7 +1,9 @@ -import { FC, useCallback, useState } from 'react'; +import { IssueMessageData } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useMemo, useState } from 'react'; import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../layout'; -import { ModToolsOpenIssuesTabView } from './ModToolsOpenIssuesTabView'; +import { useModToolsContext } from '../../context/ModToolsContext'; import { ModToolsTicketsViewProps } from './ModToolsTicketsView.types'; +import { ModToolsOpenIssuesTabView } from './open-issues/ModToolsOpenIssuesTabView'; const TABS: string[] = [ 'Open Issues', @@ -12,17 +14,27 @@ const TABS: string[] = [ export const ModToolsTicketsView: FC = props => { const { onCloseClick = null } = props; - + const { modToolsState = null } = useModToolsContext(); + const { settings = null } = modToolsState; const [ currentTab, setCurrentTab ] = useState(0); + const openIssues = useMemo(() => + { + if(!settings) return []; + + return settings.issues.filter(issue => issue.state === IssueMessageData.STATE_OPEN) + }, [settings]); + const CurrentTabComponent = useCallback(() => { switch(currentTab) { - case 0: return ; + case 0: return ; default: return null; } - }, [currentTab]); + }, [currentTab, openIssues]); + + console.log(settings); return ( @@ -37,7 +49,7 @@ export const ModToolsTicketsView: FC = props => }) }
- + {settings && }
diff --git a/src/views/mod-tools/views/tickets/open-issues/ModToolsOpenIssuesTabView.tsx b/src/views/mod-tools/views/tickets/open-issues/ModToolsOpenIssuesTabView.tsx new file mode 100644 index 00000000..d6ebe1bc --- /dev/null +++ b/src/views/mod-tools/views/tickets/open-issues/ModToolsOpenIssuesTabView.tsx @@ -0,0 +1,11 @@ +import { FC } from 'react'; +import { ModToolsOpenIssuesTabViewProps } from './ModToolsOpenIssuesTabView.types'; + +export const ModToolsOpenIssuesTabView: FC = props => +{ + const { openIssues = null } = props; + + return ( +
{openIssues.length}
+ ); +} diff --git a/src/views/mod-tools/views/tickets/open-issues/ModToolsOpenIssuesTabView.types.ts b/src/views/mod-tools/views/tickets/open-issues/ModToolsOpenIssuesTabView.types.ts new file mode 100644 index 00000000..45a80d7d --- /dev/null +++ b/src/views/mod-tools/views/tickets/open-issues/ModToolsOpenIssuesTabView.types.ts @@ -0,0 +1,6 @@ +import { IssueMessageData } from '@nitrots/nitro-renderer'; + +export interface ModToolsOpenIssuesTabViewProps +{ + openIssues: IssueMessageData[]; +} From 5ad80cadca6233560d9bc4571122f86bc9f5779b Mon Sep 17 00:00:00 2001 From: Bill Date: Wed, 20 Oct 2021 21:37:54 -0400 Subject: [PATCH 52/73] Fixed --- src/views/mod-tools/reducers/ModToolsReducer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/mod-tools/reducers/ModToolsReducer.tsx b/src/views/mod-tools/reducers/ModToolsReducer.tsx index c2c9df87..e1515e76 100644 --- a/src/views/mod-tools/reducers/ModToolsReducer.tsx +++ b/src/views/mod-tools/reducers/ModToolsReducer.tsx @@ -49,9 +49,9 @@ export const ModToolsReducer: Reducer = (state, switch(action.type) { case ModToolsActions.SET_INIT_DATA: { - const data = (action.payload.settings || state.settings || null); + const settings = (action.payload.settings || state.settings || null); - return { ...state, data }; + return { ...state, settings }; } case ModToolsActions.SET_CURRENT_ROOM_ID: { const currentRoomId = (action.payload.currentRoomId || state.currentRoomId || null); From cf17d152d4ccebc363f75469ba06443ccf27886d Mon Sep 17 00:00:00 2001 From: dank074 Date: Wed, 20 Oct 2021 22:32:44 -0500 Subject: [PATCH 53/73] bill fix this again --- .../mod-tools/ModToolsMessageHandler.tsx | 67 +++++++++++++++++++ src/views/mod-tools/ModToolsView.tsx | 26 ++----- .../mod-tools/reducers/ModToolsReducer.tsx | 13 +++- .../views/tickets/ModToolsTicketsView.tsx | 27 ++++++-- 4 files changed, 102 insertions(+), 31 deletions(-) create mode 100644 src/views/mod-tools/ModToolsMessageHandler.tsx diff --git a/src/views/mod-tools/ModToolsMessageHandler.tsx b/src/views/mod-tools/ModToolsMessageHandler.tsx new file mode 100644 index 00000000..b9bae911 --- /dev/null +++ b/src/views/mod-tools/ModToolsMessageHandler.tsx @@ -0,0 +1,67 @@ +import { IssueInfoMessageEvent, ModeratorInitMessageEvent } from '@nitrots/nitro-renderer'; +import { FC, useCallback } from 'react'; +import { CreateMessageHook } from '../../hooks'; +import { useModToolsContext } from './context/ModToolsContext'; +import { ModToolsActions } from './reducers/ModToolsReducer'; + +export const ModToolsMessageHandler: FC<{}> = props => +{ + const { modToolsState = null, dispatchModToolsState = null } = useModToolsContext(); + const { tickets= null } = modToolsState; + + const onModeratorInitMessageEvent = useCallback((event: ModeratorInitMessageEvent) => + { + const parser = event.getParser(); + + if(!parser) return; + + const data = parser.data; + + dispatchModToolsState({ + type: ModToolsActions.SET_INIT_DATA, + payload: { + settings: data + } + }); + + dispatchModToolsState({ + type: ModToolsActions.SET_TICKETS, + payload: { + tickets: data.issues + } + }); + + console.log(parser); + }, [dispatchModToolsState]); + + const onIssueInfoMessageEvent = useCallback((event: IssueInfoMessageEvent) => + { + const parser = event.getParser(); + + if(!parser) return; + + const newTickets = tickets ? Array.from(tickets) : []; + const existingIndex = newTickets.findIndex( entry => entry.issueId === parser.issueData.issueId) + + if(existingIndex > -1) + { + newTickets[existingIndex] = parser.issueData; + } + else + { + newTickets.push(parser.issueData); + } + + dispatchModToolsState({ + type: ModToolsActions.SET_TICKETS, + payload: { + tickets: newTickets + } + }) + }, [dispatchModToolsState, tickets]); + + CreateMessageHook(ModeratorInitMessageEvent, onModeratorInitMessageEvent); + CreateMessageHook(IssueInfoMessageEvent, onIssueInfoMessageEvent); + + return null; +} diff --git a/src/views/mod-tools/ModToolsView.tsx b/src/views/mod-tools/ModToolsView.tsx index 4f73d340..5f3ba8d5 100644 --- a/src/views/mod-tools/ModToolsView.tsx +++ b/src/views/mod-tools/ModToolsView.tsx @@ -1,4 +1,4 @@ -import { ModeratorInitMessageEvent, RoomEngineEvent, RoomEngineObjectEvent, RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { RoomEngineEvent, RoomEngineObjectEvent, RoomObjectCategory } from '@nitrots/nitro-renderer'; import { FC, useCallback, useReducer, useState } from 'react'; import { GetRoomSession } from '../../api'; import { ModToolsEvent } from '../../events/mod-tools/ModToolsEvent'; @@ -6,11 +6,11 @@ import { ModToolsOpenRoomChatlogEvent } from '../../events/mod-tools/ModToolsOpe import { ModToolsOpenRoomInfoEvent } from '../../events/mod-tools/ModToolsOpenRoomInfoEvent'; import { ModToolsOpenUserChatlogEvent } from '../../events/mod-tools/ModToolsOpenUserChatlogEvent'; import { ModToolsOpenUserInfoEvent } from '../../events/mod-tools/ModToolsOpenUserInfoEvent'; -import { CreateMessageHook } from '../../hooks'; import { useRoomEngineEvent } from '../../hooks/events'; import { dispatchUiEvent, useUiEvent } from '../../hooks/events/ui/ui-event'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout'; import { ModToolsContextProvider } from './context/ModToolsContext'; +import { ModToolsMessageHandler } from './ModToolsMessageHandler'; import { ModToolsViewProps } from './ModToolsView.types'; import { initialModTools, ModToolsActions, ModToolsReducer } from './reducers/ModToolsReducer'; import { ISelectedUser } from './utils/ISelectedUser'; @@ -24,7 +24,7 @@ export const ModToolsView: FC = props => { const [ isVisible, setIsVisible ] = useState(false); const [ modToolsState, dispatchModToolsState ] = useReducer(ModToolsReducer, initialModTools); - const { currentRoomId = null, openRooms = null, openRoomChatlogs = null, openUserChatlogs = null, openUserInfo = null } = modToolsState; + const { currentRoomId = null, openRooms = null, openRoomChatlogs = null, openUserChatlogs = null, openUserInfo = null, tickets = null } = modToolsState; const [ selectedUser, setSelectedUser] = useState(null); const [ isTicketsVisible, setIsTicketsVisible ] = useState(false); @@ -155,25 +155,6 @@ export const ModToolsView: FC = props => useRoomEngineEvent(RoomEngineObjectEvent.SELECTED, onRoomEngineObjectEvent); - const onModeratorInitMessageEvent = useCallback((event: ModeratorInitMessageEvent) => - { - const parser = event.getParser(); - - if(!parser) return; - - const data = parser.data; - - dispatchModToolsState({ - type: ModToolsActions.SET_INIT_DATA, - payload: { - settings: data - } - }); - console.log(parser); - }, []); - - CreateMessageHook(ModeratorInitMessageEvent, onModeratorInitMessageEvent); - const handleClick = useCallback((action: string, value?: string) => { if(!action) return; @@ -299,6 +280,7 @@ export const ModToolsView: FC = props => return ( + { isVisible && setIsVisible(false) } /> diff --git a/src/views/mod-tools/reducers/ModToolsReducer.tsx b/src/views/mod-tools/reducers/ModToolsReducer.tsx index e1515e76..ee94db45 100644 --- a/src/views/mod-tools/reducers/ModToolsReducer.tsx +++ b/src/views/mod-tools/reducers/ModToolsReducer.tsx @@ -1,4 +1,4 @@ -import { ModeratorInitData } from '@nitrots/nitro-renderer'; +import { IssueMessageData, ModeratorInitData } from '@nitrots/nitro-renderer'; import { Reducer } from 'react'; export interface IModToolsState @@ -9,6 +9,7 @@ export interface IModToolsState openRoomChatlogs: number[]; openUserInfo: number[]; openUserChatlogs: number[]; + tickets: IssueMessageData[] } export interface IModToolsAction @@ -21,6 +22,7 @@ export interface IModToolsAction openRoomChatlogs?: number[]; openUserInfo?: number[]; openUserChatlogs?: number[]; + tickets?: IssueMessageData[]; } } @@ -32,6 +34,7 @@ export class ModToolsActions public static SET_OPEN_USERINFO: string = 'MTA_SET_OPEN_USERINFO'; public static SET_OPEN_ROOM_CHATLOGS: string = 'MTA_SET_OPEN_CHATLOGS'; public static SET_OPEN_USER_CHATLOGS: string = 'MTA_SET_OPEN_USER_CHATLOGS'; + public static SET_TICKETS: string = 'MTA_SET_TICKETS'; public static RESET_STATE: string = 'MTA_RESET_STATE'; } @@ -41,7 +44,8 @@ export const initialModTools: IModToolsState = { openRooms: null, openRoomChatlogs: null, openUserChatlogs: null, - openUserInfo: null + openUserInfo: null, + tickets: null }; export const ModToolsReducer: Reducer = (state, action) => @@ -78,6 +82,11 @@ export const ModToolsReducer: Reducer = (state, return { ...state, openUserChatlogs }; } + case ModToolsActions.SET_TICKETS: { + const tickets = (action.payload.tickets || state.tickets || null); + + return { ...state, tickets }; + } case ModToolsActions.RESET_STATE: { return { ...initialModTools }; } diff --git a/src/views/mod-tools/views/tickets/ModToolsTicketsView.tsx b/src/views/mod-tools/views/tickets/ModToolsTicketsView.tsx index 6f9471ae..38b7986b 100644 --- a/src/views/mod-tools/views/tickets/ModToolsTicketsView.tsx +++ b/src/views/mod-tools/views/tickets/ModToolsTicketsView.tsx @@ -1,5 +1,6 @@ import { IssueMessageData } from '@nitrots/nitro-renderer'; import { FC, useCallback, useMemo, useState } from 'react'; +import { GetSessionDataManager } from '../../../../api'; import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../layout'; import { useModToolsContext } from '../../context/ModToolsContext'; import { ModToolsTicketsViewProps } from './ModToolsTicketsView.types'; @@ -15,15 +16,29 @@ export const ModToolsTicketsView: FC = props => { const { onCloseClick = null } = props; const { modToolsState = null } = useModToolsContext(); - const { settings = null } = modToolsState; + const { tickets= null } = modToolsState; const [ currentTab, setCurrentTab ] = useState(0); const openIssues = useMemo(() => { - if(!settings) return []; + if(!tickets) return []; - return settings.issues.filter(issue => issue.state === IssueMessageData.STATE_OPEN) - }, [settings]); + return tickets.filter(issue => issue.state === IssueMessageData.STATE_OPEN); + }, [tickets]); + + const myIssues = useMemo(() => + { + if(!tickets) return []; + + return tickets.filter(issue => (issue.state === IssueMessageData.STATE_PICKED) && (issue.pickerUserId === GetSessionDataManager().userId)); + }, [tickets]); + + const pickedIssues = useMemo(() => + { + if(!tickets) return []; + + return tickets.filter(issue => issue.state === IssueMessageData.STATE_PICKED); + }, [tickets]); const CurrentTabComponent = useCallback(() => { @@ -34,8 +49,6 @@ export const ModToolsTicketsView: FC = props => } }, [currentTab, openIssues]); - console.log(settings); - return ( @@ -49,7 +62,7 @@ export const ModToolsTicketsView: FC = props => }) }
- {settings && } +
From 301cf3aa5a3dcac31d8efa1fa808636f10e87d9f Mon Sep 17 00:00:00 2001 From: dank074 Date: Wed, 20 Oct 2021 22:45:21 -0500 Subject: [PATCH 54/73] moved stuff to message handler --- .../mod-tools/ModToolsMessageHandler.tsx | 109 +++++++++++++++++- src/views/mod-tools/ModToolsView.tsx | 101 +--------------- 2 files changed, 110 insertions(+), 100 deletions(-) diff --git a/src/views/mod-tools/ModToolsMessageHandler.tsx b/src/views/mod-tools/ModToolsMessageHandler.tsx index b9bae911..9b3fa5a4 100644 --- a/src/views/mod-tools/ModToolsMessageHandler.tsx +++ b/src/views/mod-tools/ModToolsMessageHandler.tsx @@ -1,13 +1,18 @@ -import { IssueInfoMessageEvent, ModeratorInitMessageEvent } from '@nitrots/nitro-renderer'; +import { IssueInfoMessageEvent, ModeratorInitMessageEvent, RoomEngineEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback } from 'react'; -import { CreateMessageHook } from '../../hooks'; +import { ModToolsEvent } from '../../events/mod-tools/ModToolsEvent'; +import { ModToolsOpenRoomChatlogEvent } from '../../events/mod-tools/ModToolsOpenRoomChatlogEvent'; +import { ModToolsOpenRoomInfoEvent } from '../../events/mod-tools/ModToolsOpenRoomInfoEvent'; +import { ModToolsOpenUserChatlogEvent } from '../../events/mod-tools/ModToolsOpenUserChatlogEvent'; +import { ModToolsOpenUserInfoEvent } from '../../events/mod-tools/ModToolsOpenUserInfoEvent'; +import { CreateMessageHook, useRoomEngineEvent, useUiEvent } from '../../hooks'; import { useModToolsContext } from './context/ModToolsContext'; import { ModToolsActions } from './reducers/ModToolsReducer'; export const ModToolsMessageHandler: FC<{}> = props => { const { modToolsState = null, dispatchModToolsState = null } = useModToolsContext(); - const { tickets= null } = modToolsState; + const { openRooms = null, openRoomChatlogs = null, openUserChatlogs = null, openUserInfo = null, tickets= null } = modToolsState; const onModeratorInitMessageEvent = useCallback((event: ModeratorInitMessageEvent) => { @@ -63,5 +68,103 @@ export const ModToolsMessageHandler: FC<{}> = props => CreateMessageHook(ModeratorInitMessageEvent, onModeratorInitMessageEvent); CreateMessageHook(IssueInfoMessageEvent, onIssueInfoMessageEvent); + const onRoomEngineEvent = useCallback((event: RoomEngineEvent) => + { + switch(event.type) + { + case RoomEngineEvent.INITIALIZED: + dispatchModToolsState({ + type: ModToolsActions.SET_CURRENT_ROOM_ID, + payload: { + currentRoomId: event.roomId + } + }); + return; + case RoomEngineEvent.DISPOSED: + dispatchModToolsState({ + type: ModToolsActions.SET_CURRENT_ROOM_ID, + payload: { + currentRoomId: null + } + }); + return; + } + }, [ dispatchModToolsState ]); + + useRoomEngineEvent(RoomEngineEvent.INITIALIZED, onRoomEngineEvent); + useRoomEngineEvent(RoomEngineEvent.DISPOSED, onRoomEngineEvent); + + const onModToolsEvent = useCallback((event: ModToolsEvent) => + { + switch(event.type) + { + case ModToolsEvent.OPEN_ROOM_INFO: { + const castedEvent = (event as ModToolsOpenRoomInfoEvent); + + if(openRooms && openRooms.includes(castedEvent.roomId)) return; + + const rooms = openRooms || []; + + dispatchModToolsState({ + type: ModToolsActions.SET_OPEN_ROOMS, + payload: { + openRooms: [...rooms, castedEvent.roomId] + } + }); + return; + } + case ModToolsEvent.OPEN_ROOM_CHATLOG: { + const castedEvent = (event as ModToolsOpenRoomChatlogEvent); + + if(openRoomChatlogs && openRoomChatlogs.includes(castedEvent.roomId)) return; + + const chatlogs = openRoomChatlogs || []; + + dispatchModToolsState({ + type: ModToolsActions.SET_OPEN_ROOM_CHATLOGS, + payload: { + openRoomChatlogs: [...chatlogs, castedEvent.roomId] + } + }); + return; + } + case ModToolsEvent.OPEN_USER_INFO: { + const castedEvent = (event as ModToolsOpenUserInfoEvent); + + if(openUserInfo && openUserInfo.includes(castedEvent.userId)) return; + + const userInfo = openUserInfo || []; + + dispatchModToolsState({ + type: ModToolsActions.SET_OPEN_USERINFO, + payload: { + openUserInfo: [...userInfo, castedEvent.userId] + } + }); + return; + } + case ModToolsEvent.OPEN_USER_CHATLOG: { + const castedEvent = (event as ModToolsOpenUserChatlogEvent); + + if(openUserChatlogs && openUserChatlogs.includes(castedEvent.userId)) return; + + const userChatlog = openUserChatlogs || []; + + dispatchModToolsState({ + type: ModToolsActions.SET_OPEN_USER_CHATLOGS, + payload: { + openUserChatlogs: [...userChatlog, castedEvent.userId] + } + }); + return; + } + } + }, [openRooms, dispatchModToolsState, openRoomChatlogs, openUserInfo, openUserChatlogs]); + + useUiEvent(ModToolsEvent.OPEN_ROOM_INFO, onModToolsEvent); + useUiEvent(ModToolsEvent.OPEN_ROOM_CHATLOG, onModToolsEvent); + useUiEvent(ModToolsEvent.OPEN_USER_INFO, onModToolsEvent); + useUiEvent(ModToolsEvent.OPEN_USER_CHATLOG, onModToolsEvent); + return null; } diff --git a/src/views/mod-tools/ModToolsView.tsx b/src/views/mod-tools/ModToolsView.tsx index 5f3ba8d5..902ec5b9 100644 --- a/src/views/mod-tools/ModToolsView.tsx +++ b/src/views/mod-tools/ModToolsView.tsx @@ -1,10 +1,9 @@ -import { RoomEngineEvent, RoomEngineObjectEvent, RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { RoomEngineObjectEvent, RoomObjectCategory } from '@nitrots/nitro-renderer'; import { FC, useCallback, useReducer, useState } from 'react'; import { GetRoomSession } from '../../api'; import { ModToolsEvent } from '../../events/mod-tools/ModToolsEvent'; import { ModToolsOpenRoomChatlogEvent } from '../../events/mod-tools/ModToolsOpenRoomChatlogEvent'; import { ModToolsOpenRoomInfoEvent } from '../../events/mod-tools/ModToolsOpenRoomInfoEvent'; -import { ModToolsOpenUserChatlogEvent } from '../../events/mod-tools/ModToolsOpenUserChatlogEvent'; import { ModToolsOpenUserInfoEvent } from '../../events/mod-tools/ModToolsOpenUserInfoEvent'; import { useRoomEngineEvent } from '../../hooks/events'; import { dispatchUiEvent, useUiEvent } from '../../hooks/events/ui/ui-event'; @@ -24,7 +23,7 @@ export const ModToolsView: FC = props => { const [ isVisible, setIsVisible ] = useState(false); const [ modToolsState, dispatchModToolsState ] = useReducer(ModToolsReducer, initialModTools); - const { currentRoomId = null, openRooms = null, openRoomChatlogs = null, openUserChatlogs = null, openUserInfo = null, tickets = null } = modToolsState; + const { currentRoomId = null, openRooms = null, openRoomChatlogs = null, openUserChatlogs = null, openUserInfo = null } = modToolsState; const [ selectedUser, setSelectedUser] = useState(null); const [ isTicketsVisible, setIsTicketsVisible ] = useState(false); @@ -41,103 +40,13 @@ export const ModToolsView: FC = props => case ModToolsEvent.TOGGLE_MOD_TOOLS: setIsVisible(value => !value); return; - case ModToolsEvent.OPEN_ROOM_INFO: { - const castedEvent = (event as ModToolsOpenRoomInfoEvent); - - if(openRooms && openRooms.includes(castedEvent.roomId)) return; - - const rooms = openRooms || []; - - dispatchModToolsState({ - type: ModToolsActions.SET_OPEN_ROOMS, - payload: { - openRooms: [...rooms, castedEvent.roomId] - } - }); - return; - } - case ModToolsEvent.OPEN_ROOM_CHATLOG: { - const castedEvent = (event as ModToolsOpenRoomChatlogEvent); - - if(openRoomChatlogs && openRoomChatlogs.includes(castedEvent.roomId)) return; - - const chatlogs = openRoomChatlogs || []; - - dispatchModToolsState({ - type: ModToolsActions.SET_OPEN_ROOM_CHATLOGS, - payload: { - openRoomChatlogs: [...chatlogs, castedEvent.roomId] - } - }); - return; - } - case ModToolsEvent.OPEN_USER_INFO: { - const castedEvent = (event as ModToolsOpenUserInfoEvent); - - if(openUserInfo && openUserInfo.includes(castedEvent.userId)) return; - - const userInfo = openUserInfo || []; - - dispatchModToolsState({ - type: ModToolsActions.SET_OPEN_USERINFO, - payload: { - openUserInfo: [...userInfo, castedEvent.userId] - } - }); - return; - } - case ModToolsEvent.OPEN_USER_CHATLOG: { - const castedEvent = (event as ModToolsOpenUserChatlogEvent); - - if(openUserChatlogs && openUserChatlogs.includes(castedEvent.userId)) return; - - const userChatlog = openUserChatlogs || []; - - dispatchModToolsState({ - type: ModToolsActions.SET_OPEN_USER_CHATLOGS, - payload: { - openUserChatlogs: [...userChatlog, castedEvent.userId] - } - }); - return; - } } - }, [openRooms, openRoomChatlogs, openUserInfo, openUserChatlogs]); + }, []); useUiEvent(ModToolsEvent.SHOW_MOD_TOOLS, onModToolsEvent); useUiEvent(ModToolsEvent.HIDE_MOD_TOOLS, onModToolsEvent); useUiEvent(ModToolsEvent.TOGGLE_MOD_TOOLS, onModToolsEvent); - useUiEvent(ModToolsEvent.OPEN_ROOM_INFO, onModToolsEvent); - useUiEvent(ModToolsEvent.OPEN_ROOM_CHATLOG, onModToolsEvent); - useUiEvent(ModToolsEvent.OPEN_USER_INFO, onModToolsEvent); - useUiEvent(ModToolsEvent.OPEN_USER_CHATLOG, onModToolsEvent); - - const onRoomEngineEvent = useCallback((event: RoomEngineEvent) => - { - switch(event.type) - { - case RoomEngineEvent.INITIALIZED: - dispatchModToolsState({ - type: ModToolsActions.SET_CURRENT_ROOM_ID, - payload: { - currentRoomId: event.roomId - } - }); - return; - case RoomEngineEvent.DISPOSED: - dispatchModToolsState({ - type: ModToolsActions.SET_CURRENT_ROOM_ID, - payload: { - currentRoomId: null - } - }); - return; - } - }, [ dispatchModToolsState ]); - - useRoomEngineEvent(RoomEngineEvent.INITIALIZED, onRoomEngineEvent); - useRoomEngineEvent(RoomEngineEvent.DISPOSED, onRoomEngineEvent); - + const onRoomEngineObjectEvent = useCallback((event: RoomEngineObjectEvent) => { if(event.category !== RoomObjectCategory.UNIT) return; @@ -276,8 +185,6 @@ export const ModToolsView: FC = props => } }, [openRooms, currentRoomId, openRoomChatlogs, selectedUser, openUserInfo, openUserChatlogs]); - if(!isVisible) return null; - return ( From 5bcca108584a1547e088a4aa566e460dea3b943a Mon Sep 17 00:00:00 2001 From: dank074 Date: Wed, 20 Oct 2021 23:47:01 -0500 Subject: [PATCH 55/73] add packets --- .../mod-tools/ModToolsMessageHandler.tsx | 86 ++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/src/views/mod-tools/ModToolsMessageHandler.tsx b/src/views/mod-tools/ModToolsMessageHandler.tsx index 9b3fa5a4..1c5713d1 100644 --- a/src/views/mod-tools/ModToolsMessageHandler.tsx +++ b/src/views/mod-tools/ModToolsMessageHandler.tsx @@ -1,4 +1,4 @@ -import { IssueInfoMessageEvent, ModeratorInitMessageEvent, RoomEngineEvent } from '@nitrots/nitro-renderer'; +import { CfhSanctionMessageEvent, CfhTopicsInitEvent, IssueDeletedMessageEvent, IssueInfoMessageEvent, IssuePickFailedMessageEvent, ModeratorActionResultMessageEvent, ModeratorInitMessageEvent, ModeratorToolPreferencesEvent, RoomEngineEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback } from 'react'; import { ModToolsEvent } from '../../events/mod-tools/ModToolsEvent'; import { ModToolsOpenRoomChatlogEvent } from '../../events/mod-tools/ModToolsOpenRoomChatlogEvent'; @@ -62,11 +62,93 @@ export const ModToolsMessageHandler: FC<{}> = props => payload: { tickets: newTickets } - }) + }); + + //todo: play ticket sound + //GetNitroInstance().events.dispatchEvent(new NitroSoundEvent(NitroSoundEvent.PLAY_SOUND, sound) }, [dispatchModToolsState, tickets]); + const onModeratorToolPreferencesEvent = useCallback((event: ModeratorToolPreferencesEvent) => + { + const parser = event.getParser(); + + if(!parser) return; + + console.log(parser); + }, []); + + const onIssuePickFailedMessageEvent = useCallback((event: IssuePickFailedMessageEvent) => + { + const parser = event.getParser(); + + if(!parser) return; + + // todo: let user know it failed + }, []); + + const onIssueDeletedMessageEvent = useCallback((event: IssueDeletedMessageEvent) => + { + const parser = event.getParser(); + + if(!parser) return; + + const newTickets = tickets ? Array.from(tickets) : []; + const existingIndex = newTickets.findIndex( entry => entry.issueId === parser.issueId); + + if(existingIndex === -1) return; + + newTickets.splice(existingIndex, 1); + + dispatchModToolsState({ + type: ModToolsActions.SET_TICKETS, + payload: { + tickets: newTickets + } + }); + }, [dispatchModToolsState, tickets]); + + const onModeratorActionResultMessageEvent = useCallback((event: ModeratorActionResultMessageEvent) => + { + const parser = event.getParser(); + + if(!parser) return; + + if(parser.success) + { + // do something + } + else + { + // let user know it was a failure + } + }, []); + + const onCfhTopicsInitEvent = useCallback((event: CfhTopicsInitEvent) => + { + const parser = event.getParser(); + + if(!parser) return; + + console.log(parser); + }, []); + + const onCfhSanctionMessageEvent = useCallback((event: CfhSanctionMessageEvent) => + { + const parser = event.getParser(); + + if(!parser) return; + + console.log(parser); + }, []); + CreateMessageHook(ModeratorInitMessageEvent, onModeratorInitMessageEvent); CreateMessageHook(IssueInfoMessageEvent, onIssueInfoMessageEvent); + CreateMessageHook(ModeratorToolPreferencesEvent, onModeratorToolPreferencesEvent); + CreateMessageHook(IssuePickFailedMessageEvent, onIssuePickFailedMessageEvent); + CreateMessageHook(IssueDeletedMessageEvent, onIssueDeletedMessageEvent); + CreateMessageHook(ModeratorActionResultMessageEvent, onModeratorActionResultMessageEvent); + CreateMessageHook(CfhTopicsInitEvent, onCfhTopicsInitEvent); + CreateMessageHook(CfhSanctionMessageEvent, onCfhSanctionMessageEvent); const onRoomEngineEvent = useCallback((event: RoomEngineEvent) => { From 3ca58c81a4634462596c26ff09a0d9be35ea33ea Mon Sep 17 00:00:00 2001 From: dank074 Date: Thu, 21 Oct 2021 02:06:12 -0500 Subject: [PATCH 56/73] finished mod action --- .../mod-tools/ModToolsMessageHandler.tsx | 19 +- .../mod-tools/reducers/ModToolsReducer.tsx | 13 +- .../mod-tools/utils/ModActionDefinition.ts | 49 +++++ .../ModToolsUserModActionView.tsx | 181 +++++++++++++++++- .../ModToolsSendUserMessageView.tsx | 4 +- 5 files changed, 256 insertions(+), 10 deletions(-) create mode 100644 src/views/mod-tools/utils/ModActionDefinition.ts diff --git a/src/views/mod-tools/ModToolsMessageHandler.tsx b/src/views/mod-tools/ModToolsMessageHandler.tsx index 1c5713d1..0ffe0d0f 100644 --- a/src/views/mod-tools/ModToolsMessageHandler.tsx +++ b/src/views/mod-tools/ModToolsMessageHandler.tsx @@ -1,11 +1,12 @@ import { CfhSanctionMessageEvent, CfhTopicsInitEvent, IssueDeletedMessageEvent, IssueInfoMessageEvent, IssuePickFailedMessageEvent, ModeratorActionResultMessageEvent, ModeratorInitMessageEvent, ModeratorToolPreferencesEvent, RoomEngineEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback } from 'react'; +import { NotificationAlertEvent } from '../../events'; import { ModToolsEvent } from '../../events/mod-tools/ModToolsEvent'; import { ModToolsOpenRoomChatlogEvent } from '../../events/mod-tools/ModToolsOpenRoomChatlogEvent'; import { ModToolsOpenRoomInfoEvent } from '../../events/mod-tools/ModToolsOpenRoomInfoEvent'; import { ModToolsOpenUserChatlogEvent } from '../../events/mod-tools/ModToolsOpenUserChatlogEvent'; import { ModToolsOpenUserInfoEvent } from '../../events/mod-tools/ModToolsOpenUserInfoEvent'; -import { CreateMessageHook, useRoomEngineEvent, useUiEvent } from '../../hooks'; +import { CreateMessageHook, dispatchUiEvent, useRoomEngineEvent, useUiEvent } from '../../hooks'; import { useModToolsContext } from './context/ModToolsContext'; import { ModToolsActions } from './reducers/ModToolsReducer'; @@ -84,6 +85,7 @@ export const ModToolsMessageHandler: FC<{}> = props => if(!parser) return; // todo: let user know it failed + dispatchUiEvent(new NotificationAlertEvent(['Failed to pick issue'], null, null, null, 'Error', null)); }, []); const onIssueDeletedMessageEvent = useCallback((event: IssueDeletedMessageEvent) => @@ -115,11 +117,11 @@ export const ModToolsMessageHandler: FC<{}> = props => if(parser.success) { - // do something + dispatchUiEvent(new NotificationAlertEvent(['Moderation action was successfull'], null, null, null, 'Success', null)); } else { - // let user know it was a failure + dispatchUiEvent(new NotificationAlertEvent(['There was a problem applying that moderation action'], null, null, null, 'Error', null)); } }, []); @@ -128,9 +130,18 @@ export const ModToolsMessageHandler: FC<{}> = props => const parser = event.getParser(); if(!parser) return; + + const categories = parser.callForHelpCategories; + + dispatchModToolsState({ + type: ModToolsActions.SET_CFH_CATEGORIES, + payload: { + cfhCategories: categories + } + }); console.log(parser); - }, []); + }, [dispatchModToolsState]); const onCfhSanctionMessageEvent = useCallback((event: CfhSanctionMessageEvent) => { diff --git a/src/views/mod-tools/reducers/ModToolsReducer.tsx b/src/views/mod-tools/reducers/ModToolsReducer.tsx index ee94db45..b6fcf343 100644 --- a/src/views/mod-tools/reducers/ModToolsReducer.tsx +++ b/src/views/mod-tools/reducers/ModToolsReducer.tsx @@ -1,4 +1,4 @@ -import { IssueMessageData, ModeratorInitData } from '@nitrots/nitro-renderer'; +import { CallForHelpCategoryData, IssueMessageData, ModeratorInitData } from '@nitrots/nitro-renderer'; import { Reducer } from 'react'; export interface IModToolsState @@ -10,6 +10,7 @@ export interface IModToolsState openUserInfo: number[]; openUserChatlogs: number[]; tickets: IssueMessageData[] + cfhCategories: CallForHelpCategoryData[]; } export interface IModToolsAction @@ -23,6 +24,7 @@ export interface IModToolsAction openUserInfo?: number[]; openUserChatlogs?: number[]; tickets?: IssueMessageData[]; + cfhCategories?: CallForHelpCategoryData[]; } } @@ -35,6 +37,7 @@ export class ModToolsActions public static SET_OPEN_ROOM_CHATLOGS: string = 'MTA_SET_OPEN_CHATLOGS'; public static SET_OPEN_USER_CHATLOGS: string = 'MTA_SET_OPEN_USER_CHATLOGS'; public static SET_TICKETS: string = 'MTA_SET_TICKETS'; + public static SET_CFH_CATEGORIES: string = 'MTA_SET_CFH_CATEGORIES'; public static RESET_STATE: string = 'MTA_RESET_STATE'; } @@ -45,7 +48,8 @@ export const initialModTools: IModToolsState = { openRoomChatlogs: null, openUserChatlogs: null, openUserInfo: null, - tickets: null + tickets: null, + cfhCategories: null }; export const ModToolsReducer: Reducer = (state, action) => @@ -87,6 +91,11 @@ export const ModToolsReducer: Reducer = (state, return { ...state, tickets }; } + case ModToolsActions.SET_CFH_CATEGORIES: { + const cfhCategories = (action.payload.cfhCategories || state.cfhCategories || null); + + return { ...state, cfhCategories }; + } case ModToolsActions.RESET_STATE: { return { ...initialModTools }; } diff --git a/src/views/mod-tools/utils/ModActionDefinition.ts b/src/views/mod-tools/utils/ModActionDefinition.ts new file mode 100644 index 00000000..b8318afa --- /dev/null +++ b/src/views/mod-tools/utils/ModActionDefinition.ts @@ -0,0 +1,49 @@ +export class ModActionDefinition +{ + public static ALERT:number = 1; + public static MUTE:number = 2; + public static BAN:number = 3; + public static KICK:number = 4; + public static TRADE_LOCK:number = 5; + public static MESSAGE:number = 6; + + private readonly _actionId:number; + private readonly _name:string; + private readonly _actionType:number; + private readonly _sanctionTypeId:number; + private readonly _actionLengthHours:number; + + constructor(actionId:number, actionName:string, actionType:number, sanctionTypeId:number, actionLengthHours:number) + { + this._actionId = actionId; + this._name = actionName; + this._actionType = actionType; + this._sanctionTypeId = sanctionTypeId; + this._actionLengthHours = actionLengthHours; + } + + public get actionId():number + { + return this._actionId; + } + + public get name():string + { + return this._name; + } + + public get actionType():number + { + return this._actionType; + } + + public get sanctionTypeId():number + { + return this._sanctionTypeId; + } + + public get actionLengthHours():number + { + return this._actionLengthHours; + } +} diff --git a/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.tsx b/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.tsx index 2a8495a2..59e244f8 100644 --- a/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.tsx +++ b/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.tsx @@ -1,16 +1,191 @@ -import { FC } from 'react'; +import { CallForHelpTopicData, DefaultSanctionMessageComposer, ModAlertMessageComposer, ModBanMessageComposer, ModKickMessageComposer, ModMessageMessageComposer, ModMuteMessageComposer, ModTradingLockMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useMemo, useState } from 'react'; +import { LocalizeText } from '../../../../../api'; +import { NotificationAlertEvent } from '../../../../../events'; +import { dispatchUiEvent, SendMessageHook } from '../../../../../hooks'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout'; +import { useModToolsContext } from '../../../context/ModToolsContext'; +import { ModActionDefinition } from '../../../utils/ModActionDefinition'; import { ModToolsUserModActionViewProps } from './ModToolsUserModActionView.types'; +const actions = [ + 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 = props => { const { user = null, onCloseClick = null } = props; + const { modToolsState = null, dispatchModToolsState = null } = useModToolsContext(); + const { cfhCategories = null, settings = null } = modToolsState; + const [ selectedTopic, setSelectedTopic ] = useState(-1); + const [ selectedAction, setSelectedAction ] = useState(-1); + const [ message, setMessage ] = useState(''); + + const topics = useMemo(() => + { + const values: CallForHelpTopicData[] = []; + + if(!cfhCategories) return values; + + for(let category of cfhCategories) + { + for(let topic of category._Str_14841) + { + values.push(topic) + } + } + + return values; + }, [cfhCategories]); + + const sendSanction = useCallback(() => + { + if(selectedTopic === -1) + { + dispatchUiEvent(new NotificationAlertEvent(['You must select a CFH topic'], null, null, null, 'Error', null)); + return; + } + if(!settings || !settings.cfhPermission) + { + dispatchUiEvent(new NotificationAlertEvent(['You do not have permission to do this'], null, null, null, 'Error', null)); + return; + } + + const category = topics[selectedTopic]; + const sanction = actions[selectedAction]; + + if(!category) + { + dispatchUiEvent(new NotificationAlertEvent(['You must select a CFH topic'], null, null, null, 'Error', null)); + return; + } + + const messageOrDefault = message.trim().length === 0 ? LocalizeText('help.cfh.topic.' + category.id) : message; + + if(!sanction) // send default sanction + { + SendMessageHook(new DefaultSanctionMessageComposer(user.userId, category.id, messageOrDefault)); + onCloseClick(null); + return; + } + + switch(sanction.actionType) + { + case ModActionDefinition.ALERT: + + if(!settings.alertPermission) + { + dispatchUiEvent(new NotificationAlertEvent(['You have insufficient permissions.'], null, null, null, 'Error', null)); + return; + } + + if(message.trim().length === 0) + { + dispatchUiEvent(new NotificationAlertEvent(['Please write a message to user.'], null, null, null, 'Error', null)); + 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) + { + dispatchUiEvent(new NotificationAlertEvent(['You have insufficient permissions.'], null, null, null, 'Error', null)); + return; + } + + SendMessageHook(new ModBanMessageComposer(user.userId, messageOrDefault, category.id, selectedAction, (sanction.actionId === 106))); + + break; + + case ModActionDefinition.KICK: + + if(!settings.kickPermission) + { + dispatchUiEvent(new NotificationAlertEvent(['You have insufficient permissions.'], null, null, null, 'Error', null)); + 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) + { + dispatchUiEvent(new NotificationAlertEvent(['Please write a message to user.'], null, null, null, 'Error', null)); + return; + } + + SendMessageHook(new ModMessageMessageComposer(user.userId, message, category.id)); + + break; + } + + onCloseClick(null); + }, [message, onCloseClick, selectedAction, selectedTopic, settings, topics, user.userId]); return ( - + - {user &&
} + { user && + <> +
+ +
+ +
+ +
+ +
+ + + +
+ +
+
+ +
+
+ + + + + ) +} + +export interface FloorplanImportExportViewProps +{ + onCloseClick(): void; +} diff --git a/src/views/floorplan-editor/views/FloorplanOptionsView.tsx b/src/views/floorplan-editor/views/FloorplanOptionsView.tsx index e15229cc..0c6fc18a 100644 --- a/src/views/floorplan-editor/views/FloorplanOptionsView.tsx +++ b/src/views/floorplan-editor/views/FloorplanOptionsView.tsx @@ -1,18 +1,23 @@ import { FC, useCallback, useState } from 'react'; +import ReactSlider from 'react-slider'; import { LocalizeText } from '../../../api'; import { NitroCardGridItemView, NitroCardGridView, NitroLayoutFlex, NitroLayoutFlexColumn, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../layout'; import { NitroLayoutBase } from '../../../layout/base'; -import { FloorAction } from '../common/Constants'; +import { COLORMAP, FloorAction } from '../common/Constants'; import { FloorplanEditor } from '../common/FloorplanEditor'; import { useFloorplanEditorContext } from '../context/FloorplanEditorContext'; const MIN_WALL_HEIGHT: number = 0; const MAX_WALL_HEIGHT: number = 16; +const MIN_FLOOR_HEIGHT: number = 0; +const MAX_FLOOR_HEIGHT: number = 26; + export const FloorplanOptionsView: FC<{}> = props => { - const { floorplanSettings = null, setFloorplanSettings = null } = useFloorplanEditorContext(); + const { visualizationSettings = null, setVisualizationSettings = null } = useFloorplanEditorContext(); const [ floorAction, setFloorAction ] = useState(FloorAction.SET); + const [ floorHeight, setFloorHeight ] = useState(0); const selectAction = useCallback((action: number) => { @@ -22,7 +27,7 @@ export const FloorplanOptionsView: FC<{}> = props => const changeDoorDirection = useCallback(() => { - setFloorplanSettings(prevValue => + setVisualizationSettings(prevValue => { const newValue = Object.assign({}, prevValue); @@ -37,7 +42,40 @@ export const FloorplanOptionsView: FC<{}> = props => return newValue; }); - }, [ setFloorplanSettings ]); + }, [ setVisualizationSettings ]); + + const onFloorHeightChange = useCallback((value: number) => + { + if(isNaN(value) || (value <= 0)) value = 0; + + if(value > 26) value = 26; + + setFloorHeight(value); + + FloorplanEditor.instance.actionSettings.currentHeight = value.toString(36); + }, []); + + const onFloorThicknessChange = useCallback((value: number) => + { + setVisualizationSettings(prevValue => + { + const newValue = Object.assign({}, prevValue); + newValue.thicknessFloor = value; + + return newValue; + }); + }, [setVisualizationSettings]); + + const onWallThicknessChange = useCallback((value: number) => + { + setVisualizationSettings(prevValue => + { + const newValue = Object.assign({}, prevValue); + newValue.thicknessWall = value; + + return newValue; + }); + }, [setVisualizationSettings]); const onWallHeightChange = useCallback((value: number) => { @@ -45,7 +83,7 @@ export const FloorplanOptionsView: FC<{}> = props => if(value > MAX_WALL_HEIGHT) value = MAX_WALL_HEIGHT; - setFloorplanSettings(prevValue => + setVisualizationSettings(prevValue => { const newValue = Object.assign({}, prevValue); @@ -53,11 +91,11 @@ export const FloorplanOptionsView: FC<{}> = props => return newValue; }); - }, [ setFloorplanSettings ]); + }, [ setVisualizationSettings ]); function increaseWallHeight(): void { - let height = (floorplanSettings.wallHeight + 1); + let height = (visualizationSettings.wallHeight + 1); if(height > MAX_WALL_HEIGHT) height = MAX_WALL_HEIGHT; @@ -66,7 +104,7 @@ export const FloorplanOptionsView: FC<{}> = props => function decreaseWallHeight(): void { - let height = (floorplanSettings.wallHeight - 1); + let height = (visualizationSettings.wallHeight - 1); if(height <= 0) height = MIN_WALL_HEIGHT; @@ -97,10 +135,10 @@ export const FloorplanOptionsView: FC<{}> = props => - + { LocalizeText('floor.plan.editor.enter.direction') } - + @@ -108,41 +146,43 @@ export const FloorplanOptionsView: FC<{}> = props => { LocalizeText('floor.editor.wall.height') } - onWallHeightChange(event.target.valueAsNumber)} /> + onWallHeightChange(event.target.valueAsNumber)} /> + + + { LocalizeText('floor.plan.editor.tile.height') }: { floorHeight } + onFloorHeightChange(event) } + renderThumb={ ({ style, ...rest }, state) =>
{ state.valueNow }
} /> +
+
+ + + { LocalizeText('floor.plan.editor.room.options') } + + + + + + - // - // - // - // <> - //
- //
- // - // - // selectAction(FloorAction.SET)} className="tile-option set-tile" /> - // selectAction(FloorAction.UNSET)} className="tile-option unset-tile" /> - // selectAction(FloorAction.UP)} className="tile-option increase-height" /> - // selectAction(FloorAction.DOWN)} className="tile-option decrease-height" /> - // selectAction(FloorAction.DOOR)} className="tile-option set-door" /> - // - //
- //
- //
- // - // - //
- //
- // - // onWallHeightChange(event.target.valueAsNumber)} id="wallHeight"/> - //
- //
- //
- //
- - //
- // ); } From 96e83fdd31114de4d3e0a7f45f139a36a5881e06 Mon Sep 17 00:00:00 2001 From: dank074 Date: Tue, 2 Nov 2021 18:47:37 -0500 Subject: [PATCH 69/73] close on room leave --- src/views/floorplan-editor/FloorplanEditorView.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/views/floorplan-editor/FloorplanEditorView.tsx b/src/views/floorplan-editor/FloorplanEditorView.tsx index 6006e8c1..2f057b67 100644 --- a/src/views/floorplan-editor/FloorplanEditorView.tsx +++ b/src/views/floorplan-editor/FloorplanEditorView.tsx @@ -1,8 +1,8 @@ -import { FloorHeightMapEvent, NitroPoint, RoomVisualizationSettingsEvent, UpdateFloorPropertiesMessageComposer } from '@nitrots/nitro-renderer'; +import { FloorHeightMapEvent, NitroPoint, RoomEngineEvent, RoomVisualizationSettingsEvent, UpdateFloorPropertiesMessageComposer } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useState } from 'react'; import { LocalizeText } from '../../api'; import { FloorplanEditorEvent } from '../../events/floorplan-editor/FloorplanEditorEvent'; -import { CreateMessageHook, SendMessageHook, useUiEvent } from '../../hooks'; +import { CreateMessageHook, SendMessageHook, useRoomEngineEvent, useUiEvent } from '../../hooks'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutFlex, NitroLayoutGrid, NitroLayoutGridColumn } from '../../layout'; import { FloorplanEditor } from './common/FloorplanEditor'; import { convertNumbersForSaving, convertSettingToNumber } from './common/Utils'; @@ -50,6 +50,13 @@ export const FloorplanEditorView: FC<{}> = props => FloorplanEditor.instance.initialize(); }, []); + const onRoomEngineEvent = useCallback((event: RoomEngineEvent) => + { + setIsVisible(false); + }, []); + + useRoomEngineEvent(RoomEngineEvent.DISPOSED, onRoomEngineEvent); + const onFloorHeightMapEvent = useCallback((event: FloorHeightMapEvent) => { const parser = event.getParser(); From 5020f1f06357c81294e6f24c3f0d4c4861a0efff Mon Sep 17 00:00:00 2001 From: dank074 Date: Tue, 2 Nov 2021 20:46:48 -0500 Subject: [PATCH 70/73] update stuff to use mounteffect --- .../floorplan-editor/FloorplanEditorView.tsx | 13 ++++++------- .../views/FloorplanCanvasView.tsx | 15 +++++++++------ .../views/FloorplanImportExportView.tsx | 8 ++++---- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/views/floorplan-editor/FloorplanEditorView.tsx b/src/views/floorplan-editor/FloorplanEditorView.tsx index 2f057b67..48a7d34b 100644 --- a/src/views/floorplan-editor/FloorplanEditorView.tsx +++ b/src/views/floorplan-editor/FloorplanEditorView.tsx @@ -1,8 +1,8 @@ import { FloorHeightMapEvent, NitroPoint, RoomEngineEvent, RoomVisualizationSettingsEvent, UpdateFloorPropertiesMessageComposer } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useState } from 'react'; +import { FC, useCallback, useState } from 'react'; import { LocalizeText } from '../../api'; import { FloorplanEditorEvent } from '../../events/floorplan-editor/FloorplanEditorEvent'; -import { CreateMessageHook, SendMessageHook, useRoomEngineEvent, useUiEvent } from '../../hooks'; +import { CreateMessageHook, SendMessageHook, UseMountEffect, useRoomEngineEvent, useUiEvent } from '../../hooks'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutFlex, NitroLayoutGrid, NitroLayoutGridColumn } from '../../layout'; import { FloorplanEditor } from './common/FloorplanEditor'; import { convertNumbersForSaving, convertSettingToNumber } from './common/Utils'; @@ -45,10 +45,10 @@ export const FloorplanEditorView: FC<{}> = props => useUiEvent(FloorplanEditorEvent.SHOW_FLOORPLAN_EDITOR, onFloorplanEditorEvent); useUiEvent(FloorplanEditorEvent.TOGGLE_FLOORPLAN_EDITOR, onFloorplanEditorEvent); - useEffect(() => + UseMountEffect(() => { FloorplanEditor.instance.initialize(); - }, []); + }); const onRoomEngineEvent = useCallback((event: RoomEngineEvent) => { @@ -121,7 +121,7 @@ export const FloorplanEditorView: FC<{}> = props => return ( <> - {isVisible && <> + {isVisible && setIsVisible(false)} /> @@ -143,9 +143,8 @@ export const FloorplanEditorView: FC<{}> = props => - {importExportVisible && setImportExportVisible(false)}/>} - } + {importExportVisible && setImportExportVisible(false)}/>} ); diff --git a/src/views/floorplan-editor/views/FloorplanCanvasView.tsx b/src/views/floorplan-editor/views/FloorplanCanvasView.tsx index f9640a2c..f68a9248 100644 --- a/src/views/floorplan-editor/views/FloorplanCanvasView.tsx +++ b/src/views/floorplan-editor/views/FloorplanCanvasView.tsx @@ -1,6 +1,6 @@ import { GetOccupiedTilesMessageComposer, GetRoomEntryTileMessageComposer, NitroPoint, RoomEntryTileMessageEvent, RoomOccupiedTilesMessageEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useRef, useState } from 'react'; -import { CreateMessageHook, SendMessageHook } from '../../../hooks'; +import { CreateMessageHook, SendMessageHook, UseMountEffect } from '../../../hooks'; import { FloorplanEditor } from '../common/FloorplanEditor'; import { useFloorplanEditorContext } from '../context/FloorplanEditorContext'; @@ -13,11 +13,6 @@ export const FloorplanCanvasView: FC<{}> = props => useEffect(() => { - SendMessageHook(new GetRoomEntryTileMessageComposer()); - SendMessageHook(new GetOccupiedTilesMessageComposer()); - FloorplanEditor.instance.tilemapRenderer.interactive = true; - elementRef.current.appendChild(FloorplanEditor.instance.renderer.view); - return ( () => { FloorplanEditor.instance.clear(); @@ -25,6 +20,14 @@ export const FloorplanCanvasView: FC<{}> = props => }); }, [originalFloorplanSettings.thicknessFloor, originalFloorplanSettings.thicknessWall, originalFloorplanSettings.wallHeight, setVisualizationSettings]); + UseMountEffect(() => + { + SendMessageHook(new GetRoomEntryTileMessageComposer()); + SendMessageHook(new GetOccupiedTilesMessageComposer()); + FloorplanEditor.instance.tilemapRenderer.interactive = true; + elementRef.current.appendChild(FloorplanEditor.instance.renderer.view); + }); + const onRoomOccupiedTilesMessageEvent = useCallback((event: RoomOccupiedTilesMessageEvent) => { const parser = event.getParser(); diff --git a/src/views/floorplan-editor/views/FloorplanImportExportView.tsx b/src/views/floorplan-editor/views/FloorplanImportExportView.tsx index c707ff15..274d5ec6 100644 --- a/src/views/floorplan-editor/views/FloorplanImportExportView.tsx +++ b/src/views/floorplan-editor/views/FloorplanImportExportView.tsx @@ -1,7 +1,7 @@ import { UpdateFloorPropertiesMessageComposer } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useState } from 'react'; +import { FC, useCallback, useState } from 'react'; import { LocalizeText } from '../../../api'; -import { SendMessageHook } from '../../../hooks'; +import { SendMessageHook, UseMountEffect } from '../../../hooks'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutFlex, NitroLayoutGridColumn } from '../../../layout'; import { convertNumbersForSaving } from '../common/Utils'; import { useFloorplanEditorContext } from '../context/FloorplanEditorContext'; @@ -36,10 +36,10 @@ export const FloorplanImportExportView: FC = pro )); }, [map, originalFloorplanSettings.entryPoint, originalFloorplanSettings.entryPointDir, originalFloorplanSettings.thicknessFloor, originalFloorplanSettings.thicknessWall, originalFloorplanSettings.wallHeight]); - useEffect(() => + UseMountEffect(() => { revertChanges(); - }, [revertChanges]); + }); return ( From 4a8f738bb035e4fffdc5e0d9adea881e19ca682c Mon Sep 17 00:00:00 2001 From: dank074 Date: Tue, 2 Nov 2021 21:38:58 -0500 Subject: [PATCH 71/73] fix chooser resize --- .../widgets/choosers/ChooserWidgetView.scss | 15 ++++++++----- .../widgets/choosers/ChooserWidgetView.tsx | 21 ++++++++++++------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/views/room/widgets/choosers/ChooserWidgetView.scss b/src/views/room/widgets/choosers/ChooserWidgetView.scss index 9a461869..5365dd13 100644 --- a/src/views/room/widgets/choosers/ChooserWidgetView.scss +++ b/src/views/room/widgets/choosers/ChooserWidgetView.scss @@ -1,11 +1,16 @@ .nitro-chooser-widget { + - .list-item { - color: black; - overflow: hidden; + .chooser-container { + min-height: 150px; - &.selected { - background-color: cadetblue; + .list-item { + color: black; + overflow: hidden; + + &.selected { + background-color: cadetblue; + } } } } diff --git a/src/views/room/widgets/choosers/ChooserWidgetView.tsx b/src/views/room/widgets/choosers/ChooserWidgetView.tsx index 638b4999..492b5164 100644 --- a/src/views/room/widgets/choosers/ChooserWidgetView.tsx +++ b/src/views/room/widgets/choosers/ChooserWidgetView.tsx @@ -1,5 +1,5 @@ import { FC, useCallback, useMemo, useState } from 'react'; -import { List, ListRowProps, ListRowRenderer } from 'react-virtualized'; +import { AutoSizer, List, ListRowProps, ListRowRenderer } from 'react-virtualized'; import { RoomObjectItem, RoomWidgetRoomObjectMessage } from '../../../../api'; import { LocalizeText } from '../../../../api/utils'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout'; @@ -53,12 +53,19 @@ export const ChooserWidgetView: FC = props => setSearchValue(event.target.value)} />
- +
+ + {({ height, width }) => + { + return () + }} + +
); From ec1419acf286787c0b5f1d06a36ef43d15ed806c Mon Sep 17 00:00:00 2001 From: Bill Date: Fri, 12 Nov 2021 00:26:53 -0500 Subject: [PATCH 72/73] Rename packets --- src/views/chat-history/ChatHistoryMessageHandler.tsx | 6 +++--- .../room-information/GroupRoomInformationView.tsx | 6 +++--- src/views/navigator/NavigatorMessageHandler.tsx | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/views/chat-history/ChatHistoryMessageHandler.tsx b/src/views/chat-history/ChatHistoryMessageHandler.tsx index d1165d39..a482a36a 100644 --- a/src/views/chat-history/ChatHistoryMessageHandler.tsx +++ b/src/views/chat-history/ChatHistoryMessageHandler.tsx @@ -1,4 +1,4 @@ -import { RoomInfoEvent, RoomSessionChatEvent, RoomSessionEvent } from '@nitrots/nitro-renderer'; +import { GetGuestRoomResultEvent, RoomSessionChatEvent, RoomSessionEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback, useState } from 'react'; import { GetRoomSession } from '../../api'; import { CreateMessageHook, useRoomSessionManagerEvent } from '../../hooks'; @@ -77,7 +77,7 @@ export const ChatHistoryMessageHandler: FC<{}> = props => useRoomSessionManagerEvent(RoomSessionEvent.ENDED, onRoomSessionEvent); useRoomSessionManagerEvent(RoomSessionEvent.STARTED, onRoomSessionEvent); - const onRoomInfoEvent = useCallback((event: RoomInfoEvent) => + const onGetGuestRoomResultEvent = useCallback((event: GetGuestRoomResultEvent) => { const parser = event.getParser(); @@ -101,7 +101,7 @@ export const ChatHistoryMessageHandler: FC<{}> = props => } }, [addChatEntry, addRoomHistoryEntry, needsRoomInsert]); - CreateMessageHook(RoomInfoEvent, onRoomInfoEvent); + CreateMessageHook(GetGuestRoomResultEvent, onGetGuestRoomResultEvent); return null; } diff --git a/src/views/groups/views/room-information/GroupRoomInformationView.tsx b/src/views/groups/views/room-information/GroupRoomInformationView.tsx index 9b8f29b7..edbf18b4 100644 --- a/src/views/groups/views/room-information/GroupRoomInformationView.tsx +++ b/src/views/groups/views/room-information/GroupRoomInformationView.tsx @@ -1,4 +1,4 @@ -import { DesktopViewEvent, GroupInformationComposer, GroupInformationEvent, GroupInformationParser, GroupJoinComposer, GroupRemoveMemberComposer, RoomInfoEvent } from '@nitrots/nitro-renderer'; +import { DesktopViewEvent, GetGuestRoomResultEvent, GroupInformationComposer, GroupInformationEvent, GroupInformationParser, GroupJoinComposer, GroupRemoveMemberComposer } from '@nitrots/nitro-renderer'; import { FC, useCallback, useState } from 'react'; import { GetGroupInformation, GetSessionDataManager, LocalizeText } from '../../../../api'; import { GetGroupManager } from '../../../../api/groups/GetGroupManager'; @@ -13,7 +13,7 @@ export const GroupRoomInformationView: FC<{}> = props => const [ groupInformation, setGroupInformation ] = useState(null); const [ isExpended, setIsExpended ] = useState(true); - const onRoomInfoEvent = useCallback((event: RoomInfoEvent) => + const onGetGuestRoomResultEvent = useCallback((event: GetGuestRoomResultEvent) => { const parser = event.getParser(); @@ -26,7 +26,7 @@ export const GroupRoomInformationView: FC<{}> = props => } }, []); - CreateMessageHook(RoomInfoEvent, onRoomInfoEvent); + CreateMessageHook(GetGuestRoomResultEvent, onGetGuestRoomResultEvent); const onGroupInformationEvent = useCallback((event: GroupInformationEvent) => { diff --git a/src/views/navigator/NavigatorMessageHandler.tsx b/src/views/navigator/NavigatorMessageHandler.tsx index f2e4e019..3ca2dea8 100644 --- a/src/views/navigator/NavigatorMessageHandler.tsx +++ b/src/views/navigator/NavigatorMessageHandler.tsx @@ -1,4 +1,4 @@ -import { GenericErrorEvent, NavigatorCategoriesComposer, NavigatorCategoriesEvent, NavigatorHomeRoomEvent, NavigatorMetadataEvent, NavigatorSearchEvent, NavigatorSettingsComposer, RoomCreatedEvent, RoomDataParser, RoomDoorbellAcceptedEvent, RoomDoorbellEvent, RoomDoorbellRejectedEvent, RoomForwardEvent, RoomInfoComposer, RoomInfoEvent, RoomInfoOwnerEvent, RoomSettingsUpdatedEvent, UserInfoEvent } from '@nitrots/nitro-renderer'; +import { GenericErrorEvent, GetGuestRoomResultEvent, NavigatorCategoriesComposer, NavigatorCategoriesEvent, NavigatorHomeRoomEvent, NavigatorMetadataEvent, NavigatorSearchEvent, NavigatorSettingsComposer, RoomCreatedEvent, RoomDataParser, RoomDoorbellAcceptedEvent, RoomDoorbellEvent, RoomDoorbellRejectedEvent, RoomEntryInfoMessageEvent, RoomForwardEvent, RoomInfoComposer, RoomSettingsUpdatedEvent, UserInfoEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback } from 'react'; import { CreateRoomSession, GetSessionDataManager } from '../../api'; import { UpdateDoorStateEvent } from '../../events'; @@ -25,7 +25,7 @@ export const NavigatorMessageHandler: FC = props = SendMessageHook(new RoomInfoComposer(parser.roomId, false, true)); }, []); - const onRoomInfoOwnerEvent = useCallback((event: RoomInfoOwnerEvent) => + const onRoomEntryInfoMessageEvent = useCallback((event: RoomEntryInfoMessageEvent) => { const parser = event.getParser(); @@ -43,7 +43,7 @@ export const NavigatorMessageHandler: FC = props = SendMessageHook(new RoomInfoComposer(parser.roomId, true, false)); }, [ navigatorState, dispatchNavigatorState ]); - const onRoomInfoEvent = useCallback((event: RoomInfoEvent) => + const onGetGuestRoomResultEvent = useCallback((event: GetGuestRoomResultEvent) => { const parser = event.getParser(); @@ -196,8 +196,8 @@ export const NavigatorMessageHandler: FC = props = CreateMessageHook(UserInfoEvent, onUserInfoEvent); CreateMessageHook(RoomForwardEvent, onRoomForwardEvent); - CreateMessageHook(RoomInfoOwnerEvent, onRoomInfoOwnerEvent); - CreateMessageHook(RoomInfoEvent, onRoomInfoEvent); + CreateMessageHook(RoomEntryInfoMessageEvent, onRoomEntryInfoMessageEvent); + CreateMessageHook(GetGuestRoomResultEvent, onGetGuestRoomResultEvent); CreateMessageHook(RoomDoorbellEvent, onRoomDoorbellEvent); CreateMessageHook(RoomDoorbellAcceptedEvent, onRoomDoorbellAcceptedEvent); CreateMessageHook(RoomDoorbellRejectedEvent, onRoomDoorbellRejectedEvent); From 67a25098916145fb461092095f3357568ec4936e Mon Sep 17 00:00:00 2001 From: Bill Date: Fri, 12 Nov 2021 00:48:16 -0500 Subject: [PATCH 73/73] Change index.html --- public/index.html | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/public/index.html b/public/index.html index f40b6252..73ac4aa5 100644 --- a/public/index.html +++ b/public/index.html @@ -3,12 +3,8 @@ - + - Nitro