diff --git a/src/views/inventory/reducers/InventoryBotReducer.tsx b/src/views/inventory/reducers/InventoryBotReducer.tsx new file mode 100644 index 00000000..b3a8c369 --- /dev/null +++ b/src/views/inventory/reducers/InventoryBotReducer.tsx @@ -0,0 +1,86 @@ +import { BotData } from 'nitro-renderer'; +import { Reducer } from 'react'; +import { BotItem } from '../utils/BotItem'; +import { addSingleBotItem, processBotFragment, removeBotItemById } from '../utils/BotUtilities'; + +export interface IInventoryBotState +{ + needsBotUpdate: boolean; + botItem: BotItem; + botItems: BotItem[]; +} + +export interface IInventoryBotAction +{ + type: string; + payload: { + flag?: boolean; + botItem?: BotItem; + botId?: number; + botData?: BotData; + fragment?: BotData[]; + } +} + +export class InventoryBotActions +{ + public static SET_NEEDS_UPDATE: string = 'IBA_SET_NEEDS_UPDATE'; + public static SET_BOT_ITEM: string = 'IBA_SET_BOT_ITEM'; + public static PROCESS_FRAGMENT: string = 'IBA_PROCESS_FRAGMENT'; + public static ADD_BOT: string = 'IBA_ADD_BOT'; + public static REMOVE_BOT: string = 'IBA_REMOVE_BOT'; +} + +export const initialInventoryBot: IInventoryBotState = { + needsBotUpdate: true, + botItem: null, + botItems: [] +} + +export const inventoryBotReducer: Reducer = (state, action) => +{ + switch(action.type) + { + case InventoryBotActions.SET_NEEDS_UPDATE: + return { ...state, needsBotUpdate: (action.payload.flag || false) }; + case InventoryBotActions.SET_BOT_ITEM: { + let botItem = (action.payload.botItem || state.botItem || null); + + let index = 0; + + if(botItem) + { + const foundIndex = state.botItems.indexOf(botItem); + + if(foundIndex > -1) index = foundIndex; + } + + botItem = (state.botItems[index] || null); + + return { ...state, botItem }; + } + case InventoryBotActions.PROCESS_FRAGMENT: { + const botItems = [ ...state.botItems ]; + + processBotFragment(botItems, action.payload.fragment); + + return { ...state, botItems }; + } + case InventoryBotActions.ADD_BOT: { + const botItems = [ ...state.botItems ]; + + addSingleBotItem(action.payload.botData, botItems, true); + + return { ...state, botItems }; + } + case InventoryBotActions.REMOVE_BOT: { + const botItems = [ ...state.botItems ]; + + removeBotItemById(action.payload.botId, botItems); + + return { ...state, botItems }; + } + default: + return state; + } +} diff --git a/src/views/inventory/utils/BotItem.ts b/src/views/inventory/utils/BotItem.ts new file mode 100644 index 00000000..912adc12 --- /dev/null +++ b/src/views/inventory/utils/BotItem.ts @@ -0,0 +1,45 @@ +import { BotData } from 'nitro-renderer'; + +export class BotItem +{ + private _botData: BotData; + private _selected: boolean; + private _isUnseen: boolean; + + constructor(botData: BotData) + { + this._botData = botData; + this._selected = false; + this._isUnseen = false; + } + + public get id(): number + { + return this._botData.id; + } + + public get botData(): BotData + { + return this._botData; + } + + public get selected(): boolean + { + return this._selected; + } + + public set selected(flag: boolean) + { + this._selected = flag; + } + + public get isUnseen(): boolean + { + return this._isUnseen; + } + + public set isUnseen(flag: boolean) + { + this._isUnseen = flag; + } +} diff --git a/src/views/inventory/views/bot/InventoryBotView.scss b/src/views/inventory/views/bot/InventoryBotView.scss new file mode 100644 index 00000000..d82c5a47 --- /dev/null +++ b/src/views/inventory/views/bot/InventoryBotView.scss @@ -0,0 +1,2 @@ +@import './item/InventoryBotItemView'; +@import './results/InventoryBotResultsView'; diff --git a/src/views/inventory/views/bot/InventoryBotView.tsx b/src/views/inventory/views/bot/InventoryBotView.tsx new file mode 100644 index 00000000..09600141 --- /dev/null +++ b/src/views/inventory/views/bot/InventoryBotView.tsx @@ -0,0 +1,97 @@ +import { GetBotInventoryComposer, RoomObjectVariable } from 'nitro-renderer'; +import { FC, useEffect } from 'react'; +import { GetRoomEngine } from '../../../../api'; +import { SendMessageHook } from '../../../../hooks/messages/message-event'; +import { LocalizeText } from '../../../../utils/LocalizeText'; +import { RoomPreviewerView } from '../../../room-previewer/RoomPreviewerView'; +import { useInventoryContext } from '../../context/InventoryContext'; +import { InventoryBotActions } from '../../reducers/InventoryBotReducer'; +import { attemptBotPlacement } from '../../utils/BotUtilities'; +import { InventoryBotViewProps } from './InventoryBotView.types'; +import { InventoryBotResultsView } from './results/InventoryBotResultsView'; + +export const InventoryBotView: FC = props => +{ + const { roomSession = null, roomPreviewer = null } = props; + const { botState = null, dispatchBotState = null } = useInventoryContext(); + const { needsBotUpdate = false, botItem = null, botItems = [] } = botState; + + useEffect(() => + { + if(needsBotUpdate) + { + dispatchBotState({ + type: InventoryBotActions.SET_NEEDS_UPDATE, + payload: { + flag: false + } + }); + + SendMessageHook(new GetBotInventoryComposer()); + } + else + { + dispatchBotState({ + type: InventoryBotActions.SET_BOT_ITEM, + payload: { + botItem: null + } + }); + } + + }, [ needsBotUpdate, botItems, dispatchBotState ]); + + useEffect(() => + { + if(!botItem || !roomPreviewer) return; + + const botData = botItem.botData; + + const roomEngine = GetRoomEngine(); + + let wallType = roomEngine.getRoomInstanceVariable(roomEngine.activeRoomId, RoomObjectVariable.ROOM_WALL_TYPE); + let floorType = roomEngine.getRoomInstanceVariable(roomEngine.activeRoomId, RoomObjectVariable.ROOM_FLOOR_TYPE); + let landscapeType = roomEngine.getRoomInstanceVariable(roomEngine.activeRoomId, RoomObjectVariable.ROOM_LANDSCAPE_TYPE); + + wallType = (wallType && wallType.length) ? wallType : '101'; + floorType = (floorType && floorType.length) ? floorType : '101'; + landscapeType = (landscapeType && landscapeType.length) ? landscapeType : '1.1'; + + roomPreviewer.reset(false); + roomPreviewer.updateRoomWallsAndFloorVisibility(true, true); + roomPreviewer.updateObjectRoom(floorType, wallType, landscapeType); + roomPreviewer.addAvatarIntoRoom(botData.figure, 0); + }, [ roomPreviewer, botItem ]); + + if(!botItems || !botItems.length) + { + return ( +
+
+
+
+
+
+ { LocalizeText('inventory.empty.bots.title') } +
+
{ LocalizeText('inventory.empty.bots.desc') }
+
+
+ ); + } + + return ( +
+
+ +
+
+ + { botItem &&
+

{ botItem.botData.name }

+ { !!roomSession && } +
} +
+
+ ); +} diff --git a/src/views/inventory/views/bot/InventoryBotView.types.ts b/src/views/inventory/views/bot/InventoryBotView.types.ts new file mode 100644 index 00000000..fc070c8d --- /dev/null +++ b/src/views/inventory/views/bot/InventoryBotView.types.ts @@ -0,0 +1,7 @@ +import { IRoomSession, RoomPreviewer } from 'nitro-renderer'; + +export interface InventoryBotViewProps +{ + roomSession: IRoomSession; + roomPreviewer: RoomPreviewer; +} diff --git a/src/views/inventory/views/bot/item/InventoryBotItemView.scss b/src/views/inventory/views/bot/item/InventoryBotItemView.scss new file mode 100644 index 00000000..6dd56fb2 --- /dev/null +++ b/src/views/inventory/views/bot/item/InventoryBotItemView.scss @@ -0,0 +1,25 @@ +.inventory-bot-item-container { + height: 48px; + max-height: 48px; + + .inventory-bot-item { + width: 100%; + height: 100%; + border-color: $muted !important; + background-color: #CDD3D9; + overflow: hidden; + + &.active { + border-color: $white !important; + background-color: #ECECEC; + } + + .avatar-image { + width: 100% !important; + height: 100% !important; + background-position: center; + background-repeat: no-repeat; + background-position-y: -32px !important; + } + } +} diff --git a/src/views/inventory/views/bot/item/InventoryBotItemView.tsx b/src/views/inventory/views/bot/item/InventoryBotItemView.tsx new file mode 100644 index 00000000..408231f9 --- /dev/null +++ b/src/views/inventory/views/bot/item/InventoryBotItemView.tsx @@ -0,0 +1,46 @@ +import { MouseEventType } from 'nitro-renderer'; +import { FC, MouseEvent, useCallback, useState } from 'react'; +import { AvatarImageView } from '../../../../avatar-image/AvatarImageView'; +import { useInventoryContext } from '../../../context/InventoryContext'; +import { InventoryBotActions } from '../../../reducers/InventoryBotReducer'; +import { attemptBotPlacement } from '../../../utils/BotUtilities'; +import { InventoryBotItemViewProps } from './InventoryBotItemView.types'; + +export const InventoryBotItemView: FC = props => +{ + const { botItem } = props; + const { botState = null, dispatchBotState = null } = useInventoryContext(); + const [ isMouseDown, setMouseDown ] = useState(false); + const isActive = (botState.botItem === botItem); + + const onMouseEvent = useCallback((event: MouseEvent) => + { + switch(event.type) + { + case MouseEventType.MOUSE_DOWN: + dispatchBotState({ + type: InventoryBotActions.SET_BOT_ITEM, + payload: { botItem } + }); + + setMouseDown(true); + return; + case MouseEventType.MOUSE_UP: + setMouseDown(false); + return; + case MouseEventType.ROLL_OUT: + if(!isMouseDown || !isActive) return; + + attemptBotPlacement(botItem); + return; + } + }, [ isActive, isMouseDown, botItem, dispatchBotState ]); + + return ( +
+
+ +
+
+ ); +} diff --git a/src/views/inventory/views/bot/item/InventoryBotItemView.types.ts b/src/views/inventory/views/bot/item/InventoryBotItemView.types.ts new file mode 100644 index 00000000..8d80c9b4 --- /dev/null +++ b/src/views/inventory/views/bot/item/InventoryBotItemView.types.ts @@ -0,0 +1,6 @@ +import { BotItem } from '../../../utils/BotItem'; + +export interface InventoryBotItemViewProps +{ + botItem: BotItem; +} diff --git a/src/views/inventory/views/bot/results/InventoryBotResultsView.scss b/src/views/inventory/views/bot/results/InventoryBotResultsView.scss new file mode 100644 index 00000000..a8cf160c --- /dev/null +++ b/src/views/inventory/views/bot/results/InventoryBotResultsView.scss @@ -0,0 +1,5 @@ +.bot-item-container { + height: 226px; + max-height: 226px; + overflow-y: auto; +} diff --git a/src/views/inventory/views/bot/results/InventoryBotResultsView.tsx b/src/views/inventory/views/bot/results/InventoryBotResultsView.tsx new file mode 100644 index 00000000..7a08396c --- /dev/null +++ b/src/views/inventory/views/bot/results/InventoryBotResultsView.tsx @@ -0,0 +1,17 @@ +import { FC } from 'react'; +import { InventoryBotItemView } from '../item/InventoryBotItemView'; +import { InventoryBotResultsViewProps } from './InventoryBotResultsView.types'; + +export const InventoryBotResultsView: FC = props => +{ + const { botItems = [] } = props; + + return ( +
+ { (botItems && botItems.length && botItems.map((item, index) => + { + return + })) || null } +
+ ); +} diff --git a/src/views/inventory/views/bot/results/InventoryBotResultsView.types.ts b/src/views/inventory/views/bot/results/InventoryBotResultsView.types.ts new file mode 100644 index 00000000..bca525c7 --- /dev/null +++ b/src/views/inventory/views/bot/results/InventoryBotResultsView.types.ts @@ -0,0 +1,6 @@ +import { BotItem } from '../../../utils/BotItem'; + +export interface InventoryBotResultsViewProps +{ + botItems: BotItem[]; +}