From 47061493427cafff3f042e6c0f71f607524ca68d Mon Sep 17 00:00:00 2001 From: Dank074 Date: Tue, 10 Aug 2021 19:47:16 -0500 Subject: [PATCH] created user and furni choosers --- package.json | 1 + .../room-widgets/choosers/RoomObjectItem.ts | 28 +++++ .../choosers/RoomWidgetChooserContentEvent.ts | 29 +++++ src/views/room/RoomView.tsx | 4 + .../handlers/FurniChooserWidgetHandler.ts | 108 ++++++++++++++++ .../room/handlers/UserChooserWidgetHandler.ts | 84 +++++++++++++ src/views/room/widgets/RoomWidgets.scss | 1 + src/views/room/widgets/RoomWidgetsView.tsx | 4 + .../widgets/choosers/ChooserWidgetView.scss | 9 ++ .../choosers/FurniChooserWidgetView.tsx | 118 ++++++++++++++++++ .../choosers/UserChooserWidgetView.tsx | 117 +++++++++++++++++ .../room/widgets/choosers/utils/sorting.ts | 18 +++ 12 files changed, 521 insertions(+) create mode 100644 src/events/room-widgets/choosers/RoomObjectItem.ts create mode 100644 src/events/room-widgets/choosers/RoomWidgetChooserContentEvent.ts create mode 100644 src/views/room/handlers/FurniChooserWidgetHandler.ts create mode 100644 src/views/room/handlers/UserChooserWidgetHandler.ts create mode 100644 src/views/room/widgets/choosers/ChooserWidgetView.scss create mode 100644 src/views/room/widgets/choosers/FurniChooserWidgetView.tsx create mode 100644 src/views/room/widgets/choosers/UserChooserWidgetView.tsx create mode 100644 src/views/room/widgets/choosers/utils/sorting.ts diff --git a/package.json b/package.json index 8aa3854f..6ec3672f 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "react-scripts": "4.0.3", "react-slider": "^1.3.1", "react-transition-group": "^4.4.2", + "react-virtualized": "^9.22.3", "typescript": "^4.3.5", "web-vitals": "^1.1.2" }, diff --git a/src/events/room-widgets/choosers/RoomObjectItem.ts b/src/events/room-widgets/choosers/RoomObjectItem.ts new file mode 100644 index 00000000..d11fc5a8 --- /dev/null +++ b/src/events/room-widgets/choosers/RoomObjectItem.ts @@ -0,0 +1,28 @@ +export class RoomObjectItem +{ + private readonly _id: number; + private readonly _category: number; + private readonly _name: string; + + constructor(id: number, category: number, name: string) + { + this._id = id; + this._category = category; + this._name = name; + } + + public get id(): number + { + return this._id; + } + + public get category(): number + { + return this._category; + } + + public get name(): string + { + return this._name; + } +} diff --git a/src/events/room-widgets/choosers/RoomWidgetChooserContentEvent.ts b/src/events/room-widgets/choosers/RoomWidgetChooserContentEvent.ts new file mode 100644 index 00000000..aabb254e --- /dev/null +++ b/src/events/room-widgets/choosers/RoomWidgetChooserContentEvent.ts @@ -0,0 +1,29 @@ +import { RoomWidgetUpdateEvent } from '../../../views/room/events'; +import { RoomObjectItem } from './RoomObjectItem'; + +export class RoomWidgetChooserContentEvent extends RoomWidgetUpdateEvent +{ + public static USER_CHOOSER_CONTENT: string = 'RWCCE_USER_CHOOSER_CONTENT'; + public static FURNI_CHOOSER_CONTENT: string = 'RWCCE_FURNI_CHOOSER_CONTENT'; + + private _items: RoomObjectItem[]; + private _isAnyRoomController: boolean; + + constructor(type: string, items: RoomObjectItem[], isAnyRoomController: boolean = false) + { + super(type); + + this._items = items.slice(); + this._isAnyRoomController = isAnyRoomController; + } + + public get items(): RoomObjectItem[] + { + return this._items; + } + + public get isAnyRoomController(): boolean + { + return this._isAnyRoomController; + } +} diff --git a/src/views/room/RoomView.tsx b/src/views/room/RoomView.tsx index ed5f14aa..0d7a0362 100644 --- a/src/views/room/RoomView.tsx +++ b/src/views/room/RoomView.tsx @@ -8,9 +8,11 @@ import { GetRoomEngine } from '../../api/nitro/room/GetRoomEngine'; import { RoomContextProvider } from './context/RoomContext'; import { RoomWidgetUpdateRoomViewEvent } from './events/RoomWidgetUpdateRoomViewEvent'; import { IRoomWidgetHandlerManager, RoomWidgetAvatarInfoHandler, RoomWidgetChatHandler, RoomWidgetChatInputHandler, RoomWidgetHandlerManager, RoomWidgetInfostandHandler } from './handlers'; +import { FurniChooserWidgetHandler } from './handlers/FurniChooserWidgetHandler'; import { FurnitureContextMenuWidgetHandler } from './handlers/FurnitureContextMenuWidgetHandler'; import { FurnitureCustomStackHeightWidgetHandler } from './handlers/FurnitureCustomStackHeightWidgetHandler'; import { RoomWidgetRoomToolsHandler } from './handlers/RoomWidgetRoomToolsHandler'; +import { UserChooserWidgetHandler } from './handlers/UserChooserWidgetHandler'; import { RoomColorView } from './RoomColorView'; import { RoomViewProps } from './RoomView.types'; import { RoomWidgetsView } from './widgets/RoomWidgetsView'; @@ -44,6 +46,8 @@ export const RoomView: FC = props => widgetHandlerManager.registerHandler(new RoomWidgetChatHandler()); widgetHandlerManager.registerHandler(new FurnitureContextMenuWidgetHandler()); widgetHandlerManager.registerHandler(new FurnitureCustomStackHeightWidgetHandler()); + widgetHandlerManager.registerHandler(new FurniChooserWidgetHandler()); + widgetHandlerManager.registerHandler(new UserChooserWidgetHandler()); setWidgetHandler(widgetHandlerManager); diff --git a/src/views/room/handlers/FurniChooserWidgetHandler.ts b/src/views/room/handlers/FurniChooserWidgetHandler.ts new file mode 100644 index 00000000..2e2c7630 --- /dev/null +++ b/src/views/room/handlers/FurniChooserWidgetHandler.ts @@ -0,0 +1,108 @@ +import { NitroEvent, RoomObjectCategory, RoomObjectVariable, RoomWidgetEnum } from '@nitrots/nitro-renderer'; +import { GetNitroInstance, GetRoomEngine, GetSessionDataManager } from '../../../api'; +import { RoomObjectItem } from '../../../events/room-widgets/choosers/RoomObjectItem'; +import { RoomWidgetChooserContentEvent } from '../../../events/room-widgets/choosers/RoomWidgetChooserContentEvent'; +import { dispatchUiEvent } from '../../../hooks'; +import { RoomWidgetUpdateEvent } from '../events'; +import { RoomWidgetMessage, RoomWidgetRequestWidgetMessage, RoomWidgetRoomObjectMessage } from '../messages'; +import { dynamicSort } from '../widgets/choosers/utils/sorting'; +import { RoomWidgetHandler } from './RoomWidgetHandler'; + +export class FurniChooserWidgetHandler extends RoomWidgetHandler +{ + public processEvent(event: NitroEvent): void + { + } + + public processWidgetMessage(message: RoomWidgetMessage): RoomWidgetUpdateEvent + { + if(!message) return null; + + switch(message.type) + { + case RoomWidgetRequestWidgetMessage.FURNI_CHOOSER: + this.processFurniChooser(); + break; + case RoomWidgetRoomObjectMessage.SELECT_OBJECT: + this.selectFurni(message); + break; + } + + return null; + } + + private selectFurni(message: RoomWidgetMessage): void + { + const event = message as RoomWidgetRoomObjectMessage; + + if(event == null) return; + + if(event.category === RoomObjectCategory.WALL || event.category === RoomObjectCategory.FLOOR) + { + GetRoomEngine().selectRoomObject(this.container.roomSession.roomId, event.id, event.category); + } + } + + private processFurniChooser(): void + { + + if(this.container == null || this.container.roomSession == null || GetRoomEngine() == null || this.container.roomSession.userDataManager == null) return; + + const roomId = this.container.roomSession.roomId; + const furniInRoom : RoomObjectItem[] = []; + + furniInRoom.push(...GetRoomEngine().getRoomObjects(roomId, RoomObjectCategory.WALL).map(roomObject => { + const type = roomObject.type; + let name = null; + if(type.startsWith('poster')) + { + const posterNumber = Number.parseInt(type.replace('poster', '')); + name = GetNitroInstance().localization.getValue('poster_' + posterNumber + '_name'); + } + else + { + const furniTypeId = Number.parseInt(roomObject.model.getValue(RoomObjectVariable.FURNITURE_TYPE_ID)); + const wallItemData = GetSessionDataManager().getWallItemData(furniTypeId); + if(wallItemData != null && wallItemData.name.length > 0) + { + name = wallItemData.name; + } + else + { + name = type; + } + } + return new RoomObjectItem(roomObject.id, RoomObjectCategory.WALL, name) + })); + + furniInRoom.push(...GetRoomEngine().getRoomObjects(roomId, RoomObjectCategory.FLOOR).map(roomObject => { + const furniTypeId = Number.parseInt(roomObject.model.getValue(RoomObjectVariable.FURNITURE_TYPE_ID)); + const floorItemData = GetSessionDataManager().getFloorItemData(furniTypeId); + const name = floorItemData != null ? floorItemData.name : roomObject.type; + + return new RoomObjectItem(roomObject.id, RoomObjectCategory.FLOOR, name); + })); + + furniInRoom.sort(dynamicSort('name')); + + dispatchUiEvent(new RoomWidgetChooserContentEvent(RoomWidgetChooserContentEvent.FURNI_CHOOSER_CONTENT, furniInRoom, false)); + } + + public get type(): string + { + return RoomWidgetEnum.FURNI_CHOOSER; + } + + public get eventTypes(): string[] + { + return []; + } + + public get messageTypes(): string[] + { + return [ + RoomWidgetRequestWidgetMessage.FURNI_CHOOSER, + RoomWidgetRoomObjectMessage.SELECT_OBJECT + ]; + } +} diff --git a/src/views/room/handlers/UserChooserWidgetHandler.ts b/src/views/room/handlers/UserChooserWidgetHandler.ts new file mode 100644 index 00000000..379b479d --- /dev/null +++ b/src/views/room/handlers/UserChooserWidgetHandler.ts @@ -0,0 +1,84 @@ +import { NitroEvent, RoomObjectCategory, RoomWidgetEnum } from '@nitrots/nitro-renderer'; +import { RoomWidgetHandler } from '.'; +import { GetRoomEngine } from '../../../api'; +import { RoomObjectItem } from '../../../events/room-widgets/choosers/RoomObjectItem'; +import { RoomWidgetChooserContentEvent } from '../../../events/room-widgets/choosers/RoomWidgetChooserContentEvent'; +import { dispatchUiEvent } from '../../../hooks'; +import { RoomWidgetUpdateEvent } from '../events'; +import { RoomWidgetMessage, RoomWidgetRequestWidgetMessage, RoomWidgetRoomObjectMessage } from '../messages'; +import { dynamicSort } from '../widgets/choosers/utils/sorting'; + +export class UserChooserWidgetHandler extends RoomWidgetHandler +{ + public processEvent(event: NitroEvent): void + { + } + + public processWidgetMessage(message: RoomWidgetMessage): RoomWidgetUpdateEvent + { + if(!message) return null; + + switch(message.type) + { + case RoomWidgetRequestWidgetMessage.USER_CHOOSER: + this.processUserChooser(); + break; + case RoomWidgetRoomObjectMessage.SELECT_OBJECT: + this.selectUnit(message); + break; + } + + return null; + } + + private processUserChooser(): void + { + + if(this.container == null || this.container.roomSession == null || GetRoomEngine() == null || this.container.roomSession.userDataManager == null) return; + + const roomId = this.container.roomSession.roomId; + const categoryId = RoomObjectCategory.UNIT; + const units = []; + + units.push(...GetRoomEngine().getRoomObjects(roomId, categoryId).map(roomObject => { + const unitData = this.container.roomSession.userDataManager.getUserDataByIndex(roomObject.id); + + if(!unitData) return null; + + return new RoomObjectItem(unitData.roomIndex, categoryId, unitData.name); + })); + + units.sort(dynamicSort('name')); + dispatchUiEvent(new RoomWidgetChooserContentEvent(RoomWidgetChooserContentEvent.USER_CHOOSER_CONTENT, units)); + } + + private selectUnit(k: RoomWidgetMessage): void + { + const event = k as RoomWidgetRoomObjectMessage; + + if(event == null) return; + + if(event.category === RoomObjectCategory.UNIT) + { + GetRoomEngine().selectRoomObject(this.container.roomSession.roomId, event.id, event.category); + } + } + + public get type(): string + { + return RoomWidgetEnum.USER_CHOOSER; + } + + public get eventTypes(): string[] + { + return []; + } + + public get messageTypes(): string[] + { + return [ + RoomWidgetRequestWidgetMessage.USER_CHOOSER, + RoomWidgetRoomObjectMessage.SELECT_OBJECT + ]; + } +} diff --git a/src/views/room/widgets/RoomWidgets.scss b/src/views/room/widgets/RoomWidgets.scss index ed284cd9..e7c33301 100644 --- a/src/views/room/widgets/RoomWidgets.scss +++ b/src/views/room/widgets/RoomWidgets.scss @@ -7,3 +7,4 @@ @import './infostand/InfoStandWidgetView'; @import './object-location/ObjectLocationView'; @import './room-tools/RoomToolsWidgetView'; +@import './choosers/ChooserWidgetView'; diff --git a/src/views/room/widgets/RoomWidgetsView.tsx b/src/views/room/widgets/RoomWidgetsView.tsx index 2ed8ba9d..66c85b43 100644 --- a/src/views/room/widgets/RoomWidgetsView.tsx +++ b/src/views/room/widgets/RoomWidgetsView.tsx @@ -9,6 +9,8 @@ import { AvatarInfoWidgetView } from './avatar-info/AvatarInfoWidgetView'; import { CameraWidgetView } from './camera/CameraWidgetView'; import { ChatInputView } from './chat-input/ChatInputView'; import { ChatWidgetView } from './chat/ChatWidgetView'; +import { FurniChooserWidgetView } from './choosers/FurniChooserWidgetView'; +import { UserChooserWidgetView } from './choosers/UserChooserWidgetView'; import { FurnitureWidgetsView } from './furniture/FurnitureWidgetsView'; import { InfoStandWidgetView } from './infostand/InfoStandWidgetView'; import { RoomThumbnailWidgetView } from './room-thumbnail/RoomThumbnailWidgetView'; @@ -246,6 +248,8 @@ export const RoomWidgetsView: FC = props => + + ); } diff --git a/src/views/room/widgets/choosers/ChooserWidgetView.scss b/src/views/room/widgets/choosers/ChooserWidgetView.scss new file mode 100644 index 00000000..bca2d777 --- /dev/null +++ b/src/views/room/widgets/choosers/ChooserWidgetView.scss @@ -0,0 +1,9 @@ +.chooser-widget { + .selected-item { + background-color: cadetblue; + } + .list-item { + color: black; + overflow: hidden; + } +} diff --git a/src/views/room/widgets/choosers/FurniChooserWidgetView.tsx b/src/views/room/widgets/choosers/FurniChooserWidgetView.tsx new file mode 100644 index 00000000..7bc25c54 --- /dev/null +++ b/src/views/room/widgets/choosers/FurniChooserWidgetView.tsx @@ -0,0 +1,118 @@ +import { FC, useCallback, useEffect, useState } from 'react'; +import List from 'react-virtualized/dist/commonjs/List'; +import { RoomObjectItem } from '../../../../events/room-widgets/choosers/RoomObjectItem'; +import { RoomWidgetChooserContentEvent } from '../../../../events/room-widgets/choosers/RoomWidgetChooserContentEvent'; +import { CreateEventDispatcherHook, useUiEvent } from '../../../../hooks'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout'; +import { LocalizeText } from '../../../../utils'; +import { useRoomContext } from '../../context/RoomContext'; +import { RoomWidgetRoomObjectUpdateEvent } from '../../events'; +import { RoomWidgetRequestWidgetMessage, RoomWidgetRoomObjectMessage } from '../../messages'; + +export const FurniChooserWidgetView: FC = props => +{ + const [isVisible, setIsVisible] = useState(false); + const [items, setItems] = useState(null); + const [filteredItems, setFilteredItems] = useState(null); + const [selectedItem, setSelectedItem] = useState(null); + const [refreshTimeout, setRefreshTimeout] = useState>(null); + const [searchValue, setSearchValue] = useState(''); + const { eventDispatcher = null, widgetHandler = null } = useRoomContext(); + + const onFurniChooserContent = useCallback((event: RoomWidgetChooserContentEvent) => + { + setItems(event.items); + setIsVisible(true); + }, []); + + const onRoomWidgetRoomObjectUpdateEvent = useCallback((event: RoomWidgetRoomObjectUpdateEvent) => + { + if (!event || !isVisible) return; + + if (refreshTimeout) clearTimeout(refreshTimeout); + + setRefreshTimeout(setTimeout(() => + { + widgetHandler.processWidgetMessage(new RoomWidgetRequestWidgetMessage(RoomWidgetRequestWidgetMessage.FURNI_CHOOSER)); + }, 100)); + + }, [isVisible, refreshTimeout, widgetHandler]); + + useUiEvent(RoomWidgetChooserContentEvent.FURNI_CHOOSER_CONTENT, onFurniChooserContent); + CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_ADDED, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); + CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); + + useEffect(() => + { + if(!items) return; + + let filteredGroupItems = [ ...items ]; + + if(searchValue && searchValue.length) + { + const comparison = searchValue.toLocaleLowerCase(); + + filteredGroupItems = items.filter(item => + { + if(comparison && comparison.length) + { + if(item.name.toLocaleLowerCase().includes(comparison)) return item; + } + + return null; + }); + } + + setFilteredItems(filteredGroupItems); + }, [ items, searchValue, setFilteredItems ]); + + + const onClose = useCallback(() => + { + setIsVisible(false); + setItems(null); + }, []); + + const onClickItem = useCallback((item: RoomObjectItem) => + { + setSelectedItem(item); + widgetHandler.processWidgetMessage(new RoomWidgetRoomObjectMessage(RoomWidgetRoomObjectMessage.SELECT_OBJECT, item.id, item.category)); + }, [setSelectedItem, widgetHandler]); + + const rowRenderer = function ({ + key, // Unique key within array of rows + index, // Index of row within collection + isScrolling, // The List is currently being scrolled + isVisible, // This row is visible within the List (eg it is not an overscanned row) + style, // Style object to be applied to row (to position it) + }) + { + return ( +
onClickItem(filteredItems[index])} className={(selectedItem === filteredItems[index] ? 'selected-item ' : '') + 'list-item'}> + {filteredItems[index].name} - {filteredItems[index].id} +
+ ); + } + + if (!isVisible) return null; + + return ( +
+ + + +
+
+ setSearchValue(event.target.value)} /> +
+
+ +
+
+
+ ) +} diff --git a/src/views/room/widgets/choosers/UserChooserWidgetView.tsx b/src/views/room/widgets/choosers/UserChooserWidgetView.tsx new file mode 100644 index 00000000..86ebaafd --- /dev/null +++ b/src/views/room/widgets/choosers/UserChooserWidgetView.tsx @@ -0,0 +1,117 @@ +import { FC, useCallback, useEffect, useState } from 'react'; +import List from 'react-virtualized/dist/commonjs/List'; +import { RoomObjectItem } from '../../../../events/room-widgets/choosers/RoomObjectItem'; +import { RoomWidgetChooserContentEvent } from '../../../../events/room-widgets/choosers/RoomWidgetChooserContentEvent'; +import { CreateEventDispatcherHook, useUiEvent } from '../../../../hooks'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout'; +import { LocalizeText } from '../../../../utils'; +import { useRoomContext } from '../../context/RoomContext'; +import { RoomWidgetRoomObjectUpdateEvent } from '../../events'; +import { RoomWidgetRequestWidgetMessage, RoomWidgetRoomObjectMessage } from '../../messages'; + +export const UserChooserWidgetView : FC = props => +{ + const [isVisible, setIsVisible] = useState(false); + const [items, setItems] = useState(null); + const [filteredItems, setFilteredItems] = useState(null); + const [selectedItem, setSelectedItem] = useState(null); + const [refreshTimeout, setRefreshTimeout] = useState>(null); + const [searchValue, setSearchValue] = useState(''); + const { eventDispatcher = null, widgetHandler = null } = useRoomContext(); + + const onUserChooserContent = useCallback((event: RoomWidgetChooserContentEvent) => + { + setItems(event.items); + setIsVisible(true); + }, []); + + const onRoomWidgetRoomObjectUpdateEvent = useCallback((event: RoomWidgetRoomObjectUpdateEvent) => + { + if (!event || !isVisible) return; + + if (refreshTimeout) clearTimeout(refreshTimeout); + + setRefreshTimeout(setTimeout(() => + { + widgetHandler.processWidgetMessage(new RoomWidgetRequestWidgetMessage(RoomWidgetRequestWidgetMessage.USER_CHOOSER)); + }, 100)); + + }, [isVisible, refreshTimeout, widgetHandler]); + + useUiEvent(RoomWidgetChooserContentEvent.USER_CHOOSER_CONTENT, onUserChooserContent); + CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.USER_REMOVED, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); + CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.USER_ADDED, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); + + useEffect(() => + { + if(!items) return; + + let filteredGroupItems = [ ...items ]; + + if(searchValue && searchValue.length) + { + const comparison = searchValue.toLocaleLowerCase(); + + filteredGroupItems = items.filter(item => + { + if(comparison && comparison.length) + { + if(item.name.toLocaleLowerCase().includes(comparison)) return item; + } + + return null; + }); + } + + setFilteredItems(filteredGroupItems); + }, [ items, searchValue, setFilteredItems ]); + + const onClose = useCallback(() => + { + setIsVisible(false); + setItems(null); + }, []); + + const onClickItem = useCallback((item: RoomObjectItem) => + { + setSelectedItem(item); + widgetHandler.processWidgetMessage(new RoomWidgetRoomObjectMessage(RoomWidgetRoomObjectMessage.SELECT_OBJECT, item.id, item.category)); + }, [setSelectedItem, widgetHandler]); + + const rowRenderer = function ({ + key, // Unique key within array of rows + index, // Index of row within collection + isScrolling, // The List is currently being scrolled + isVisible, // This row is visible within the List (eg it is not an overscanned row) + style, // Style object to be applied to row (to position it) + }) + { + return ( +
onClickItem(filteredItems[index])} className={(selectedItem === filteredItems[index] ? 'selected-item ' : '') + 'list-item'}> + {filteredItems[index].name} +
+ ); + } + + if(!isVisible) return null; + + return ( +
+ + + +
+
+ setSearchValue(event.target.value)} /> +
+
+ +
+
+
+ ); +} diff --git a/src/views/room/widgets/choosers/utils/sorting.ts b/src/views/room/widgets/choosers/utils/sorting.ts new file mode 100644 index 00000000..eee12529 --- /dev/null +++ b/src/views/room/widgets/choosers/utils/sorting.ts @@ -0,0 +1,18 @@ +export function dynamicSort(property) +{ + // Source: https://stackoverflow.com/questions/1129216/sort-array-of-objects-by-string-property-value + let sortOrder = 1; + if(property[0] === '-') + { + sortOrder = -1; + property = property.substr(1); + } + return function (a,b) + { + /* next line works with strings and numbers, + * and you may want to customize it to your needs + */ + const result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0; + return result * sortOrder; + }; +}