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 diff --git a/public/ui-config.json b/public/ui-config.json index 58dfdcc7..1104aa32 100644 --- a/public/ui-config.json +++ b/public/ui-config.json @@ -4,6 +4,7 @@ "camera.url": "https://nitro.nitrots.co/camera", "thumbnails.url": "https://nitro.nitrots.co/camera/thumbnail/%thumbnail%.png", "url.prefix": "http://localhost:3000", + "floorplan.tile.url": "${asset.url}/floorplan-editor/tiles.json", "chat.viewer.height.percentage": 0.40, "widget.dimmer.colorwheel": false, "hotelview": { diff --git a/src/App.scss b/src/App.scss index 2a93b1ff..3ab4f7fd 100644 --- a/src/App.scss +++ b/src/App.scss @@ -17,13 +17,13 @@ $grid-active-border-color: $white; $toolbar-height: 55px; -$achievement-width: 650px; -$achievement-height: 400px; +$achievement-width: 350px; +$achievement-height: 370px; $avatar-editor-width: 620px; $avatar-editor-height: 374px; -$catalog-width: 620px; +$catalog-width: 630px; $catalog-height: 400px; $inventory-width: 528px; @@ -32,6 +32,18 @@ $inventory-height: 320px; $navigator-width: 400px; $navigator-height: 420px; +$chat-input-style-selector-widget-width: 200px; +$chat-input-style-selector-widget-height: 200px; + +$user-profile-width: 560px; +$user-profile-height: 500px; + +$nitro-widget-custom-stack-height-width: 275px; +$nitro-widget-custom-stack-height-height: 220px; + +$nitro-widget-exchange-credit-width: 375px; +$nitro-widget-exchange-credit-height: 150px; + .nitro-app { width: 100%; height: 100%; diff --git a/src/App.tsx b/src/App.tsx index 9d050bd9..b6106abb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,9 +11,9 @@ import { MainView } from './views/main/MainView'; export const App: FC<{}> = props => { - const [ isReady, setIsReady ] = useState(false); - const [ isError, setIsError ] = useState(false); - const [ message, setMessage ] = useState('Getting Ready'); + const [ isReady, setIsReady ] = useState(false); + const [ isError, setIsError ] = useState(false); + const [ message, setMessage ] = useState('Getting Ready'); //@ts-ignore if(!NitroConfig) throw new Error('NitroConfig is not defined!'); @@ -128,7 +128,7 @@ export const App: FC<{}> = props =>
{ (!isReady || isError) && } - +
diff --git a/src/api/nitro/room/widgets/events/RoomWidgetUpdateDimmerEvent.ts b/src/api/nitro/room/widgets/events/RoomWidgetUpdateDimmerEvent.ts index 82cdb0c6..30556c71 100644 --- a/src/api/nitro/room/widgets/events/RoomWidgetUpdateDimmerEvent.ts +++ b/src/api/nitro/room/widgets/events/RoomWidgetUpdateDimmerEvent.ts @@ -36,7 +36,7 @@ export class RoomWidgetUpdateDimmerEvent extends RoomWidgetUpdateEvent this._selectedPresetId = k; } - public setPresetValues(id: number, type: number, color: number, brightness: number):void + public setPresetValues(id: number, type: number, color: number, brightness: number): void { const preset = new RoomDimmerPreset(id, type, color, brightness); diff --git a/src/api/nitro/room/widgets/events/RoomWidgetUpdateMannequinEvent.ts b/src/api/nitro/room/widgets/events/RoomWidgetUpdateMannequinEvent.ts new file mode 100644 index 00000000..1894a9fb --- /dev/null +++ b/src/api/nitro/room/widgets/events/RoomWidgetUpdateMannequinEvent.ts @@ -0,0 +1,41 @@ +import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent'; + +export class RoomWidgetUpdateMannequinEvent extends RoomWidgetUpdateEvent +{ + public static MANNEQUIN_UPDATE: string = 'RWUME_MANNEQUIN_UPDATE'; + + private _objectId: number; + private _figure: string; + private _gender: string; + private _name: string; + + constructor(type: string, objectId: number, figure: string, gender: string, name: string) + { + super(type); + + this._objectId = objectId; + this._figure = figure; + this._gender = gender; + this._name = name; + } + + public get objectId(): number + { + return this._objectId; + } + + public get figure(): string + { + return this._figure; + } + + public get gender(): string + { + return this._gender; + } + + public get name(): string + { + return this._name; + } +} diff --git a/src/api/nitro/room/widgets/events/RoomWidgetRoomEngineUpdateEvent.ts b/src/api/nitro/room/widgets/events/RoomWidgetUpdateRoomEngineEvent.ts similarity index 62% rename from src/api/nitro/room/widgets/events/RoomWidgetRoomEngineUpdateEvent.ts rename to src/api/nitro/room/widgets/events/RoomWidgetUpdateRoomEngineEvent.ts index c7e433a2..0e72f2c3 100644 --- a/src/api/nitro/room/widgets/events/RoomWidgetRoomEngineUpdateEvent.ts +++ b/src/api/nitro/room/widgets/events/RoomWidgetUpdateRoomEngineEvent.ts @@ -1,9 +1,9 @@ import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent'; -export class RoomWidgetRoomEngineUpdateEvent extends RoomWidgetUpdateEvent +export class RoomWidgetUpdateRoomEngineEvent extends RoomWidgetUpdateEvent { - public static GAME_MODE: string = 'RWREUE_GAME_MODE'; - public static NORMAL_MODE: string = 'RWREUE_NORMAL_MODE'; + public static GAME_MODE: string = 'RWUREE_GAME_MODE'; + public static NORMAL_MODE: string = 'RWUREE_NORMAL_MODE'; private _roomId: number = 0; diff --git a/src/api/nitro/room/widgets/events/RoomWidgetRoomObjectUpdateEvent.ts b/src/api/nitro/room/widgets/events/RoomWidgetUpdateRoomObjectEvent.ts similarity index 54% rename from src/api/nitro/room/widgets/events/RoomWidgetRoomObjectUpdateEvent.ts rename to src/api/nitro/room/widgets/events/RoomWidgetUpdateRoomObjectEvent.ts index 8c63ab4c..cbc1d9c9 100644 --- a/src/api/nitro/room/widgets/events/RoomWidgetRoomObjectUpdateEvent.ts +++ b/src/api/nitro/room/widgets/events/RoomWidgetUpdateRoomObjectEvent.ts @@ -1,16 +1,16 @@ import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent'; -export class RoomWidgetRoomObjectUpdateEvent extends RoomWidgetUpdateEvent +export class RoomWidgetUpdateRoomObjectEvent extends RoomWidgetUpdateEvent { - public static OBJECT_SELECTED: string = 'RWROUE_OBJECT_SELECTED'; - public static OBJECT_DESELECTED: string = 'RWROUE_OBJECT_DESELECTED'; - public static USER_REMOVED: string = 'RWROUE_USER_REMOVED'; - public static FURNI_REMOVED: string = 'RWROUE_FURNI_REMOVED'; - public static FURNI_ADDED: string = 'RWROUE_FURNI_ADDED'; - public static USER_ADDED: string = 'RWROUE_USER_ADDED'; - public static OBJECT_ROLL_OVER: string = 'RWROUE_OBJECT_ROLL_OVER'; - public static OBJECT_ROLL_OUT: string = 'RWROUE_OBJECT_ROLL_OUT'; - public static OBJECT_REQUEST_MANIPULATION: string = 'RWROUE_OBJECT_REQUEST_MANIPULATION'; + public static OBJECT_SELECTED: string = 'RWUROE_OBJECT_SELECTED'; + public static OBJECT_DESELECTED: string = 'RWUROE_OBJECT_DESELECTED'; + public static USER_REMOVED: string = 'RWUROE_USER_REMOVED'; + public static FURNI_REMOVED: string = 'RWUROE_FURNI_REMOVED'; + public static FURNI_ADDED: string = 'RWUROE_FURNI_ADDED'; + public static USER_ADDED: string = 'RWUROE_USER_ADDED'; + public static OBJECT_ROLL_OVER: string = 'RWUROE_OBJECT_ROLL_OVER'; + public static OBJECT_ROLL_OUT: string = 'RWUROE_OBJECT_ROLL_OUT'; + public static OBJECT_REQUEST_MANIPULATION: string = 'RWUROE_OBJECT_REQUEST_MANIPULATION'; private _id: number; private _category: number; diff --git a/src/api/nitro/room/widgets/events/RoomWidgetUpdateTrophyEvent.ts b/src/api/nitro/room/widgets/events/RoomWidgetUpdateTrophyEvent.ts new file mode 100644 index 00000000..b13b2b66 --- /dev/null +++ b/src/api/nitro/room/widgets/events/RoomWidgetUpdateTrophyEvent.ts @@ -0,0 +1,48 @@ +import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent'; + +export class RoomWidgetUpdateTrophyEvent extends RoomWidgetUpdateEvent +{ + public static TROPHY_UPDATE: string = 'RWUTE_TROPHY_UPDATE'; + + private _color: number; + private _name: string; + private _date: string; + private _message: string; + private _extra: number; + + constructor(type: string, color: number, name: string, date: string, message: string, extra: number) + { + super(type); + + this._color = color; + this._name = name; + this._date = date; + this._message = message; + this._extra = extra; + } + + public get color(): number + { + return this._color; + } + + public get name(): string + { + return this._name; + } + + public get date(): string + { + return this._date; + } + + public get message(): string + { + return this._message; + } + + public get extra(): number + { + return this._extra; + } +} diff --git a/src/api/nitro/room/widgets/events/index.ts b/src/api/nitro/room/widgets/events/index.ts index 775d210e..f4415bb9 100644 --- a/src/api/nitro/room/widgets/events/index.ts +++ b/src/api/nitro/room/widgets/events/index.ts @@ -6,8 +6,6 @@ export * from './RoomWidgetChooserContentEvent'; export * from './RoomWidgetDoorbellEvent'; export * from './RoomWidgetFloodControlEvent'; export * from './RoomWidgetObjectNameEvent'; -export * from './RoomWidgetRoomEngineUpdateEvent'; -export * from './RoomWidgetRoomObjectUpdateEvent'; export * from './RoomWidgetUpdateBackgroundColorPreviewEvent'; export * from './RoomWidgetUpdateChatEvent'; export * from './RoomWidgetUpdateChatInputContentEvent'; @@ -24,10 +22,14 @@ export * from './RoomWidgetUpdateInfostandFurniEvent'; export * from './RoomWidgetUpdateInfostandPetEvent'; export * from './RoomWidgetUpdateInfostandRentableBotEvent'; export * from './RoomWidgetUpdateInfostandUserEvent'; +export * from './RoomWidgetUpdateMannequinEvent'; export * from './RoomWidgetUpdatePresentDataEvent'; export * from './RoomWidgetUpdateRentableBotChatEvent'; +export * from './RoomWidgetUpdateRoomEngineEvent'; +export * from './RoomWidgetUpdateRoomObjectEvent'; export * from './RoomWidgetUpdateRoomViewEvent'; export * from './RoomWidgetUpdateSongEvent'; +export * from './RoomWidgetUpdateTrophyEvent'; export * from './RoomWidgetUpdateUserDataEvent'; export * from './RoomWidgetUseProductBubbleEvent'; export * from './UseProductItem'; diff --git a/src/api/nitro/room/widgets/handlers/FurnitureMannequinWidgetHandler.ts b/src/api/nitro/room/widgets/handlers/FurnitureMannequinWidgetHandler.ts new file mode 100644 index 00000000..a2fc46bd --- /dev/null +++ b/src/api/nitro/room/widgets/handlers/FurnitureMannequinWidgetHandler.ts @@ -0,0 +1,54 @@ +import { NitroEvent, RoomEngineTriggerWidgetEvent, RoomObjectVariable, RoomWidgetEnum } from '@nitrots/nitro-renderer'; +import { RoomWidgetUpdateMannequinEvent } from '..'; +import { GetRoomEngine } from '../../GetRoomEngine'; +import { RoomWidgetUpdateEvent } from '../events/RoomWidgetUpdateEvent'; +import { RoomWidgetMessage } from '../messages/RoomWidgetMessage'; +import { RoomWidgetHandler } from './RoomWidgetHandler'; + +export class FurnitureMannequinWidgetHandler extends RoomWidgetHandler +{ + public processEvent(event: NitroEvent): void + { + switch(event.type) + { + case RoomEngineTriggerWidgetEvent.REQUEST_MANNEQUIN: { + const widgetEvent = (event as RoomEngineTriggerWidgetEvent); + const roomObject = GetRoomEngine().getRoomObject(widgetEvent.roomId, widgetEvent.objectId, widgetEvent.category); + + if(!roomObject) return; + + const model = roomObject.model; + const figure = model.getValue(RoomObjectVariable.FURNITURE_MANNEQUIN_FIGURE); + const gender = model.getValue(RoomObjectVariable.FURNITURE_MANNEQUIN_GENDER); + const name = model.getValue(RoomObjectVariable.FURNITURE_MANNEQUIN_NAME); + + this.container.eventDispatcher.dispatchEvent(new RoomWidgetUpdateMannequinEvent(RoomWidgetUpdateMannequinEvent.MANNEQUIN_UPDATE, roomObject.id, figure, gender, name)); + return; + } + } + } + + public processWidgetMessage(message: RoomWidgetMessage): RoomWidgetUpdateEvent + { + switch(message.type) + { + } + + return null; + } + + public get type(): string + { + return RoomWidgetEnum.MANNEQUIN; + } + + public get eventTypes(): string[] + { + return [ RoomEngineTriggerWidgetEvent.REQUEST_MANNEQUIN ]; + } + + public get messageTypes(): string[] + { + return []; + } +} diff --git a/src/api/nitro/room/widgets/handlers/RoomWidgetChatInputHandler.ts b/src/api/nitro/room/widgets/handlers/RoomWidgetChatInputHandler.ts index 81f5411d..9e6f234b 100644 --- a/src/api/nitro/room/widgets/handlers/RoomWidgetChatInputHandler.ts +++ b/src/api/nitro/room/widgets/handlers/RoomWidgetChatInputHandler.ts @@ -1,6 +1,8 @@ import { AvatarExpressionEnum, HabboClubLevelEnum, NitroEvent, RoomControllerLevel, RoomSessionChatEvent, RoomSettingsComposer, RoomWidgetEnum, RoomZoomEvent, TextureUtils } from '@nitrots/nitro-renderer'; import { GetConfiguration, GetNitroInstance } from '../../..'; import { GetRoomEngine, GetSessionDataManager } from '../../../..'; +import { FloorplanEditorEvent } from '../../../../../events/floorplan-editor/FloorplanEditorEvent'; +import { dispatchUiEvent } from '../../../../../hooks'; import { SendMessageHook } from '../../../../../hooks/messages'; import { RoomWidgetFloodControlEvent, RoomWidgetUpdateEvent } from '../events'; import { RoomWidgetChatMessage, RoomWidgetChatSelectAvatarMessage, RoomWidgetChatTypingMessage, RoomWidgetMessage, RoomWidgetRequestWidgetMessage } from '../messages'; @@ -143,7 +145,8 @@ export class RoomWidgetChatInputHandler extends RoomWidgetHandler case ':bcfloor': if(this.container.roomSession.controllerLevel >= RoomControllerLevel.ROOM_OWNER) { - this.container.processWidgetMessage(new RoomWidgetRequestWidgetMessage(RoomWidgetRequestWidgetMessage.FLOOR_EDITOR)); + //this.container.processWidgetMessage(new RoomWidgetRequestWidgetMessage(RoomWidgetRequestWidgetMessage.FLOOR_EDITOR)); + dispatchUiEvent(new FloorplanEditorEvent(FloorplanEditorEvent.SHOW_FLOORPLAN_EDITOR)); } return null; diff --git a/src/api/nitro/room/widgets/handlers/RoomWidgetInfostandHandler.ts b/src/api/nitro/room/widgets/handlers/RoomWidgetInfostandHandler.ts index e86aba4e..be1d8608 100644 --- a/src/api/nitro/room/widgets/handlers/RoomWidgetInfostandHandler.ts +++ b/src/api/nitro/room/widgets/handlers/RoomWidgetInfostandHandler.ts @@ -2,6 +2,7 @@ import { IFurnitureData, NitroEvent, ObjectDataFactory, PetFigureData, PetRespec import { GetNitroInstance, GetRoomEngine, GetSessionDataManager, IsOwnerOfFurniture } from '../../../..'; import { InventoryTradeRequestEvent, WiredSelectObjectEvent } from '../../../../../events'; import { FriendsSendFriendRequestEvent } from '../../../../../events/friends/FriendsSendFriendRequestEvent'; +import { HelpReportUserEvent } from '../../../../../events/help/HelpReportUserEvent'; import { dispatchUiEvent } from '../../../../../hooks/events'; import { SendMessageHook } from '../../../../../hooks/messages'; import { PetSupplementEnum } from '../../../../../views/room/widgets/avatar-info/common/PetSupplementEnum'; @@ -164,6 +165,7 @@ export class RoomWidgetInfostandHandler extends RoomWidgetHandler case RoomWidgetUserActionMessage.REPORT: return; case RoomWidgetUserActionMessage.REPORT_CFH_OTHER: + dispatchUiEvent(new HelpReportUserEvent(userId)); return; case RoomWidgetUserActionMessage.AMBASSADOR_ALERT_USER: this.container.roomSession.sendAmbassadorAlertMessage(userId); diff --git a/src/api/nitro/room/widgets/handlers/index.ts b/src/api/nitro/room/widgets/handlers/index.ts index f2a12569..51f10e1a 100644 --- a/src/api/nitro/room/widgets/handlers/index.ts +++ b/src/api/nitro/room/widgets/handlers/index.ts @@ -5,6 +5,7 @@ export * from './FurnitureCreditWidgetHandler'; export * from './FurnitureCustomStackHeightWidgetHandler'; export * from './FurnitureDimmerWidgetHandler'; export * from './FurnitureExternalImageWidgetHandler'; +export * from './FurnitureMannequinWidgetHandler'; export * from './FurniturePresentWidgetHandler'; export * from './IRoomWidgetHandler'; export * from './IRoomWidgetHandlerManager'; diff --git a/src/api/nitro/session/GetFurnitureData.ts b/src/api/nitro/session/GetFurnitureData.ts new file mode 100644 index 00000000..485f4b73 --- /dev/null +++ b/src/api/nitro/session/GetFurnitureData.ts @@ -0,0 +1,20 @@ +import { IFurnitureData } from '@nitrots/nitro-renderer'; +import { GetSessionDataManager } from '.'; +import { ProductTypeEnum } from '../../../views/catalog/common/ProductTypeEnum'; + +export function GetFurnitureData(furniClassId: number, productType: string): IFurnitureData +{ + let furniData: IFurnitureData = null; + + switch(productType.toUpperCase()) + { + case ProductTypeEnum.FLOOR: + furniData = GetSessionDataManager().getFloorItemData(furniClassId); + break; + case ProductTypeEnum.WALL: + furniData = GetSessionDataManager().getWallItemData(furniClassId); + break; + } + + return furniData; +} diff --git a/src/api/nitro/session/index.ts b/src/api/nitro/session/index.ts index 5409266d..3a53eb05 100644 --- a/src/api/nitro/session/index.ts +++ b/src/api/nitro/session/index.ts @@ -3,6 +3,7 @@ export * from './CreateRoomSession'; export * from './GetCanStandUp'; export * from './GetCanUseExpression'; export * from './GetClubMemberLevel'; +export * from './GetFurnitureData'; export * from './GetFurnitureDataForProductOffer'; export * from './GetFurnitureDataForRoomObject'; export * from './GetOwnPosture'; diff --git a/src/assets/images/achievements/back-arrow.png b/src/assets/images/achievements/back-arrow.png new file mode 100644 index 00000000..e795c0ee Binary files /dev/null and b/src/assets/images/achievements/back-arrow.png differ diff --git a/src/assets/images/floorplaneditor/door-direction-0.png b/src/assets/images/floorplaneditor/door-direction-0.png new file mode 100644 index 00000000..8c272a0a Binary files /dev/null and b/src/assets/images/floorplaneditor/door-direction-0.png differ diff --git a/src/assets/images/floorplaneditor/door-direction-1.png b/src/assets/images/floorplaneditor/door-direction-1.png new file mode 100644 index 00000000..52e488f6 Binary files /dev/null and b/src/assets/images/floorplaneditor/door-direction-1.png differ diff --git a/src/assets/images/floorplaneditor/door-direction-2.png b/src/assets/images/floorplaneditor/door-direction-2.png new file mode 100644 index 00000000..da1a1cb5 Binary files /dev/null and b/src/assets/images/floorplaneditor/door-direction-2.png differ diff --git a/src/assets/images/floorplaneditor/door-direction-3.png b/src/assets/images/floorplaneditor/door-direction-3.png new file mode 100644 index 00000000..15712a91 Binary files /dev/null and b/src/assets/images/floorplaneditor/door-direction-3.png differ diff --git a/src/assets/images/floorplaneditor/door-direction-4.png b/src/assets/images/floorplaneditor/door-direction-4.png new file mode 100644 index 00000000..eb1b4b5e Binary files /dev/null and b/src/assets/images/floorplaneditor/door-direction-4.png differ diff --git a/src/assets/images/floorplaneditor/door-direction-5.png b/src/assets/images/floorplaneditor/door-direction-5.png new file mode 100644 index 00000000..46e6f4d9 Binary files /dev/null and b/src/assets/images/floorplaneditor/door-direction-5.png differ diff --git a/src/assets/images/floorplaneditor/door-direction-6.png b/src/assets/images/floorplaneditor/door-direction-6.png new file mode 100644 index 00000000..fda613ac Binary files /dev/null and b/src/assets/images/floorplaneditor/door-direction-6.png differ diff --git a/src/assets/images/floorplaneditor/door-direction-7.png b/src/assets/images/floorplaneditor/door-direction-7.png new file mode 100644 index 00000000..96fa8e48 Binary files /dev/null and b/src/assets/images/floorplaneditor/door-direction-7.png differ diff --git a/src/assets/images/floorplaneditor/icon-door.png b/src/assets/images/floorplaneditor/icon-door.png new file mode 100644 index 00000000..1b56bb2b Binary files /dev/null and b/src/assets/images/floorplaneditor/icon-door.png differ diff --git a/src/assets/images/floorplaneditor/icon-tile-down.png b/src/assets/images/floorplaneditor/icon-tile-down.png new file mode 100644 index 00000000..352c48df Binary files /dev/null and b/src/assets/images/floorplaneditor/icon-tile-down.png differ diff --git a/src/assets/images/floorplaneditor/icon-tile-set.png b/src/assets/images/floorplaneditor/icon-tile-set.png new file mode 100644 index 00000000..eac61532 Binary files /dev/null and b/src/assets/images/floorplaneditor/icon-tile-set.png differ diff --git a/src/assets/images/floorplaneditor/icon-tile-unset.png b/src/assets/images/floorplaneditor/icon-tile-unset.png new file mode 100644 index 00000000..3f5e2181 Binary files /dev/null and b/src/assets/images/floorplaneditor/icon-tile-unset.png differ diff --git a/src/assets/images/floorplaneditor/icon-tile-up.png b/src/assets/images/floorplaneditor/icon-tile-up.png new file mode 100644 index 00000000..27153e0c Binary files /dev/null and b/src/assets/images/floorplaneditor/icon-tile-up.png differ diff --git a/src/assets/images/floorplaneditor/preview_tile.png b/src/assets/images/floorplaneditor/preview_tile.png new file mode 100644 index 00000000..607f4501 Binary files /dev/null and b/src/assets/images/floorplaneditor/preview_tile.png differ diff --git a/src/assets/images/floorplaneditor/selected_height_icon.png b/src/assets/images/floorplaneditor/selected_height_icon.png new file mode 100644 index 00000000..f763fde5 Binary files /dev/null and b/src/assets/images/floorplaneditor/selected_height_icon.png differ diff --git a/src/assets/images/help/help_index.png b/src/assets/images/help/help_index.png new file mode 100644 index 00000000..2844f41e Binary files /dev/null and b/src/assets/images/help/help_index.png differ 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 00000000..36c5daec Binary files /dev/null and b/src/assets/images/loading/connecting-duck-spritesheet.png differ 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 00000000..e3772c8b Binary files /dev/null and b/src/assets/images/loading/connecting_duck_01.png differ diff --git a/src/assets/images/loading/connecting_duck_02.png b/src/assets/images/loading/connecting_duck_02.png new file mode 100644 index 00000000..86499a37 Binary files /dev/null and b/src/assets/images/loading/connecting_duck_02.png differ 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 00000000..15a2bb9f Binary files /dev/null and b/src/assets/images/loading/connecting_duck_03.png differ diff --git a/src/assets/images/loading/connecting_duck_04.png b/src/assets/images/loading/connecting_duck_04.png new file mode 100644 index 00000000..90860f2c Binary files /dev/null and b/src/assets/images/loading/connecting_duck_04.png differ diff --git a/src/assets/images/loading/connecting_duck_05.png b/src/assets/images/loading/connecting_duck_05.png new file mode 100644 index 00000000..a72154ab Binary files /dev/null and b/src/assets/images/loading/connecting_duck_05.png differ 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 00000000..0e51a51d Binary files /dev/null and b/src/assets/images/loading/connecting_duck_06.png differ diff --git a/src/assets/images/loading/connecting_duck_07.png b/src/assets/images/loading/connecting_duck_07.png new file mode 100644 index 00000000..76c9e347 Binary files /dev/null and b/src/assets/images/loading/connecting_duck_07.png differ diff --git a/src/assets/images/room-widgets/exchange-credit/exchange-credit-image.png b/src/assets/images/room-widgets/exchange-credit/exchange-credit-image.png new file mode 100644 index 00000000..eef5da6c Binary files /dev/null and b/src/assets/images/room-widgets/exchange-credit/exchange-credit-image.png differ diff --git a/src/assets/styles/bootstrap/_variables.scss b/src/assets/styles/bootstrap/_variables.scss index feb71255..374a9abb 100644 --- a/src/assets/styles/bootstrap/_variables.scss +++ b/src/assets/styles/bootstrap/_variables.scss @@ -610,7 +610,7 @@ $aspect-ratios: ( // scss-docs-start font-variables // stylelint-disable value-keyword-case -$font-family-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !default; +$font-family-sans-serif: GameUbuntu, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !default; $font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !default; // stylelint-enable value-keyword-case $font-family-base: var(--#{$variable-prefix}font-sans-serif) !default; @@ -1381,15 +1381,15 @@ $form-feedback-tooltip-border-radius: $tooltip-border-radius !default; // scss-docs-start popover-variables $popover-font-size: $font-size-sm !default; -$popover-bg: $white !default; +$popover-bg: $light !default; $popover-max-width: 276px !default; $popover-border-width: $border-width !default; -$popover-border-color: rgba($black, .2) !default; -$popover-border-radius: $border-radius-lg !default; +$popover-border-color: $border-color !default; +$popover-border-radius: $border-radius !default; $popover-inner-border-radius: subtract($popover-border-radius, $popover-border-width) !default; $popover-box-shadow: $box-shadow !default; -$popover-header-bg: shade-color($popover-bg, 6%) !default; +$popover-header-bg: $primary !default; $popover-header-color: $headings-color !default; $popover-header-padding-y: .5rem !default; $popover-header-padding-x: $spacer !default; diff --git a/src/assets/styles/icons.scss b/src/assets/styles/icons.scss index 8517fcce..87fd77ce 100644 --- a/src/assets/styles/icons.scss +++ b/src/assets/styles/icons.scss @@ -716,6 +716,90 @@ height: 16px; } + &.icon-hc-banner { + background: url("../images/catalog/hc_big.png"); + width: 68px; + height: 40px; + } + + &.icon-set-tile { + background-image: url("../images/floorplaneditor/icon-tile-set.png"); + width: 40px; + height: 40px; + } + + &.icon-unset-tile { + background-image: url("../images/floorplaneditor/icon-tile-unset.png"); + width: 40px; + height: 40px; + } + + &.icon-increase-height { + background-image: url("../images/floorplaneditor/icon-tile-up.png"); + width: 40px; + height: 40px; + } + + &.icon-decrease-height { + background-image: url("../images/floorplaneditor/icon-tile-down.png"); + width: 40px; + height: 40px; + } + + &.icon-set-door { + background-image: url("../images/floorplaneditor/icon-door.png"); + width: 40px; + height: 40px; + } + + &.icon-door-direction-0 { + background-image: url("../images/floorplaneditor/door-direction-0.png"); + width: 80px; + height: 45px; + } + + &.icon-door-direction-1 { + background-image: url("../images/floorplaneditor/door-direction-1.png"); + width: 80px; + height: 45px; + } + + &.icon-door-direction-2 { + background-image: url("../images/floorplaneditor/door-direction-2.png"); + width: 80px; + height: 45px; + } + + &.icon-door-direction-3 { + background-image: url("../images/floorplaneditor/door-direction-3.png"); + width: 80px; + height: 45px; + } + + &.icon-door-direction-4 { + background-image: url("../images/floorplaneditor/door-direction-4.png"); + width: 80px; + height: 45px; + } + + &.icon-door-direction-5 { + background-image: url("../images/floorplaneditor/door-direction-5.png"); + width: 80px; + height: 45px; + } + + &.icon-door-direction-6 { + background-image: url("../images/floorplaneditor/door-direction-6.png"); + width: 80px; + height: 45px; + } + + &.icon-door-direction-7 { + background-image: url("../images/floorplaneditor/door-direction-7.png"); + width: 80px; + height: 45px; + } + &.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); +} 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/events/chat-history/ChatHistoryEvent.ts b/src/events/chat-history/ChatHistoryEvent.ts new file mode 100644 index 00000000..e038f22e --- /dev/null +++ b/src/events/chat-history/ChatHistoryEvent.ts @@ -0,0 +1,8 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; + +export class ChatHistoryEvent extends NitroEvent +{ + public static SHOW_CHAT_HISTORY: string = 'CHE_SHOW_CHAT_HISTORY'; + public static HIDE_CHAT_HISTORY: string = 'CHE_HIDE_CHAT_HISTORY'; + public static TOGGLE_CHAT_HISTORY: string = 'CHE_TOGGLE_CHAT_HISTORY'; +} diff --git a/src/events/floorplan-editor/FloorplanEditorEvent.ts b/src/events/floorplan-editor/FloorplanEditorEvent.ts new file mode 100644 index 00000000..62f7f3bf --- /dev/null +++ b/src/events/floorplan-editor/FloorplanEditorEvent.ts @@ -0,0 +1,8 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; + +export class FloorplanEditorEvent extends NitroEvent +{ + public static SHOW_FLOORPLAN_EDITOR: string = 'FPEE_SHOW_FLOORPLAN_EDITOR'; + public static HIDE_FLOORPLAN_EDITOR: string = 'FPEE_HIDE_FLOORPLAN_EDITOR'; + public static TOGGLE_FLOORPLAN_EDITOR: string = 'FPEE_TOGGLE_FLOORPLAN_EDITOR'; +} diff --git a/src/events/help/HelpEvent.ts b/src/events/help/HelpEvent.ts new file mode 100644 index 00000000..1403f20f --- /dev/null +++ b/src/events/help/HelpEvent.ts @@ -0,0 +1,8 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; + +export class HelpEvent extends NitroEvent +{ + public static SHOW_HELP_CENTER: string = 'HCE_SHOW_HELP_CENTER'; + public static HIDE_HELP_CENTER: string = 'HCE_HIDE_HELP_CENTER'; + public static TOGGLE_HELP_CENTER: string = 'HCE_TOGGLE_HELP_CENTER'; +} diff --git a/src/events/help/HelpReportUserEvent.ts b/src/events/help/HelpReportUserEvent.ts new file mode 100644 index 00000000..b4711b76 --- /dev/null +++ b/src/events/help/HelpReportUserEvent.ts @@ -0,0 +1,20 @@ +import { HelpEvent } from './HelpEvent'; + +export class HelpReportUserEvent extends HelpEvent +{ + public static REPORT_USER: string = 'HCE_HELP_CENTER_REPORT_USER'; + + private _reportedUserId: number; + + constructor(userId: number) + { + super(HelpReportUserEvent.REPORT_USER); + + this._reportedUserId = userId; + } + + public get reportedUserId(): number + { + return this._reportedUserId; + } +} diff --git a/src/events/mod-tools/ModToolsEvent.ts b/src/events/mod-tools/ModToolsEvent.ts index b6070a36..ac7dc7d8 100644 --- a/src/events/mod-tools/ModToolsEvent.ts +++ b/src/events/mod-tools/ModToolsEvent.ts @@ -5,6 +5,8 @@ export class ModToolsEvent extends NitroEvent public static SHOW_MOD_TOOLS: string = 'MTE_SHOW_MOD_TOOLS'; public static HIDE_MOD_TOOLS: string = 'MTE_HIDE_MOD_TOOLS'; public static TOGGLE_MOD_TOOLS: string = 'MTE_TOGGLE_MOD_TOOLS'; - public static SELECT_USER: string = 'MTE_SELECT_USER'; public static OPEN_ROOM_INFO: string = 'MTE_OPEN_ROOM_INFO'; + public static OPEN_ROOM_CHATLOG: string = 'MTE_OPEN_ROOM_CHATLOG'; + public static OPEN_USER_INFO: string = 'MTE_OPEN_USER_INFO'; + public static OPEN_USER_CHATLOG: string = 'MTE_OPEN_USER_CHATLOG'; } diff --git a/src/events/mod-tools/ModToolsOpenRoomChatlogEvent.ts b/src/events/mod-tools/ModToolsOpenRoomChatlogEvent.ts new file mode 100644 index 00000000..692e687d --- /dev/null +++ b/src/events/mod-tools/ModToolsOpenRoomChatlogEvent.ts @@ -0,0 +1,18 @@ +import { ModToolsEvent } from './ModToolsEvent'; + +export class ModToolsOpenRoomChatlogEvent extends ModToolsEvent +{ + private _roomId: number; + + constructor(roomId: number) + { + super(ModToolsEvent.OPEN_ROOM_CHATLOG); + + this._roomId = roomId; + } + + public get roomId(): number + { + return this._roomId; + } +} diff --git a/src/events/mod-tools/ModToolsOpenUserChatlogEvent.ts b/src/events/mod-tools/ModToolsOpenUserChatlogEvent.ts new file mode 100644 index 00000000..aa8858e8 --- /dev/null +++ b/src/events/mod-tools/ModToolsOpenUserChatlogEvent.ts @@ -0,0 +1,18 @@ +import { ModToolsEvent } from './ModToolsEvent'; + +export class ModToolsOpenUserChatlogEvent extends ModToolsEvent +{ + private _userId: number; + + constructor(userId: number) + { + super(ModToolsEvent.OPEN_USER_CHATLOG); + + this._userId = userId; + } + + public get userId(): number + { + return this._userId; + } +} diff --git a/src/events/mod-tools/ModToolsOpenUserInfoEvent.ts b/src/events/mod-tools/ModToolsOpenUserInfoEvent.ts new file mode 100644 index 00000000..58148ee9 --- /dev/null +++ b/src/events/mod-tools/ModToolsOpenUserInfoEvent.ts @@ -0,0 +1,18 @@ +import { ModToolsEvent } from './ModToolsEvent'; + +export class ModToolsOpenUserInfoEvent extends ModToolsEvent +{ + private _userId: number; + + constructor(userId: number) + { + super(ModToolsEvent.OPEN_USER_INFO); + + this._userId = userId; + } + + public get userId(): number + { + return this._userId; + } +} diff --git a/src/events/mod-tools/ModToolsSelectUserEvent.ts b/src/events/mod-tools/ModToolsSelectUserEvent.ts deleted file mode 100644 index 148ffe28..00000000 --- a/src/events/mod-tools/ModToolsSelectUserEvent.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ModToolsEvent } from './ModToolsEvent'; - -export class ModToolsSelectUserEvent extends ModToolsEvent -{ - private _webID: number; - private _name: string; - - constructor(webID: number, name: string) - { - super(ModToolsEvent.SELECT_USER); - - this._webID = webID; - this._name = name; - } - - public get webID(): number - { - return this._webID; - } - - public get name(): string - { - return this._name; - } -} diff --git a/src/index.scss b/src/index.scss index d3842a6a..d5baf038 100644 --- a/src/index.scss +++ b/src/index.scss @@ -3,8 +3,6 @@ html, body { - font-family: GameUbuntu, sans-serif; - margin: 0; padding: 0; width: 100%; height: 100%; 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/button/NitroLayoutButton.tsx b/src/layout/button/NitroLayoutButton.tsx index 0b66a3d0..68ea060f 100644 --- a/src/layout/button/NitroLayoutButton.tsx +++ b/src/layout/button/NitroLayoutButton.tsx @@ -7,7 +7,7 @@ export const NitroLayoutButton: FC = props => const getClassName = useMemo(() => { - let newClassName = 'btn'; + let newClassName = 'd-flex justify-content-center align-items-center btn'; if(variant && variant.length) newClassName += ` btn-${ variant }`; diff --git a/src/layout/card/NitroCardView.scss b/src/layout/card/NitroCardView.scss index 7e82a515..2ba6ec2e 100644 --- a/src/layout/card/NitroCardView.scss +++ b/src/layout/card/NitroCardView.scss @@ -5,11 +5,16 @@ $nitro-card-tabs-height: 33px; pointer-events: all; resize: both; - @import './accordion/NitroCardAccordionView'; - @import './content/NitroCardContentView'; - @import './grid/NitroCardGridView'; - @import './header/NitroCardHeaderView'; - @import './tabs/NitroCardTabsView'; + &.theme-dark { + padding: 2px; + background-color: #1C323F; + border: 2px solid rgba(255, 255, 255, 0.5); + border-radius: 0.25rem; + } + + &.theme-primary { + border: $border-width solid $border-color; + } } .nitro-card-responsive { @@ -19,10 +24,7 @@ $nitro-card-tabs-height: 33px; width: 100%; height: 100%; pointer-events: none; - - .theme-primary { - border: $border-width solid $border-color; - } + overflow: hidden; @include media-breakpoint-down(lg) { @@ -31,7 +33,7 @@ $nitro-card-tabs-height: 33px; left: 0 !important; width: 100%; height: 100%; - transform: none !important; + //transform: none !important; overflow: hidden; } @@ -45,4 +47,33 @@ $nitro-card-tabs-height: 33px; // } } } + + @include media-breakpoint-down(sm) { + + .draggable-window { + top: 0 !important; + left: 0 !important; + width: 100%; + height: 100%; + //transform: none !important; + overflow: hidden; + } + + .nitro-card { + width: 100%; + max-width: 100%; + max-height: calc(100% - #{$toolbar-height}); + margin: 0; + + &.rounded { + border-radius: 0 !important; + } + } + } } + +@import './accordion/NitroCardAccordionView'; +@import './content/NitroCardContentView'; +@import './grid/NitroCardGridView'; +@import './header/NitroCardHeaderView'; +@import './tabs/NitroCardTabsView'; diff --git a/src/layout/card/content/NitroCardContentView.scss b/src/layout/card/content/NitroCardContentView.scss index c56cad8c..a3ec273d 100644 --- a/src/layout/card/content/NitroCardContentView.scss +++ b/src/layout/card/content/NitroCardContentView.scss @@ -3,6 +3,10 @@ padding-top: $container-padding-x; padding-bottom: $container-padding-x; overflow: auto; + + &.theme-dark { + background-color: #1C323F !important; + } } @include media-breakpoint-down(lg) { diff --git a/src/layout/card/content/NitroCardContentView.tsx b/src/layout/card/content/NitroCardContentView.tsx index 8565b3ab..4553d7dc 100644 --- a/src/layout/card/content/NitroCardContentView.tsx +++ b/src/layout/card/content/NitroCardContentView.tsx @@ -4,11 +4,11 @@ import { NitroCardContentViewProps } from './NitroCardContextView.types'; export const NitroCardContentView: FC = props => { - const { children = null, className = '', ...rest } = props; + const { theme = 'primary', children = null, className = '', ...rest } = props; const { simple = false } = useNitroCardContext(); return ( -
+
{ children }
); diff --git a/src/layout/card/content/NitroCardContextView.types.ts b/src/layout/card/content/NitroCardContextView.types.ts index 120cd9d3..21b1d68d 100644 --- a/src/layout/card/content/NitroCardContextView.types.ts +++ b/src/layout/card/content/NitroCardContextView.types.ts @@ -2,4 +2,6 @@ import { DetailsHTMLAttributes } from 'react'; export interface NitroCardContentViewProps extends DetailsHTMLAttributes -{} +{ + theme?: string; +} 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 => { - const { headerText = null, onCloseClick = null } = props; + const { headerText = null, onCloseClick = null, theme= 'primary' } = props; const { simple = false } = useNitroCardContext(); const onMouseDown = useCallback((event: MouseEvent) => @@ -17,7 +17,7 @@ export const NitroCardHeaderView: FC = props => { return (
-
+
{ headerText }
@@ -31,7 +31,7 @@ export const NitroCardHeaderView: FC = props => return (
-
+
{ headerText }
diff --git a/src/layout/card/header/NitroCardHeaderView.types.ts b/src/layout/card/header/NitroCardHeaderView.types.ts index d1d78d29..7a5c7e88 100644 --- a/src/layout/card/header/NitroCardHeaderView.types.ts +++ b/src/layout/card/header/NitroCardHeaderView.types.ts @@ -3,5 +3,6 @@ import { MouseEvent } from 'react'; export interface NitroCardHeaderViewProps { headerText: string; + theme?: string; onCloseClick: (event: MouseEvent) => void; } 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/NitroLayoutSpacing.type.ts b/src/layout/common/NitroLayoutSpacing.type.ts index 96e53311..4f2bd26a 100644 --- a/src/layout/common/NitroLayoutSpacing.type.ts +++ b/src/layout/common/NitroLayoutSpacing.type.ts @@ -1 +1 @@ -export type NitroLayoutSpacing = 1 | 2 | 3 | 4 | 5; +export type NitroLayoutSpacing = 0 | 1 | 2 | 3 | 4 | 5; 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/draggable-window/DraggableWindow.tsx b/src/layout/draggable-window/DraggableWindow.tsx index a5cab370..d9d1fb03 100644 --- a/src/layout/draggable-window/DraggableWindow.tsx +++ b/src/layout/draggable-window/DraggableWindow.tsx @@ -1,5 +1,5 @@ -import { MouseEventType } from '@nitrots/nitro-renderer'; -import { FC, Key, MouseEvent as ReactMouseEvent, useCallback, useEffect, useRef, useState } from 'react'; +import { MouseEventType, TouchEventType } from '@nitrots/nitro-renderer'; +import { FC, Key, MouseEvent as ReactMouseEvent, TouchEvent as ReactTouchEvent, useCallback, useEffect, useRef, useState } from 'react'; import { DraggableWindowPosition, DraggableWindowProps } from './DraggableWindow.types'; const CURRENT_WINDOWS: HTMLElement[] = []; @@ -29,7 +29,7 @@ export const DraggableWindow: FC = props => } }, []); - const onMouseDown = useCallback((event: ReactMouseEvent) => + const moveCurrentWindow = useCallback(() => { const index = CURRENT_WINDOWS.indexOf(elementRef.current); @@ -50,18 +50,47 @@ export const DraggableWindow: FC = props => bringToTop(); }, [ bringToTop ]); - const onDragMouseDown = useCallback((event: MouseEvent) => + const onMouseDown = useCallback((event: ReactMouseEvent) => { - setStart({ x: event.clientX, y: event.clientY }); + moveCurrentWindow(); + }, [ moveCurrentWindow ]); + + const onTouchStart = useCallback((event: ReactTouchEvent) => + { + moveCurrentWindow(); + }, [ moveCurrentWindow ]); + + const startDragging = useCallback((startX: number, startY: number) => + { + setStart({ x: startX, y: startY }); setIsDragging(true); }, []); + const onDragMouseDown = useCallback((event: MouseEvent) => + { + startDragging(event.clientX, event.clientY); + }, [ startDragging ]); + + const onTouchDown = useCallback((event: TouchEvent) => + { + const touch = event.touches[0]; + + startDragging(touch.clientX, touch.clientY); + }, [ startDragging ]); + const onDragMouseMove = useCallback((event: MouseEvent) => { setDelta({ x: (event.clientX - start.x), y: (event.clientY - start.y) }); }, [ start ]); - const onDragMouseUp = useCallback((event: MouseEvent) => + const onDragTouchMove = useCallback((event: TouchEvent) => + { + const touch = event.touches[0]; + + setDelta({ x: (touch.clientX - start.x), y: (touch.clientY - start.y) }); + }, [ start ]); + + const completeDrag = useCallback(() => { if(!elementRef.current || !dragHandler) return; @@ -98,6 +127,16 @@ export const DraggableWindow: FC = props => if(uniqueKey !== null) POS_MEMORY.set(uniqueKey, { x: offsetX, y: offsetY }); }, [ dragHandler, delta, offset, uniqueKey ]); + const onDragMouseUp = useCallback((event: MouseEvent) => + { + completeDrag(); + }, [ completeDrag ]); + + const onDragTouchUp = useCallback((event: TouchEvent) => + { + completeDrag(); + }, [ completeDrag ]); + useEffect(() => { const element = (elementRef.current as HTMLElement); @@ -169,29 +208,35 @@ export const DraggableWindow: FC = props => if(!dragHandler) return; dragHandler.addEventListener(MouseEventType.MOUSE_DOWN, onDragMouseDown); + dragHandler.addEventListener(TouchEventType.TOUCH_START, onTouchDown); return () => { dragHandler.removeEventListener(MouseEventType.MOUSE_DOWN, onDragMouseDown); + dragHandler.removeEventListener(TouchEventType.TOUCH_START, onTouchDown); } - }, [ dragHandler, onDragMouseDown ]); + }, [ dragHandler, onDragMouseDown, onTouchDown ]); useEffect(() => { if(!isDragging) return; document.addEventListener(MouseEventType.MOUSE_UP, onDragMouseUp); + document.addEventListener(TouchEventType.TOUCH_END, onDragTouchUp); document.addEventListener(MouseEventType.MOUSE_MOVE, onDragMouseMove); + document.addEventListener(TouchEventType.TOUCH_MOVE, onDragTouchMove); return () => { document.removeEventListener(MouseEventType.MOUSE_UP, onDragMouseUp); + document.removeEventListener(TouchEventType.TOUCH_END, onDragTouchUp); document.removeEventListener(MouseEventType.MOUSE_MOVE, onDragMouseMove); + document.removeEventListener(TouchEventType.TOUCH_MOVE, onDragTouchMove); } - }, [ isDragging, onDragMouseUp, onDragMouseMove ]); + }, [ isDragging, onDragMouseUp, onDragMouseMove, onDragTouchUp, onDragTouchMove ]); return ( -
+
{ children }
); 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'; diff --git a/src/layout/notification-bubble/NotificationBubbleView.tsx b/src/layout/notification-bubble/NotificationBubbleView.tsx index 49a45b9f..5c91505d 100644 --- a/src/layout/notification-bubble/NotificationBubbleView.tsx +++ b/src/layout/notification-bubble/NotificationBubbleView.tsx @@ -1,28 +1,46 @@ -import { FC, useEffect, useState } from 'react'; +import { FC, useEffect, useMemo, useState } from 'react'; +import { NitroLayoutBase } from '../base'; +import { TransitionAnimation, TransitionAnimationTypes } from '../transitions'; import { NotificationBubbleViewProps } from './NotificationBubbleView.types'; export const NotificationBubbleView: FC = props => { - const { fadesOut = false, close = null, className = '', children = null, ...rest } = props; - const [ isFading, setIsFading ] = useState(false); + const { fadesOut = true, timeoutMs = 8000, close = null, className = '', ...rest } = props; + const [ isVisible, setIsVisible ] = useState(false); + + const getClassName = useMemo(() => + { + let newClassName = 'nitro-notification-bubble rounded'; + + if(className && className.length) newClassName += ` ${ className }`; + + return newClassName; + }, [ className ]); + + useEffect(() => + { + setIsVisible(true); + + return () => setIsVisible(false); + }, []); useEffect(() => { if(!fadesOut) return; const timeout = setTimeout(() => - { - setIsFading(true); + { + setIsVisible(false); - setTimeout(() => close()); - }, 8000); + setTimeout(() => close(), 300); + }, timeoutMs); return () => clearTimeout(timeout); - }, [ fadesOut, close ]); + }, [ fadesOut, timeoutMs, close ]); return ( -
- { children } -
- ) + + + + ); } diff --git a/src/layout/notification-bubble/NotificationBubbleView.types.ts b/src/layout/notification-bubble/NotificationBubbleView.types.ts index 1fac4e46..5c160b4e 100644 --- a/src/layout/notification-bubble/NotificationBubbleView.types.ts +++ b/src/layout/notification-bubble/NotificationBubbleView.types.ts @@ -1,7 +1,8 @@ -import { DetailsHTMLAttributes } from 'react'; +import { NitroLayoutBaseProps } from '../base'; -export interface NotificationBubbleViewProps extends DetailsHTMLAttributes +export interface NotificationBubbleViewProps extends NitroLayoutBaseProps { fadesOut?: boolean; + timeoutMs?: number; close: () => void; } diff --git a/src/layout/trophy/NitroLayoutTrophyView.scss b/src/layout/trophy/NitroLayoutTrophyView.scss index edcc05f8..795169b4 100644 --- a/src/layout/trophy/NitroLayoutTrophyView.scss +++ b/src/layout/trophy/NitroLayoutTrophyView.scss @@ -3,6 +3,7 @@ width: 340px; height: 173px; color: black; + pointer-events: all; background-position: 0px 0px; background-image: url('../../assets/images/room-widgets/trophy-widget/trophy-spritesheet.png'); diff --git a/src/views/Styles.scss b/src/views/Styles.scss index 4f31d9e5..6369f546 100644 --- a/src/views/Styles.scss +++ b/src/views/Styles.scss @@ -20,3 +20,6 @@ @import './achievements/AchievementsView'; @import './user-settings/UserSettingsView'; @import './user-profile/UserProfileVew'; +@import './chat-history/ChatHistoryView'; +@import './help/HelpView'; +@import './floorplan-editor/FloorplanEditorView'; 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..a3bfbaca 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,210 @@ 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 scaledProgressPercent = useMemo(() => + { + return ~~((((getProgress - 0) * (100 - 0)) / (getMaxProgress - 0)) + 0); + }, [ getProgress, getMaxProgress ]); + + 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/GetAchievementLevel.ts b/src/views/achievements/common/GetAchievementLevel.ts new file mode 100644 index 00000000..c62bf7aa --- /dev/null +++ b/src/views/achievements/common/GetAchievementLevel.ts @@ -0,0 +1,8 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; + +export const GetAchievementLevel = (achievement: AchievementData) => +{ + if(achievement.finalLevel) return achievement.level; + + return (achievement.level - 1); +} diff --git a/src/views/achievements/common/GetScaledProgressPercent.ts b/src/views/achievements/common/GetScaledProgressPercent.ts new file mode 100644 index 00000000..8708a709 --- /dev/null +++ b/src/views/achievements/common/GetScaledProgressPercent.ts @@ -0,0 +1,8 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; + +export const GetScaledProgressPercent = (achievement: AchievementData) => +{ + if(!achievement) return 0; + + return ~~(((((achievement.currentPoints + achievement.scoreAtStartOfLevel) - 0) * (100 - 0)) / ((achievement.scoreLimit + achievement.scoreAtStartOfLevel) - 0)) + 0); +} 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..5ab5d320 --- /dev/null +++ b/src/views/achievements/views/achievement-details/AchievementDetailsView.tsx @@ -0,0 +1,60 @@ +import { FC } 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 { GetAchievementLevel } from '../../common/GetAchievementLevel'; +import { GetScaledProgressPercent } from '../../common/GetScaledProgressPercent'; +import { AchievementBadgeView } from '../achievement-badge/AchievementBadgeView'; +import { AchievementDetailsViewProps } from './AchievementDetailsView.types'; + +export const AchievementDetailsView: FC = props => +{ + const { achievement = null } = props; + + if(!achievement) return null; + + const achievementLevel = GetAchievementLevel(achievement); + const scaledProgressPercent = GetScaledProgressPercent(achievement); + + return ( + + + + + { LocalizeText('achievements.details.level', [ 'level', 'limit' ], [ achievementLevel.toString(), achievement.levelCount.toString() ]) } + + + + + + { LocalizeBadgeName(AchievementUtilities.getBadgeCode(achievement)) } + + + { LocalizeBadgeDescription(AchievementUtilities.getBadgeCode(achievement)) } + + + { ((achievement.levelRewardPoints > 0) || (achievement.scoreLimit > 0)) && + + { (achievement.levelRewardPoints > 0) && + + + { LocalizeText('achievements.details.reward') } + + + { achievement.levelRewardPoints } + + + } + { (achievement.scoreLimit > 0) && + + + { LocalizeText('achievements.details.progress', [ 'progress', 'limit' ], [ (achievement.currentPoints + achievement.scoreAtStartOfLevel).toString(), (achievement.scoreLimit + achievement.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..4475e263 --- /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, index) => + { + 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; +} 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 } + ); } 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/gift/CatalogGiftView.tsx b/src/views/catalog/views/gift/CatalogGiftView.tsx index d466d5b6..258d07f5 100644 --- a/src/views/catalog/views/gift/CatalogGiftView.tsx +++ b/src/views/catalog/views/gift/CatalogGiftView.tsx @@ -54,8 +54,11 @@ export const CatalogGiftView: FC<{}> = props => if(giftData.colors && giftData.colors.length > 0) newColors.push({ id: colorId, color: `#${giftData.colors[0].toString(16)}` }); } - setSelectedColorId(newColors[0].id); - setColors(newColors); + if(newColors.length) + { + setSelectedColorId(newColors[0].id); + setColors(newColors); + } }, [ giftConfiguration ]); const close = useCallback(() => @@ -68,7 +71,8 @@ export const CatalogGiftView: FC<{}> = props => setMessage(''); setSelectedBoxIndex(0); setSelectedRibbonIndex(0); - setSelectedColorId(colors[0].id); + + if(colors.length) setSelectedColorId(colors[0].id); }, [ colors ]); const onCatalogEvent = useCallback((event: CatalogEvent) => 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 ( -
-
+ + + +
+ +
+
+ +
+
+
+ + + + ) +} + +export interface FloorplanImportExportViewProps +{ + onCloseClick(): void; +} diff --git a/src/views/floorplan-editor/views/FloorplanOptionsView.tsx b/src/views/floorplan-editor/views/FloorplanOptionsView.tsx new file mode 100644 index 00000000..0c6fc18a --- /dev/null +++ b/src/views/floorplan-editor/views/FloorplanOptionsView.tsx @@ -0,0 +1,188 @@ +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 { 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 { visualizationSettings = null, setVisualizationSettings = null } = useFloorplanEditorContext(); + const [ floorAction, setFloorAction ] = useState(FloorAction.SET); + const [ floorHeight, setFloorHeight ] = useState(0); + + const selectAction = useCallback((action: number) => + { + setFloorAction(action); + FloorplanEditor.instance.actionSettings.currentAction = action; + }, []); + + const changeDoorDirection = useCallback(() => + { + setVisualizationSettings(prevValue => + { + const newValue = Object.assign({}, prevValue); + + if(newValue.entryPointDir < 7) + { + ++newValue.entryPointDir; + } + else + { + newValue.entryPointDir = 0; + } + + return newValue; + }); + }, [ 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) => + { + if(isNaN(value) || (value <= 0)) value = MIN_WALL_HEIGHT; + + if(value > MAX_WALL_HEIGHT) value = MAX_WALL_HEIGHT; + + setVisualizationSettings(prevValue => + { + const newValue = Object.assign({}, prevValue); + + newValue.wallHeight = value; + + return newValue; + }); + }, [ setVisualizationSettings ]); + + function increaseWallHeight(): void + { + let height = (visualizationSettings.wallHeight + 1); + + if(height > MAX_WALL_HEIGHT) height = MAX_WALL_HEIGHT; + + onWallHeightChange(height); + } + + function decreaseWallHeight(): void + { + let height = (visualizationSettings.wallHeight - 1); + + if(height <= 0) height = MIN_WALL_HEIGHT; + + onWallHeightChange(height); + } + + return ( + + + + { LocalizeText('floor.plan.editor.draw.mode') } + + selectAction(FloorAction.SET) }> + + + selectAction(FloorAction.UNSET) }> + + + selectAction(FloorAction.UP) }> + + + selectAction(FloorAction.DOWN) }> + + + selectAction(FloorAction.DOOR) }> + + + + + + + + { LocalizeText('floor.plan.editor.enter.direction') } + + + + + + { LocalizeText('floor.editor.wall.height') } + + + 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') } + + + + + + +
+ ); +} 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/help/HelpMessageHandler.tsx b/src/views/help/HelpMessageHandler.tsx index f71634b7..959abdb9 100644 --- a/src/views/help/HelpMessageHandler.tsx +++ b/src/views/help/HelpMessageHandler.tsx @@ -1,12 +1,10 @@ -import { CallForHelpResultMessageEvent, FurnitureListItemParser, PetData } from '@nitrots/nitro-renderer'; +import { CallForHelpResultMessageEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback } from 'react'; import { LocalizeText } from '../../api'; import { CreateMessageHook } from '../../hooks/messages/message-event'; import { NotificationAlertType } from '../notification-center/common/NotificationAlertType'; import { NotificationUtilities } from '../notification-center/common/NotificationUtilities'; import { GetCloseReasonKey } from './common/GetCloseReasonKey'; -let furniMsgFragments: Map[] = null; -let petMsgFragments: Map[] = null; export const HelpMessageHandler: FC<{}> = props => { diff --git a/src/views/help/HelpView.scss b/src/views/help/HelpView.scss new file mode 100644 index 00000000..f7f513f8 --- /dev/null +++ b/src/views/help/HelpView.scss @@ -0,0 +1,10 @@ +.nitro-help { + height: 430px; + width: 300px; + + .index-image { + background: url('../../assets/images/help/help_index.png'); + width: 126px; + height: 105px; + } +} diff --git a/src/views/help/HelpView.tsx b/src/views/help/HelpView.tsx index 4917c713..630d41d1 100644 --- a/src/views/help/HelpView.tsx +++ b/src/views/help/HelpView.tsx @@ -1,11 +1,79 @@ -import { FC } from 'react'; +import { FC, useCallback, useState } from 'react'; +import { LocalizeText } from '../../api'; +import { HelpEvent } from '../../events/help/HelpEvent'; +import { HelpReportUserEvent } from '../../events/help/HelpReportUserEvent'; +import { useUiEvent } from '../../hooks'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout'; +import { HelpContextProvider } from './context/HelpContext'; +import { IHelpReportState, initialReportState } from './context/HelpContext.types'; import { HelpMessageHandler } from './HelpMessageHandler'; +import { DescribeReportView } from './views/DescribeReportView'; +import { HelpIndexView } from './views/HelpIndexView'; +import { SelectReportedChatsView } from './views/SelectReportedChatsView'; +import { SelectReportedUserView } from './views/SelectReportedUserView'; +import { SelectTopicView } from './views/SelectTopicView'; export const HelpView: FC<{}> = props => { + const [isVisible, setIsVisible] = useState(false); + const [ helpReportState, setHelpReportState ] = useState(initialReportState); + + const onHelpEvent = useCallback((event: HelpEvent) => + { + setHelpReportState(initialReportState); + + switch(event.type) + { + case HelpEvent.SHOW_HELP_CENTER: + setIsVisible(true); + break; + case HelpEvent.HIDE_HELP_CENTER: + setIsVisible(false); + break; + case HelpEvent.TOGGLE_HELP_CENTER: + setIsVisible(!isVisible); + break; + case HelpReportUserEvent.REPORT_USER: + const reportEvent = event as HelpReportUserEvent; + const reportState = Object.assign({}, helpReportState ); + reportState.reportedUserId = reportEvent.reportedUserId; + reportState.currentStep = 2; + setHelpReportState(reportState); + setIsVisible(true); + break; + } + }, [helpReportState, isVisible]); + + useUiEvent(HelpEvent.TOGGLE_HELP_CENTER, onHelpEvent); + useUiEvent(HelpEvent.SHOW_HELP_CENTER, onHelpEvent); + useUiEvent(HelpEvent.HIDE_HELP_CENTER, onHelpEvent); + useUiEvent(HelpReportUserEvent.REPORT_USER, onHelpEvent); + + const CurrentStepView = useCallback(() => + { + switch(helpReportState.currentStep) + { + case 0: return + case 1: return + case 2: return + case 3: return + case 4: return + } + + return null; + }, [helpReportState.currentStep]); + return ( - <> + - + {isVisible && + + setIsVisible(false)} /> + + + + + } + ); } diff --git a/src/views/help/common/IUser.ts b/src/views/help/common/IUser.ts new file mode 100644 index 00000000..bd3852ab --- /dev/null +++ b/src/views/help/common/IUser.ts @@ -0,0 +1,5 @@ +export interface IUser +{ + id: number; + username: string; +} diff --git a/src/views/help/context/HelpContext.tsx b/src/views/help/context/HelpContext.tsx new file mode 100644 index 00000000..2cad4a52 --- /dev/null +++ b/src/views/help/context/HelpContext.tsx @@ -0,0 +1,14 @@ +import { createContext, FC, useContext } from 'react'; +import { HelpContextProps, IHelpContext } from './HelpContext.types'; + +const HelpContext = createContext({ + helpReportState: null, + setHelpReportState: null +}); + +export const HelpContextProvider: FC = props => +{ + return { props.children } +} + +export const useHelpContext = () => useContext(HelpContext); diff --git a/src/views/help/context/HelpContext.types.ts b/src/views/help/context/HelpContext.types.ts new file mode 100644 index 00000000..9368308b --- /dev/null +++ b/src/views/help/context/HelpContext.types.ts @@ -0,0 +1,33 @@ +import { ProviderProps } from 'react'; +import { IChatEntry } from '../../chat-history/context/ChatHistoryContext.types'; + +export interface IHelpContext +{ + helpReportState: IHelpReportState; + setHelpReportState: React.Dispatch>; +} + +export interface IHelpReportState { + reportedUserId: number; + reportedChats: IChatEntry[]; + cfhCategory: number; + cfhTopic: number; + roomId: number; + message: string; + currentStep: number; +} + +export const initialReportState: IHelpReportState = { + reportedUserId: -1, + reportedChats: [], + cfhCategory: -1, + cfhTopic: -1, + roomId: -1, + message: '', + currentStep: 0 +} + +export interface HelpContextProps extends ProviderProps +{ + +} diff --git a/src/views/help/views/DescribeReportView.tsx b/src/views/help/views/DescribeReportView.tsx new file mode 100644 index 00000000..f936d225 --- /dev/null +++ b/src/views/help/views/DescribeReportView.tsx @@ -0,0 +1,50 @@ +import { CallForHelpMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useState } from 'react'; +import { LocalizeText } from '../../../api'; +import { HelpEvent } from '../../../events/help/HelpEvent'; +import { dispatchUiEvent, SendMessageHook } from '../../../hooks'; +import { useHelpContext } from '../context/HelpContext'; + +export const DescribeReportView: FC<{}> = props => +{ + const { helpReportState = null, setHelpReportState = null } = useHelpContext(); + const [message, setMessage] = useState(''); + + const submitReport = useCallback(() => + { + if(message.length < 15) return; + + const reportState = Object.assign({}, helpReportState); + + reportState.message = message; + + setHelpReportState(reportState); + + const roomId = reportState.reportedChats[0].roomId; + const chats: (string | number )[] = []; + reportState.reportedChats.forEach(entry => + { + chats.push(entry.entityId); + chats.push(entry.message); + }); + + SendMessageHook(new CallForHelpMessageComposer(message, reportState.cfhTopic, reportState.reportedUserId, roomId, chats)); + + dispatchUiEvent(new HelpEvent(HelpEvent.HIDE_HELP_CENTER)); + }, [helpReportState, message, setHelpReportState]); + + return ( + <> +
+

{LocalizeText('help.emergency.chat_report.subtitle')}

+
{LocalizeText('help.cfh.input.text')}
+
+ +
+ +
- - + +
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/tickets/ModToolsTicketView.scss b/src/views/mod-tools/views/tickets/ModToolsTicketView.scss new file mode 100644 index 00000000..f2459483 --- /dev/null +++ b/src/views/mod-tools/views/tickets/ModToolsTicketView.scss @@ -0,0 +1,11 @@ +.nitro-mod-tools-tickets +{ + width: 400px; + height: 200px; +} + +.nitro-mod-tools-handle-issue +{ + width: 400px; + height: 300px; +} diff --git a/src/views/mod-tools/views/tickets/ModToolsTicketsView.tsx b/src/views/mod-tools/views/tickets/ModToolsTicketsView.tsx index 5d2ebf47..e2ccea90 100644 --- a/src/views/mod-tools/views/tickets/ModToolsTicketsView.tsx +++ b/src/views/mod-tools/views/tickets/ModToolsTicketsView.tsx @@ -1,6 +1,13 @@ -import { FC, useState } from 'react'; +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 { IssueInfoView } from './issue-info/IssueInfoView'; import { ModToolsTicketsViewProps } from './ModToolsTicketsView.types'; +import { ModToolsMyIssuesTabView } from './my-issues/ModToolsMyIssuesTabView'; +import { ModToolsOpenIssuesTabView } from './open-issues/ModToolsOpenIssuesTabView'; +import { ModToolsPickedIssuesTabView } from './picked-issues/ModToolsPickedIssuesTabView'; const TABS: string[] = [ 'Open Issues', @@ -11,10 +18,70 @@ const TABS: string[] = [ export const ModToolsTicketsView: FC = props => { const { onCloseClick = null } = props; - + const { modToolsState = null } = useModToolsContext(); + const { tickets= null } = modToolsState; const [ currentTab, setCurrentTab ] = useState(0); + const [ issueInfoWindows, setIssueInfoWindows ] = useState([]); + + const openIssues = useMemo(() => + { + if(!tickets) return []; + + 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 onIssueInfoClosed = useCallback((issueId: number) => + { + const indexOfValue = issueInfoWindows.indexOf(issueId); + + if(indexOfValue === -1) return; + + const newValues = Array.from(issueInfoWindows); + newValues.splice(indexOfValue, 1); + setIssueInfoWindows(newValues); + }, [issueInfoWindows]); + + const onIssueHandleClicked = useCallback((issueId: number) => + { + if(issueInfoWindows.indexOf(issueId) === -1) + { + const newValues = Array.from(issueInfoWindows); + newValues.push(issueId); + setIssueInfoWindows(newValues); + } + else + { + onIssueInfoClosed(issueId); + } + }, [issueInfoWindows, onIssueInfoClosed]); + + const CurrentTabComponent = useCallback(() => + { + switch(currentTab) + { + case 0: return ; + case 1: return ; + case 2: return ; + default: return null; + } + }, [currentTab, myIssues, onIssueHandleClicked, openIssues, pickedIssues]); return ( + <> @@ -26,8 +93,14 @@ export const ModToolsTicketsView: FC = props => ); }) } -
+
+ +
+ { + issueInfoWindows && issueInfoWindows.map(issueId => ) + } + ); } diff --git a/src/views/mod-tools/views/tickets/issue-info/CfhChatlogView.tsx b/src/views/mod-tools/views/tickets/issue-info/CfhChatlogView.tsx new file mode 100644 index 00000000..fdb4c28e --- /dev/null +++ b/src/views/mod-tools/views/tickets/issue-info/CfhChatlogView.tsx @@ -0,0 +1,37 @@ +import { CfhChatlogData, CfhChatlogEvent, GetCfhChatlogMessageComposer } 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 { CfhChatlogViewProps } from './CfhChatlogView.types'; + +export const CfhChatlogView: FC = props => +{ + const { onCloseClick = null, issueId = null } = props; + const [ chatlogData, setChatlogData ] = useState(null); + + useEffect(() => + { + SendMessageHook(new GetCfhChatlogMessageComposer(issueId)); + }, [issueId]); + + const onCfhChatlogEvent = useCallback((event: CfhChatlogEvent) => + { + const parser = event.getParser(); + + if(!parser || parser.data.issueId !== issueId) return; + + setChatlogData(parser.data); + }, [issueId]); + + CreateMessageHook(CfhChatlogEvent, onCfhChatlogEvent); + + return ( + + + + { chatlogData && } + + + ); +} diff --git a/src/views/mod-tools/views/tickets/issue-info/CfhChatlogView.types.ts b/src/views/mod-tools/views/tickets/issue-info/CfhChatlogView.types.ts new file mode 100644 index 00000000..c40c18ad --- /dev/null +++ b/src/views/mod-tools/views/tickets/issue-info/CfhChatlogView.types.ts @@ -0,0 +1,5 @@ +export interface CfhChatlogViewProps +{ + issueId: number; + onCloseClick(): void; +} diff --git a/src/views/mod-tools/views/tickets/issue-info/IssueInfoView.tsx b/src/views/mod-tools/views/tickets/issue-info/IssueInfoView.tsx new file mode 100644 index 00000000..61a9784a --- /dev/null +++ b/src/views/mod-tools/views/tickets/issue-info/IssueInfoView.tsx @@ -0,0 +1,72 @@ +import { CloseIssuesMessageComposer, ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useMemo, useState } from 'react'; +import { LocalizeText } from '../../../../../api'; +import { ModToolsOpenUserInfoEvent } from '../../../../../events/mod-tools/ModToolsOpenUserInfoEvent'; +import { dispatchUiEvent, SendMessageHook } from '../../../../../hooks'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout'; +import { getSourceName } from '../../../common/IssueCategoryNames'; +import { useModToolsContext } from '../../../context/ModToolsContext'; +import { CfhChatlogView } from './CfhChatlogView'; +import { IssueInfoViewProps } from './IssueInfoView.types'; + +export const IssueInfoView: FC = props => +{ + const { issueId = null, onIssueInfoClosed = null } = props; + const { modToolsState = null } = useModToolsContext(); + const { tickets= null } = modToolsState; + const [ cfhChatlogOpen, setcfhChatlogOpen ] = useState(false); + + const ticket = useMemo(() => + { + return tickets.find( issue => issue.issueId === issueId); + }, [issueId, tickets]); + + const onReleaseIssue = useCallback((issueId: number) => + { + SendMessageHook(new ReleaseIssuesMessageComposer([issueId])); + onIssueInfoClosed(issueId); + }, [onIssueInfoClosed]); + + const openUserInfo = useCallback((userId: number) => + { + dispatchUiEvent(new ModToolsOpenUserInfoEvent(userId)); + }, []); + + const closeIssue = useCallback((resolutionType: number) => + { + SendMessageHook(new CloseIssuesMessageComposer([issueId], resolutionType)); + onIssueInfoClosed(issueId) + }, [issueId, onIssueInfoClosed]); + + return ( + <> + + onIssueInfoClosed(issueId)} /> + +
+
+

Issue Information

+
Source: {getSourceName(ticket.categoryId)}
+
Category: {LocalizeText('help.cfh.topic.' + ticket.reportedCategoryId)}
+
Description: {ticket.message}
+
Caller:
+
Reported User:
+
+
+
+ +
+
+ + + + +
+
+
+
+
+ { cfhChatlogOpen && setcfhChatlogOpen(false) }/>} + + ); +} diff --git a/src/views/mod-tools/views/tickets/issue-info/IssueInfoView.types.ts b/src/views/mod-tools/views/tickets/issue-info/IssueInfoView.types.ts new file mode 100644 index 00000000..1b28d803 --- /dev/null +++ b/src/views/mod-tools/views/tickets/issue-info/IssueInfoView.types.ts @@ -0,0 +1,5 @@ +export interface IssueInfoViewProps +{ + issueId: number; + onIssueInfoClosed(issueId: number): void; +} diff --git a/src/views/mod-tools/views/tickets/my-issues/ModToolsMyIssuesTabView.tsx b/src/views/mod-tools/views/tickets/my-issues/ModToolsMyIssuesTabView.tsx new file mode 100644 index 00000000..1a3fcde3 --- /dev/null +++ b/src/views/mod-tools/views/tickets/my-issues/ModToolsMyIssuesTabView.tsx @@ -0,0 +1,45 @@ +import { ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback } from 'react'; +import { SendMessageHook } from '../../../../../hooks'; +import { ModToolsMyIssuesTabViewProps } from './ModToolsMyIssuesTabView.types'; + +export const ModToolsMyIssuesTabView: FC = props => +{ + const { myIssues = null, onIssueHandleClick = null } = props; + + + const onReleaseIssue = useCallback((issueId: number) => + { + SendMessageHook(new ReleaseIssuesMessageComposer([issueId])); + }, []); + + return ( + <> + + + + + + + + + + + + {myIssues.map(issue => + { + return ( + + + + + + + ) + }) + } + +
TypeRoom/PlayerOpened
{issue.categoryId}{issue.reportedUserName}{new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString()}
+ + ); +} diff --git a/src/views/mod-tools/views/tickets/my-issues/ModToolsMyIssuesTabView.types.ts b/src/views/mod-tools/views/tickets/my-issues/ModToolsMyIssuesTabView.types.ts new file mode 100644 index 00000000..6d03fbf8 --- /dev/null +++ b/src/views/mod-tools/views/tickets/my-issues/ModToolsMyIssuesTabView.types.ts @@ -0,0 +1,7 @@ +import { IssueMessageData } from '@nitrots/nitro-renderer'; + +export interface ModToolsMyIssuesTabViewProps +{ + myIssues: IssueMessageData[]; + onIssueHandleClick(issueId: number): void; +} 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..4f8bb6d6 --- /dev/null +++ b/src/views/mod-tools/views/tickets/open-issues/ModToolsOpenIssuesTabView.tsx @@ -0,0 +1,42 @@ +import { PickIssuesMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback } from 'react'; +import { SendMessageHook } from '../../../../../hooks'; +import { ModToolsOpenIssuesTabViewProps } from './ModToolsOpenIssuesTabView.types'; + +export const ModToolsOpenIssuesTabView: FC = props => +{ + const { openIssues = null } = props; + + const onPickIssue = useCallback((issueId: number) => + { + SendMessageHook(new PickIssuesMessageComposer([issueId], false, 0, 'pick issue button')); + }, []); + + return ( + <> + + + + + + + + + + + {openIssues.map(issue => + { + return ( + + + + + + ) + }) + } + +
TypeRoom/PlayerOpened
{issue.categoryId}{issue.reportedUserName}{new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString()}
+ + ); +} 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[]; +} diff --git a/src/views/mod-tools/views/tickets/picked-issues/ModToolsPickedIssuesTabView.tsx b/src/views/mod-tools/views/tickets/picked-issues/ModToolsPickedIssuesTabView.tsx new file mode 100644 index 00000000..988a0fd9 --- /dev/null +++ b/src/views/mod-tools/views/tickets/picked-issues/ModToolsPickedIssuesTabView.tsx @@ -0,0 +1,35 @@ +import { FC } from 'react'; +import { ModToolsPickedIssuesTabViewProps } from './ModToolsPickedIssuesTabView.types'; + +export const ModToolsPickedIssuesTabView: FC = props => +{ + const { pickedIssues = null } = props; + + return ( + <> + + + + + + + + + + + {pickedIssues.map(issue => + { + return ( + + + + + + ) + }) + } + +
TypeRoom/PlayerOpenedPicker
{issue.categoryId}{issue.reportedUserName}{new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString()}{issue.pickerUserName}
+ + ); +} diff --git a/src/views/mod-tools/views/tickets/picked-issues/ModToolsPickedIssuesTabView.types.ts b/src/views/mod-tools/views/tickets/picked-issues/ModToolsPickedIssuesTabView.types.ts new file mode 100644 index 00000000..c69b1772 --- /dev/null +++ b/src/views/mod-tools/views/tickets/picked-issues/ModToolsPickedIssuesTabView.types.ts @@ -0,0 +1,6 @@ +import { IssueMessageData } from '@nitrots/nitro-renderer'; + +export interface ModToolsPickedIssuesTabViewProps +{ + pickedIssues: IssueMessageData[]; +} 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 d3a2f076..00000000 --- a/src/views/mod-tools/views/user/ModToolsUserView.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { FC } from 'react'; -import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout'; -import { ModToolsUserViewProps } from './ModToolsUserView.types'; - -export const ModToolsUserView: FC = props => -{ - return ( - - {} } /> - - - - - ); -} diff --git a/src/views/mod-tools/views/user/ModToolsUserView.types.ts b/src/views/mod-tools/views/user/ModToolsUserView.types.ts deleted file mode 100644 index 55b9477c..00000000 --- a/src/views/mod-tools/views/user/ModToolsUserView.types.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface ModToolsUserViewProps -{} diff --git a/src/views/mod-tools/views/user/user-chatlog/ModToolsUserChatlogView.tsx b/src/views/mod-tools/views/user/user-chatlog/ModToolsUserChatlogView.tsx new file mode 100644 index 00000000..705cc690 --- /dev/null +++ b/src/views/mod-tools/views/user/user-chatlog/ModToolsUserChatlogView.tsx @@ -0,0 +1,41 @@ +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'; +import { ChatlogView } from '../../chatlog/ChatlogView'; +import { ModToolsUserChatlogViewProps } from './ModToolsUserChatlogView.types'; + +export const ModToolsUserChatlogView: FC = props => +{ + const { userId = null, onCloseClick = null } = props; + const [userChatlog, setUserChatlog] = useState(null); + const [username, setUsername] = useState(null); + + useEffect(() => + { + SendMessageHook(new GetUserChatlogMessageComposer(userId)); + }, [userId]); + + const onModtoolUserChatlogEvent = useCallback((event: UserChatlogEvent) => + { + const parser = event.getParser(); + + if(!parser || parser.data.userId !== userId) return; + + setUsername(parser.data.username); + setUserChatlog(parser.data.roomChatlogs); + }, [setUsername, setUserChatlog, userId]); + + CreateMessageHook(UserChatlogEvent, onModtoolUserChatlogEvent); + + return ( + + onCloseClick()} /> + + {userChatlog && + + } + + + ); +} diff --git a/src/views/mod-tools/views/user/user-chatlog/ModToolsUserChatlogView.types.ts b/src/views/mod-tools/views/user/user-chatlog/ModToolsUserChatlogView.types.ts new file mode 100644 index 00000000..c8eea569 --- /dev/null +++ b/src/views/mod-tools/views/user/user-chatlog/ModToolsUserChatlogView.types.ts @@ -0,0 +1,5 @@ +export interface ModToolsUserChatlogViewProps +{ + userId: number; + onCloseClick: () => void; +} 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..ddc24350 --- /dev/null +++ b/src/views/mod-tools/views/user/user-info/ModToolsUserView.scss @@ -0,0 +1,23 @@ +.nitro-mod-tools-user { + width: 350px; + height: 370px; + + .username { + color: #1E7295; + text-decoration: underline; + } + + .table { + color: $black; + + > :not(caption) > * > * { + box-shadow: none; + border-bottom: 1px solid rgba(0, 0, 0, .2); + } + + &.table-striped > tbody > tr:nth-of-type(odd) { + color: $black; + background: rgba(0, 0, 0, .05); + } + } +} 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..24f8c876 --- /dev/null +++ b/src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx @@ -0,0 +1,153 @@ +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'; +import { CreateMessageHook, dispatchUiEvent, SendMessageHook } from '../../../../../hooks'; +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 } = props; + const [ userInfo, setUserInfo ] = useState(null); + const [ sendMessageVisible, setSendMessageVisible ] = useState(false); + const [ modActionVisible, setModActionVisible ] = useState(false); + const [ roomVisitsVisible, setRoomVisitsVisible ] = useState(false); + + useEffect(() => + { + SendMessageHook(new GetModeratorUserInfoMessageComposer(userId)); + }, [ userId ]); + + const onModtoolUserInfoEvent = useCallback((event: ModeratorUserInfoEvent) => + { + const parser = event.getParser(); + + if(!parser || parser.data.userId !== userId) return; + + setUserInfo(parser.data); + }, [setUserInfo, userId]); + + CreateMessageHook(ModeratorUserInfoEvent, onModtoolUserInfoEvent); + + const userProperties = useMemo(() => + { + if(!userInfo) return null; + + return [ + { + localeKey: 'modtools.userinfo.userName', + value: userInfo.userName, + showOnline: true + }, + { + localeKey: 'modtools.userinfo.cfhCount', + value: userInfo.cfhCount.toString() + }, + { + localeKey: 'modtools.userinfo.abusiveCfhCount', + value: userInfo.abusiveCfhCount.toString() + }, + { + localeKey: 'modtools.userinfo.cautionCount', + value: userInfo.cautionCount.toString() + }, + { + localeKey: 'modtools.userinfo.banCount', + value: userInfo.banCount.toString() + }, + { + localeKey: 'modtools.userinfo.lastSanctionTime', + value: userInfo.lastSanctionTime + }, + { + localeKey: 'modtools.userinfo.tradingLockCount', + value: userInfo.tradingLockCount.toString() + }, + { + localeKey: 'modtools.userinfo.tradingExpiryDate', + value: userInfo.tradingExpiryDate + }, + { + localeKey: 'modtools.userinfo.minutesSinceLastLogin', + value: FriendlyTime.format(userInfo.minutesSinceLastLogin * 60, '.ago', 2) + }, + { + localeKey: 'modtools.userinfo.lastPurchaseDate', + value: userInfo.lastPurchaseDate + }, + { + localeKey: 'modtools.userinfo.primaryEmailAddress', + value: userInfo.primaryEmailAddress + }, + { + localeKey: 'modtools.userinfo.identityRelatedBanCount', + value: userInfo.identityRelatedBanCount.toString() + }, + { + localeKey: 'modtools.userinfo.registrationAgeInMinutes', + value: FriendlyTime.format(userInfo.registrationAgeInMinutes * 60, '.ago', 2) + }, + { + localeKey: 'modtools.userinfo.userClassification', + value: userInfo.userClassification + } + ]; + }, [ userInfo ]); + + if(!userInfo) return null; + + return ( + <> + + onCloseClick() } /> + + + + + + { userProperties.map( (property, index) => + { + + return ( + + + + + ); + }) } + +
{ LocalizeText(property.localeKey) } + { property.value } + { property.showOnline && } +
+
+ + 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 new file mode 100644 index 00000000..534d647d --- /dev/null +++ b/src/views/mod-tools/views/user/user-info/ModToolsUserView.types.ts @@ -0,0 +1,5 @@ +export interface ModToolsUserViewProps +{ + userId: number; + onCloseClick: () => void; +} 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..09454bdc --- /dev/null +++ b/src/views/mod-tools/views/user/user-mod-action/ModToolsUserModActionView.tsx @@ -0,0 +1,201 @@ +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.topics) + { + values.push(topic) + } + } + + return values; + }, [cfhCategories]); + + const sendDefaultSanction = useCallback(() => + { + SendMessageHook(new DefaultSanctionMessageComposer(user.userId, selectedTopic, message)); + onCloseClick(); + }, [message, onCloseClick, selectedTopic, user.userId]); + + const sendSanction = useCallback(() => + { + if( (selectedTopic === -1) || (selectedAction === -1) ) + { + dispatchUiEvent(new NotificationAlertEvent(['You must select a CFH topic and Sanction'], 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; + } + + if(!sanction) + { + dispatchUiEvent(new NotificationAlertEvent(['You must select a sanction'], null, null, null, 'Error', null)); + return; + } + + const messageOrDefault = message.trim().length === 0 ? LocalizeText('help.cfh.topic.' + category.id) : message; + + 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(); + }, [message, onCloseClick, selectedAction, selectedTopic, settings, topics, user.userId]); + + return ( + + onCloseClick() } /> + + { user && + <> +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ } +
+
+ ); +} 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); diff --git a/src/views/navigator/NavigatorView.tsx b/src/views/navigator/NavigatorView.tsx index 892fa2ae..d26d9147 100644 --- a/src/views/navigator/NavigatorView.tsx +++ b/src/views/navigator/NavigatorView.tsx @@ -27,7 +27,7 @@ export const NavigatorView: FC = props => const [ isRoomLinkOpen, setRoomLinkOpen ] = useState(false); const [ pendingDoorState, setPendingDoorState ] = useState<{ roomData: RoomDataParser, state: string }>(null); const [ navigatorState, dispatchNavigatorState ] = useReducer(NavigatorReducer, initialNavigator); - const { needsNavigatorUpdate = false, topLevelContext = null, topLevelContexts = null } = navigatorState; + const { needsNavigatorUpdate = false, topLevelContext = null, topLevelContexts = null, homeRoomId } = navigatorState; const onNavigatorEvent = useCallback((event: NavigatorEvent) => { @@ -119,6 +119,13 @@ export const NavigatorView: FC = props => SendMessageHook(new NavigatorSearchComposer(contextCode, searchValue)); }, []); + const goToHomeRoom = useCallback(() => + { + if(homeRoomId <= 0) return; + + TryVisitRoom(homeRoomId); + }, [ homeRoomId ]); + const linkReceived = useCallback((url: string) => { const parts = url.split('/'); @@ -133,7 +140,7 @@ export const NavigatorView: FC = props => switch(parts[2]) { case 'home': - //goToHomeRoom(); + goToHomeRoom(); break; default: { const roomId = parseInt(parts[2]); @@ -147,8 +154,8 @@ export const NavigatorView: FC = props => setIsVisible(true); setCreatorOpen(true); return; - } - }, []); + } + }, [ goToHomeRoom ]); const closePendingDoorState = useCallback((state: string) => { diff --git a/src/views/navigator/views/room-info/NavigatorRoomInfoView.tsx b/src/views/navigator/views/room-info/NavigatorRoomInfoView.tsx index f46de29c..f0a9ab42 100644 --- a/src/views/navigator/views/room-info/NavigatorRoomInfoView.tsx +++ b/src/views/navigator/views/room-info/NavigatorRoomInfoView.tsx @@ -3,6 +3,7 @@ import classNames from 'classnames'; import { FC, useCallback, useEffect, useState } from 'react'; import { GetConfiguration, GetGroupInformation, GetSessionDataManager, LocalizeText } from '../../../../api'; import { NavigatorEvent } from '../../../../events'; +import { FloorplanEditorEvent } from '../../../../events/floorplan-editor/FloorplanEditorEvent'; import { RoomWidgetThumbnailEvent } from '../../../../events/room-widgets/thumbnail'; import { dispatchUiEvent } from '../../../../hooks/events'; import { SendMessageHook } from '../../../../hooks/messages'; @@ -98,6 +99,9 @@ export const NavigatorRoomInfoView: FC = props => setIsRoomMuted(value => !value); SendMessageHook(new RoomMuteComposer()); return; + case 'open_floorplan_editor': + dispatchUiEvent(new FloorplanEditorEvent(FloorplanEditorEvent.TOGGLE_FLOORPLAN_EDITOR)); + return; case 'close': onCloseClick(); return; @@ -155,7 +159,7 @@ export const NavigatorRoomInfoView: FC = props =>
{ hasPermission('settings') && <> - + } { hasPermission('staff_pick') && } 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 +
{ LocalizeText('notifications.text.club_gift') } diff --git a/src/views/notification-center/views/bubble-layouts/default/NotificationDefaultBubbleView.tsx b/src/views/notification-center/views/bubble-layouts/default/NotificationDefaultBubbleView.tsx index 3e70b32c..4b748636 100644 --- a/src/views/notification-center/views/bubble-layouts/default/NotificationDefaultBubbleView.tsx +++ b/src/views/notification-center/views/bubble-layouts/default/NotificationDefaultBubbleView.tsx @@ -7,7 +7,7 @@ export const NotificationDefaultBubbleView: FC + { (item.iconUrl && item.iconUrl.length) && } { item.message } diff --git a/src/views/purse/PurseView.tsx b/src/views/purse/PurseView.tsx index 7a2f8028..bfab90ea 100644 --- a/src/views/purse/PurseView.tsx +++ b/src/views/purse/PurseView.tsx @@ -1,6 +1,7 @@ import { FriendlyTime, HabboClubLevelEnum, UserCurrencyComposer, UserSubscriptionComposer } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { GetConfiguration, LocalizeText } from '../../api'; +import { HelpEvent } from '../../events/help/HelpEvent'; import { UserSettingsUIEvent } from '../../events/user-settings/UserSettingsUIEvent'; import { dispatchUiEvent } from '../../hooks'; import { SendMessageHook } from '../../hooks/messages/message-event'; @@ -24,6 +25,11 @@ export const PurseView: FC<{}> = props => dispatchUiEvent(new UserSettingsUIEvent(UserSettingsUIEvent.TOGGLE_USER_SETTINGS)); }, []); + const handleHelpCenterClick = useCallback(() => + { + dispatchUiEvent(new HelpEvent(HelpEvent.TOGGLE_HELP_CENTER)); + }, []); + const displayedCurrencies = useMemo(() => { return GetConfiguration('system.currency.types', []); @@ -140,7 +146,7 @@ export const PurseView: FC<{}> = props =>
-
+
diff --git a/src/views/room/RoomView.tsx b/src/views/room/RoomView.tsx index 9ab44e69..b885b48c 100644 --- a/src/views/room/RoomView.tsx +++ b/src/views/room/RoomView.tsx @@ -1,6 +1,6 @@ import { EventDispatcher, NitroRectangle, RoomGeometry, RoomVariableEnum, Vector3d } from '@nitrots/nitro-renderer'; import { FC, useEffect, useRef, useState } from 'react'; -import { DispatchMouseEvent, DispatchTouchEvent, DoorbellWidgetHandler, FurniChooserWidgetHandler, FurnitureContextMenuWidgetHandler, FurnitureCreditWidgetHandler, FurnitureCustomStackHeightWidgetHandler, FurnitureDimmerWidgetHandler, FurnitureExternalImageWidgetHandler, FurniturePresentWidgetHandler, GetNitroInstance, GetRoomEngine, InitializeRoomInstanceRenderingCanvas, IRoomWidgetHandlerManager, RoomWidgetAvatarInfoHandler, RoomWidgetChatHandler, RoomWidgetChatInputHandler, RoomWidgetHandlerManager, RoomWidgetInfostandHandler, RoomWidgetRoomToolsHandler, RoomWidgetUpdateRoomViewEvent, UserChooserWidgetHandler } from '../../api'; +import { DispatchMouseEvent, DispatchTouchEvent, DoorbellWidgetHandler, FurniChooserWidgetHandler, FurnitureContextMenuWidgetHandler, FurnitureCreditWidgetHandler, FurnitureCustomStackHeightWidgetHandler, FurnitureDimmerWidgetHandler, FurnitureExternalImageWidgetHandler, FurnitureMannequinWidgetHandler, FurniturePresentWidgetHandler, GetNitroInstance, GetRoomEngine, InitializeRoomInstanceRenderingCanvas, IRoomWidgetHandlerManager, RoomWidgetAvatarInfoHandler, RoomWidgetChatHandler, RoomWidgetChatInputHandler, RoomWidgetHandlerManager, RoomWidgetInfostandHandler, RoomWidgetRoomToolsHandler, RoomWidgetUpdateRoomViewEvent, UserChooserWidgetHandler } from '../../api'; import { FurnitureYoutubeDisplayWidgetHandler } from '../../api/nitro/room/widgets/handlers/FurnitureYoutubeDisplayWidgetHandler'; import { RoomContextProvider } from './context/RoomContext'; import { RoomColorView } from './RoomColorView'; @@ -46,13 +46,14 @@ export const RoomView: FC = props => widgetHandlerManager.registerHandler(new FurniturePresentWidgetHandler()); widgetHandlerManager.registerHandler(new FurnitureDimmerWidgetHandler()); widgetHandlerManager.registerHandler(new FurnitureYoutubeDisplayWidgetHandler()); + widgetHandlerManager.registerHandler(new FurnitureMannequinWidgetHandler()); setWidgetHandler(widgetHandlerManager); GetNitroInstance().renderer.resize(window.innerWidth, window.innerHeight); const canvasId = 1; - + const displayObject = GetRoomEngine().getRoomInstanceDisplay(roomSession.roomId, canvasId, GetNitroInstance().width, GetNitroInstance().height, RoomGeometry.SCALE_ZOOMED_IN); if(!displayObject) return; diff --git a/src/views/room/widgets/RoomWidgetsView.tsx b/src/views/room/widgets/RoomWidgetsView.tsx index c2c8922d..3fd67cdc 100644 --- a/src/views/room/widgets/RoomWidgetsView.tsx +++ b/src/views/room/widgets/RoomWidgetsView.tsx @@ -1,7 +1,9 @@ import { RoomEngineEvent, RoomEngineObjectEvent, RoomEngineRoomAdEvent, RoomEngineTriggerWidgetEvent, RoomEngineUseProductEvent, RoomId, RoomObjectCategory, RoomObjectOperationType, RoomObjectVariable, RoomSessionChatEvent, RoomSessionDanceEvent, RoomSessionDimmerPresetsEvent, RoomSessionDoorbellEvent, RoomSessionErrorMessageEvent, RoomSessionEvent, RoomSessionFriendRequestEvent, RoomSessionPetInfoUpdateEvent, RoomSessionPresentEvent, RoomSessionUserBadgesEvent, RoomZoomEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback } from 'react'; -import { CanManipulateFurniture, GetRoomEngine, GetSessionDataManager, IsFurnitureSelectionDisabled, LocalizeText, ProcessRoomObjectOperation, RoomWidgetFurniToWidgetMessage, RoomWidgetRoomEngineUpdateEvent, RoomWidgetRoomObjectUpdateEvent } from '../../../api'; +import { CanManipulateFurniture, GetRoomEngine, GetSessionDataManager, IsFurnitureSelectionDisabled, LocalizeText, ProcessRoomObjectOperation, RoomWidgetFurniToWidgetMessage, RoomWidgetUpdateRoomEngineEvent, RoomWidgetUpdateRoomObjectEvent } from '../../../api'; import { useRoomEngineEvent, useRoomSessionManagerEvent } from '../../../hooks/events'; +import { NotificationAlertType } from '../../notification-center/common/NotificationAlertType'; +import { NotificationUtilities } from '../../notification-center/common/NotificationUtilities'; import { useRoomContext } from '../context/RoomContext'; import { AvatarInfoWidgetView } from './avatar-info/AvatarInfoWidgetView'; import { ChatInputView } from './chat-input/ChatInputView'; @@ -25,10 +27,10 @@ export const RoomWidgetsView: FC = props => switch(event.type) { case RoomEngineEvent.NORMAL_MODE: - eventDispatcher.dispatchEvent(new RoomWidgetRoomEngineUpdateEvent(RoomWidgetRoomEngineUpdateEvent.NORMAL_MODE, event.roomId)); + eventDispatcher.dispatchEvent(new RoomWidgetUpdateRoomEngineEvent(RoomWidgetUpdateRoomEngineEvent.NORMAL_MODE, event.roomId)); return; case RoomEngineEvent.GAME_MODE: - eventDispatcher.dispatchEvent(new RoomWidgetRoomEngineUpdateEvent(RoomWidgetRoomEngineUpdateEvent.GAME_MODE, event.roomId)); + eventDispatcher.dispatchEvent(new RoomWidgetUpdateRoomEngineEvent(RoomWidgetUpdateRoomEngineEvent.GAME_MODE, event.roomId)); return; case RoomZoomEvent.ROOM_ZOOM: { const zoomEvent = (event as RoomZoomEvent); @@ -65,15 +67,15 @@ export const RoomWidgetsView: FC = props => const objectId = event.objectId; const category = event.category; - let updateEvent: RoomWidgetRoomObjectUpdateEvent = null; + let updateEvent: RoomWidgetUpdateRoomObjectEvent = null; switch(event.type) { case RoomEngineObjectEvent.SELECTED: - if(!IsFurnitureSelectionDisabled(event)) updateEvent = new RoomWidgetRoomObjectUpdateEvent(RoomWidgetRoomObjectUpdateEvent.OBJECT_SELECTED, objectId, category, event.roomId); + if(!IsFurnitureSelectionDisabled(event)) updateEvent = new RoomWidgetUpdateRoomObjectEvent(RoomWidgetUpdateRoomObjectEvent.OBJECT_SELECTED, objectId, category, event.roomId); break; case RoomEngineObjectEvent.DESELECTED: - updateEvent = new RoomWidgetRoomObjectUpdateEvent(RoomWidgetRoomObjectUpdateEvent.OBJECT_DESELECTED, objectId, category, event.roomId); + updateEvent = new RoomWidgetUpdateRoomObjectEvent(RoomWidgetUpdateRoomObjectEvent.OBJECT_DESELECTED, objectId, category, event.roomId); break; case RoomEngineObjectEvent.ADDED: { let addedEventType: string = null; @@ -82,14 +84,14 @@ export const RoomWidgetsView: FC = props => { case RoomObjectCategory.FLOOR: case RoomObjectCategory.WALL: - addedEventType = RoomWidgetRoomObjectUpdateEvent.FURNI_ADDED; + addedEventType = RoomWidgetUpdateRoomObjectEvent.FURNI_ADDED; break; case RoomObjectCategory.UNIT: - addedEventType = RoomWidgetRoomObjectUpdateEvent.USER_ADDED; + addedEventType = RoomWidgetUpdateRoomObjectEvent.USER_ADDED; break; } - if(addedEventType) updateEvent = new RoomWidgetRoomObjectUpdateEvent(addedEventType, objectId, category, event.roomId); + if(addedEventType) updateEvent = new RoomWidgetUpdateRoomObjectEvent(addedEventType, objectId, category, event.roomId); break; } case RoomEngineObjectEvent.REMOVED: { @@ -99,14 +101,14 @@ export const RoomWidgetsView: FC = props => { case RoomObjectCategory.FLOOR: case RoomObjectCategory.WALL: - removedEventType = RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED; + removedEventType = RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED; break; case RoomObjectCategory.UNIT: - removedEventType = RoomWidgetRoomObjectUpdateEvent.USER_REMOVED; + removedEventType = RoomWidgetUpdateRoomObjectEvent.USER_REMOVED; break; } - if(removedEventType) updateEvent = new RoomWidgetRoomObjectUpdateEvent(removedEventType, objectId, category, event.roomId); + if(removedEventType) updateEvent = new RoomWidgetUpdateRoomObjectEvent(removedEventType, objectId, category, event.roomId); break; } case RoomEngineObjectEvent.REQUEST_MOVE: @@ -116,13 +118,13 @@ export const RoomWidgetsView: FC = props => if(CanManipulateFurniture(roomSession, objectId, category)) ProcessRoomObjectOperation(objectId, category, RoomObjectOperationType.OBJECT_ROTATE_POSITIVE); break; case RoomEngineObjectEvent.REQUEST_MANIPULATION: - if(CanManipulateFurniture(roomSession, objectId, category)) updateEvent = new RoomWidgetRoomObjectUpdateEvent(RoomWidgetRoomObjectUpdateEvent.OBJECT_REQUEST_MANIPULATION, objectId, category, event.roomId); + if(CanManipulateFurniture(roomSession, objectId, category)) updateEvent = new RoomWidgetUpdateRoomObjectEvent(RoomWidgetUpdateRoomObjectEvent.OBJECT_REQUEST_MANIPULATION, objectId, category, event.roomId); break; case RoomEngineObjectEvent.MOUSE_ENTER: - updateEvent = new RoomWidgetRoomObjectUpdateEvent(RoomWidgetRoomObjectUpdateEvent.OBJECT_ROLL_OVER, objectId, category, event.roomId); + updateEvent = new RoomWidgetUpdateRoomObjectEvent(RoomWidgetUpdateRoomObjectEvent.OBJECT_ROLL_OVER, objectId, category, event.roomId); break; case RoomEngineObjectEvent.MOUSE_LEAVE: - updateEvent = new RoomWidgetRoomObjectUpdateEvent(RoomWidgetRoomObjectUpdateEvent.OBJECT_ROLL_OUT, objectId, category, event.roomId); + updateEvent = new RoomWidgetUpdateRoomObjectEvent(RoomWidgetUpdateRoomObjectEvent.OBJECT_ROLL_OUT, objectId, category, event.roomId); break; case RoomEngineTriggerWidgetEvent.REQUEST_CREDITFURNI: widgetHandler.processWidgetMessage(new RoomWidgetFurniToWidgetMessage(RoomWidgetFurniToWidgetMessage.REQUEST_CREDITFURNI, objectId, category, event.roomId)); @@ -200,7 +202,7 @@ export const RoomWidgetsView: FC = props => { let dispatchEvent = true; - if(updateEvent instanceof RoomWidgetRoomObjectUpdateEvent) dispatchEvent = (!RoomId.isRoomPreviewerId(updateEvent.roomId)); + if(updateEvent instanceof RoomWidgetUpdateRoomObjectEvent) dispatchEvent = (!RoomId.isRoomPreviewerId(updateEvent.roomId)); if(dispatchEvent) widgetHandler.eventDispatcher.dispatchEvent(updateEvent); } @@ -316,6 +318,8 @@ export const RoomWidgetsView: FC = props => default: return; } + + NotificationUtilities.simpleAlert(errorMessage, NotificationAlertType.DEFAULT, null, null, errorTitle); }, []); useRoomSessionManagerEvent(RoomSessionErrorMessageEvent.RSEME_KICKED, onRoomSessionErrorMessageEvent); diff --git a/src/views/room/widgets/avatar-info/AvatarInfoWidgetView.tsx b/src/views/room/widgets/avatar-info/AvatarInfoWidgetView.tsx index a12d17bc..7014b2f7 100644 --- a/src/views/room/widgets/avatar-info/AvatarInfoWidgetView.tsx +++ b/src/views/room/widgets/avatar-info/AvatarInfoWidgetView.tsx @@ -1,6 +1,6 @@ import { RoomEnterEffect, RoomObjectCategory } from '@nitrots/nitro-renderer'; import { FC, useCallback, useMemo, useState } from 'react'; -import { GetRoomSession, GetSessionDataManager, RoomWidgetObjectNameEvent, RoomWidgetRoomEngineUpdateEvent, RoomWidgetRoomObjectMessage, RoomWidgetRoomObjectUpdateEvent, RoomWidgetUpdateDanceStatusEvent, RoomWidgetUpdateDecorateModeEvent, RoomWidgetUpdateInfostandEvent, RoomWidgetUpdateInfostandFurniEvent, RoomWidgetUpdateInfostandPetEvent, RoomWidgetUpdateInfostandRentableBotEvent, RoomWidgetUpdateInfostandUserEvent, RoomWidgetUpdateRentableBotChatEvent, RoomWidgetUseProductBubbleEvent, UseProductItem } from '../../../../api'; +import { GetRoomSession, GetSessionDataManager, RoomWidgetObjectNameEvent, RoomWidgetRoomObjectMessage, RoomWidgetUpdateDanceStatusEvent, RoomWidgetUpdateDecorateModeEvent, RoomWidgetUpdateInfostandEvent, RoomWidgetUpdateInfostandFurniEvent, RoomWidgetUpdateInfostandPetEvent, RoomWidgetUpdateInfostandRentableBotEvent, RoomWidgetUpdateInfostandUserEvent, RoomWidgetUpdateRentableBotChatEvent, RoomWidgetUpdateRoomEngineEvent, RoomWidgetUpdateRoomObjectEvent, RoomWidgetUseProductBubbleEvent, UseProductItem } from '../../../../api'; import { CreateEventDispatcherHook } from '../../../../hooks/events/event-dispatcher.base'; import { useRoomContext } from '../../context/RoomContext'; import { AvatarInfoWidgetAvatarView } from './views/avatar/AvatarInfoWidgetAvatarView'; @@ -73,25 +73,25 @@ export const AvatarInfoWidgetView: FC<{}> = props => setProductBubbles([]); }, []); - const onRoomWidgetRoomEngineUpdateEvent = useCallback((event: RoomWidgetRoomEngineUpdateEvent) => + const onRoomWidgetRoomEngineUpdateEvent = useCallback((event: RoomWidgetUpdateRoomEngineEvent) => { switch(event.type) { - case RoomWidgetRoomEngineUpdateEvent.NORMAL_MODE: { + case RoomWidgetUpdateRoomEngineEvent.NORMAL_MODE: { if(isGameMode) setGameMode(false); return; } - case RoomWidgetRoomEngineUpdateEvent.GAME_MODE: { + case RoomWidgetUpdateRoomEngineEvent.GAME_MODE: { if(!isGameMode) setGameMode(true); return; } } }, [ isGameMode ]); - CreateEventDispatcherHook(RoomWidgetRoomEngineUpdateEvent.NORMAL_MODE, eventDispatcher, onRoomWidgetRoomEngineUpdateEvent); - CreateEventDispatcherHook(RoomWidgetRoomEngineUpdateEvent.GAME_MODE, eventDispatcher, onRoomWidgetRoomEngineUpdateEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomEngineEvent.NORMAL_MODE, eventDispatcher, onRoomWidgetRoomEngineUpdateEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomEngineEvent.GAME_MODE, eventDispatcher, onRoomWidgetRoomEngineUpdateEvent); - const onRoomObjectRemoved = useCallback((event: RoomWidgetRoomObjectUpdateEvent) => + const onRoomObjectRemoved = useCallback((event: RoomWidgetUpdateRoomObjectEvent) => { if(name) { @@ -152,15 +152,15 @@ export const AvatarInfoWidgetView: FC<{}> = props => } }, [ name, infoStandEvent, nameBubbles, productBubbles, removeNameBubble, clearInfoStandEvent ]); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.USER_REMOVED, eventDispatcher, onRoomObjectRemoved); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED, eventDispatcher, onRoomObjectRemoved); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.USER_REMOVED, eventDispatcher, onRoomObjectRemoved); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED, eventDispatcher, onRoomObjectRemoved); - const onObjectRolled = useCallback((event: RoomWidgetRoomObjectUpdateEvent) => + const onObjectRolled = useCallback((event: RoomWidgetUpdateRoomObjectEvent) => { switch(event.type) { - case RoomWidgetRoomObjectUpdateEvent.OBJECT_ROLL_OVER: { - const roomObjectEvent = (event as RoomWidgetRoomObjectUpdateEvent); + case RoomWidgetUpdateRoomObjectEvent.OBJECT_ROLL_OVER: { + const roomObjectEvent = (event as RoomWidgetUpdateRoomObjectEvent); if(infoStandEvent) return; @@ -168,8 +168,8 @@ export const AvatarInfoWidgetView: FC<{}> = props => return; } - case RoomWidgetRoomObjectUpdateEvent.OBJECT_ROLL_OUT: { - const roomObjectEvent = (event as RoomWidgetRoomObjectUpdateEvent); + case RoomWidgetUpdateRoomObjectEvent.OBJECT_ROLL_OUT: { + const roomObjectEvent = (event as RoomWidgetUpdateRoomObjectEvent); if(!name || (name.roomIndex !== roomObjectEvent.id)) return; @@ -180,16 +180,16 @@ export const AvatarInfoWidgetView: FC<{}> = props => } }, [ infoStandEvent, name, widgetHandler ]); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.OBJECT_ROLL_OVER, eventDispatcher, onObjectRolled); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.OBJECT_ROLL_OUT, eventDispatcher, onObjectRolled); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.OBJECT_ROLL_OVER, eventDispatcher, onObjectRolled); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.OBJECT_ROLL_OUT, eventDispatcher, onObjectRolled); - const onObjectDeselected = useCallback((event: RoomWidgetRoomObjectUpdateEvent) => + const onObjectDeselected = useCallback((event: RoomWidgetUpdateRoomObjectEvent) => { if(infoStandEvent) clearInfoStandEvent(); if(productBubbles.length) setProductBubbles([]); }, [ infoStandEvent, productBubbles, clearInfoStandEvent ]); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.OBJECT_DESELECTED, eventDispatcher, onObjectDeselected); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.OBJECT_DESELECTED, eventDispatcher, onObjectDeselected); const onRoomWidgetObjectNameEvent = useCallback((event: RoomWidgetObjectNameEvent) => { diff --git a/src/views/room/widgets/avatar-info/views/own-avatar/AvatarInfoWidgetOwnAvatarView.tsx b/src/views/room/widgets/avatar-info/views/own-avatar/AvatarInfoWidgetOwnAvatarView.tsx index 1fa8c8e0..e20da225 100644 --- a/src/views/room/widgets/avatar-info/views/own-avatar/AvatarInfoWidgetOwnAvatarView.tsx +++ b/src/views/room/widgets/avatar-info/views/own-avatar/AvatarInfoWidgetOwnAvatarView.tsx @@ -1,5 +1,5 @@ -import { AvatarAction, AvatarExpressionEnum, RoomObjectCategory, UserProfileComposer } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useState } from 'react'; +import { AvatarAction, AvatarExpressionEnum, RoomControllerLevel, RoomObjectCategory, UserProfileComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useMemo, useState } from 'react'; import { GetCanStandUp, GetCanUseExpression, GetOwnPosture, HasHabboClub, HasHabboVip, IsRidingHorse, LocalizeText, RoomWidgetAvatarExpressionMessage, RoomWidgetChangePostureMessage, RoomWidgetDanceMessage, RoomWidgetMessage, RoomWidgetUpdateDecorateModeEvent, RoomWidgetUserActionMessage } from '../../../../../../api'; import { AvatarEditorEvent } from '../../../../../../events'; import { dispatchUiEvent, SendMessageHook } from '../../../../../../hooks'; @@ -107,6 +107,11 @@ export const AvatarInfoWidgetOwnAvatarView: FC + { + return (userData.amIOwner || userData.amIAnyRoomController || (userData.roomControllerLevel > RoomControllerLevel.GUEST)); + }, [ userData ]); const isRidingHorse = IsRidingHorse(); @@ -121,9 +126,10 @@ export const AvatarInfoWidgetOwnAvatarView: FC processAction('change_name') }> { LocalizeText('widget.avatar.change_name') } } - processAction('decorate') }> - { LocalizeText('widget.avatar.decorate') } - + { isShowDecorate && + processAction('decorate') }> + { LocalizeText('widget.avatar.decorate') } + } processAction('change_looks') }> { LocalizeText('widget.memenu.myclothes') } diff --git a/src/views/room/widgets/chat-input/ChatInputView.scss b/src/views/room/widgets/chat-input/ChatInputView.scss index 60995c42..650a2e73 100644 --- a/src/views/room/widgets/chat-input/ChatInputView.scss +++ b/src/views/room/widgets/chat-input/ChatInputView.scss @@ -1,13 +1,3 @@ -@media only screen and (max-width: 600px) { - .nitro-chat-input { - position: fixed; - left: 50%; - transform: translateX(-50%); - bottom: 65px !important; - z-index: $chatinput-zindex; - } -} - .nitro-chat-input-container { display: flex; justify-content: center; @@ -17,9 +7,17 @@ border-radius: 8px; border: 2px solid rgb(0, 0, 0); background: #EDEDED; - padding-right: 30px; + padding-right: 10px; width: 100%; + @include media-breakpoint-down(sm) { + display: flex; + position: absolute; + bottom: 70px; + left: calc(100% / 3); + width: 200px; + } + &:before { content: ""; position: absolute; @@ -60,6 +58,44 @@ white-space: pre-wrap; } } + + .bubble-container { + visibility: visible; + width: 75%; + } } -@import './style-selector/ChatInputStyleSelectorView'; +.nitro-chat-style-selector-button { + position: absolute; + display: flex; + justify-content: center; + align-items: center; + height: 100%; + right: 7px; +} + +.nitro-chat-style-selector-container { + width: $chat-input-style-selector-widget-width; + max-height: $chat-input-style-selector-widget-height; + + .content-area { + max-height: $chat-input-style-selector-widget-height !important; + } + + .grid-item { + font-size: $font-size-sm; + height: 30px !important; + border-color: unset !important; + border: 0 !important; + padding: 1px 3px; + + &:not(.active) { + background-color: unset !important; + } + + .bubble-container { + visibility: visible; + width: 75%; + } + } +} diff --git a/src/views/room/widgets/chat-input/ChatInputView.tsx b/src/views/room/widgets/chat-input/ChatInputView.tsx index 534dd93c..ca2b54d7 100644 --- a/src/views/room/widgets/chat-input/ChatInputView.tsx +++ b/src/views/room/widgets/chat-input/ChatInputView.tsx @@ -1,6 +1,7 @@ +import { HabboClubLevelEnum, RoomControllerLevel } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; -import { GetConfiguration, GetSessionDataManager, LocalizeText, RoomWidgetChatMessage, RoomWidgetChatTypingMessage, RoomWidgetRoomObjectUpdateEvent, RoomWidgetUpdateChatInputContentEvent, RoomWidgetUpdateInfostandUserEvent } from '../../../../api'; +import { GetConfiguration, GetSessionDataManager, LocalizeText, RoomWidgetChatMessage, RoomWidgetChatTypingMessage, RoomWidgetUpdateChatInputContentEvent, RoomWidgetUpdateInfostandUserEvent, RoomWidgetUpdateRoomObjectEvent } from '../../../../api'; import { CreateEventDispatcherHook } from '../../../../hooks/events'; import { useRoomContext } from '../../context/RoomContext'; import { ChatInputStyleSelectorView } from './style-selector/ChatInputStyleSelectorView'; @@ -117,6 +118,7 @@ export const ChatInputView: FC<{}> = props => { if(needsChatStyleUpdate) { + console.log('send') GetSessionDataManager().sendChatStyleUpdate(chatStyleId); setNeedsChatStyleUpdate(false); @@ -175,18 +177,12 @@ export const ChatInputView: FC<{}> = props => }, [ inputRef, chatModeIdWhisper, anotherInputHasFocus, setInputFocus, checkSpecialKeywordForInput, sendChatValue ]); - const onStyleSelected = useCallback((styleId: number) => - { - setChatStyleId(styleId); - setNeedsChatStyleUpdate(true); - }, []); - - const onRoomWidgetRoomObjectUpdateEvent = useCallback((event: RoomWidgetRoomObjectUpdateEvent) => + const onRoomWidgetRoomObjectUpdateEvent = useCallback((event: RoomWidgetUpdateRoomObjectEvent) => { setSelectedUsername(''); }, []); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.OBJECT_DESELECTED, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.OBJECT_DESELECTED, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); const onRoomWidgetUpdateInfostandUserEvent = useCallback((event: RoomWidgetUpdateInfostandUserEvent) => { @@ -210,6 +206,61 @@ export const ChatInputView: FC<{}> = props => CreateEventDispatcherHook(RoomWidgetUpdateChatInputContentEvent.CHAT_INPUT_CONTENT, eventDispatcher, onRoomWidgetChatInputContentUpdateEvent); + const selectChatStyleId = useCallback((styleId: number) => + { + setChatStyleId(styleId); + setNeedsChatStyleUpdate(true); + }, []); + + const chatStyleIds = useMemo(() => + { + let styleIds: number[] = []; + + const styles = GetConfiguration<{ styleId: number, minRank: number, isSystemStyle: boolean, isHcOnly: boolean, isAmbassadorOnly: boolean }[]>('chat.styles'); + + for(const style of styles) + { + if(!style) continue; + + if(style.minRank > 0) + { + if(GetSessionDataManager().hasSecurity(style.minRank)) styleIds.push(style.styleId); + + continue; + } + + if(style.isSystemStyle) + { + if(GetSessionDataManager().hasSecurity(RoomControllerLevel.MODERATOR)) + { + styleIds.push(style.styleId); + + continue; + } + } + + if(GetConfiguration('chat.styles.disabled').indexOf(style.styleId) >= 0) continue; + + if(style.isHcOnly && (GetSessionDataManager().clubLevel >= HabboClubLevelEnum.CLUB)) + { + styleIds.push(style.styleId); + + continue; + } + + if(style.isAmbassadorOnly && GetSessionDataManager().isAmbassador) + { + styleIds.push(style.styleId); + + continue; + } + + if(!style.isHcOnly && !style.isAmbassadorOnly) styleIds.push(style.styleId); + } + + return styleIds; + }, []); + useEffect(() => { if(isTyping) @@ -273,7 +324,7 @@ export const ChatInputView: FC<{}> = props =>
updateChatInput(event.target.value) } onMouseDown={ event => setInputFocus() } />
- +
, document.getElementById('toolbar-chat-input-container')) ); } diff --git a/src/views/room/widgets/chat-input/style-selector/ChatInputStyleSelectorView.scss b/src/views/room/widgets/chat-input/style-selector/ChatInputStyleSelectorView.scss deleted file mode 100644 index 5be93b7c..00000000 --- a/src/views/room/widgets/chat-input/style-selector/ChatInputStyleSelectorView.scss +++ /dev/null @@ -1,61 +0,0 @@ -.nitro-chat-style-selector-button { - position: absolute; - display: flex; - justify-content: center; - align-items: center; - height: 100%; - right: 7px; -} - -.nitro-chat-style-selector-test { - display: flex; - position: relative; - right: 30px; - pointer-events: all; - height: 100%; - - i.icon { - cursor: pointer; - align-self: center; - } - - .nitro-chatstyle-selector { - position: absolute; - width: 250px; - top: -4px; - transition: transform 0.22s ease-in-out; - transform: translate(-81px, -50%) scale(0); - - &.active { - visibility: visible; - transform: translate(-160px, -100%) scale(1); - } - - .grid-container { - - .grid-items { - margin-top: -7px; - - .item-detail { - height: 30px; - max-height: 30px; - width: calc(1 / 3 * 100% - (1 - 1 / 3) * 7px); - margin: 7px 7px 0 0; - overflow: visible; - - &:hover { - cursor: pointer; - } - - .detail-info { - - .bubble-container { - visibility: visible; - width: 75%; - } - } - } - } - } - } -} diff --git a/src/views/room/widgets/chat-input/style-selector/ChatInputStyleSelectorView.tsx b/src/views/room/widgets/chat-input/style-selector/ChatInputStyleSelectorView.tsx index 52d1d43f..04bcb68f 100644 --- a/src/views/room/widgets/chat-input/style-selector/ChatInputStyleSelectorView.tsx +++ b/src/views/room/widgets/chat-input/style-selector/ChatInputStyleSelectorView.tsx @@ -1,20 +1,69 @@ -import { FC, useState } from 'react'; +import { FC, MouseEvent, useCallback, useEffect, useState } from 'react'; +import { Overlay, Popover } from 'react-bootstrap'; +import { BatchUpdates } from '../../../../../hooks'; +import { NitroCardContentView, NitroCardGridItemView, NitroCardGridView } from '../../../../../layout'; import { ChatInputStyleSelectorViewProps } from './ChatInputStyleSelectorView.types'; export const ChatInputStyleSelectorView: FC = props => { - const { onStyleSelected = null } = props; + const { chatStyleId = 0, chatStyleIds = null, selectChatStyleId = null } = props; + const [ target, setTarget ] = useState<(EventTarget & HTMLElement)>(null); const [ selectorVisible, setSelectorVisible ] = useState(false); + useEffect(() => + { + if(selectorVisible) return; + + setTarget(null); + }, [ selectorVisible ]); + + const selectStyle = (styleId: number) => + { + BatchUpdates(() => + { + selectChatStyleId(styleId); + setSelectorVisible(false); + }); + } + + const toggleSelector = useCallback((event: MouseEvent) => + { + BatchUpdates(() => + { + let visible = false; + + setSelectorVisible(prevValue => + { + visible = !prevValue; + + return visible; + }); + + if(visible) setTarget((event.target as (EventTarget & HTMLElement))); + }) + }, []); + return ( <> -
- -
- { selectorVisible && -
- -
} + + + + + + { chatStyleIds && (chatStyleIds.length > 0) && chatStyleIds.map((styleId) => + { + return ( + selectStyle(styleId) }> +
+
 
+
+
+ ); + }) } +
+
+
+
- ) + ); } diff --git a/src/views/room/widgets/chat-input/style-selector/ChatInputStyleSelectorView.types.ts b/src/views/room/widgets/chat-input/style-selector/ChatInputStyleSelectorView.types.ts index bd159ffe..9df829be 100644 --- a/src/views/room/widgets/chat-input/style-selector/ChatInputStyleSelectorView.types.ts +++ b/src/views/room/widgets/chat-input/style-selector/ChatInputStyleSelectorView.types.ts @@ -1,4 +1,6 @@ export interface ChatInputStyleSelectorViewProps { - onStyleSelected: (styleId: number) => void; + chatStyleId: number; + chatStyleIds: number[]; + selectChatStyleId: (styleId: number) => void; } diff --git a/src/views/room/widgets/chat/ChatWidgetView.scss b/src/views/room/widgets/chat/ChatWidgetView.scss index 67bb611f..53904e1d 100644 --- a/src/views/room/widgets/chat/ChatWidgetView.scss +++ b/src/views/room/widgets/chat/ChatWidgetView.scss @@ -11,6 +11,6 @@ border-radius: 0; box-shadow: none; pointer-events: none; - - @import './message/ChatWidgetMessageView'; } + +@import './message/ChatWidgetMessageView'; 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) => { 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 () + }} + +
); diff --git a/src/views/room/widgets/choosers/FurniChooserWidgetView.tsx b/src/views/room/widgets/choosers/FurniChooserWidgetView.tsx index ac257419..68a2971f 100644 --- a/src/views/room/widgets/choosers/FurniChooserWidgetView.tsx +++ b/src/views/room/widgets/choosers/FurniChooserWidgetView.tsx @@ -1,5 +1,5 @@ import { FC, useCallback, useState } from 'react'; -import { LocalizeText, RoomObjectItem, RoomWidgetChooserContentEvent, RoomWidgetRequestWidgetMessage, RoomWidgetRoomObjectUpdateEvent } from '../../../../api'; +import { LocalizeText, RoomObjectItem, RoomWidgetChooserContentEvent, RoomWidgetRequestWidgetMessage, RoomWidgetUpdateRoomObjectEvent } from '../../../../api'; import { CreateEventDispatcherHook } from '../../../../hooks'; import { useRoomContext } from '../../context/RoomContext'; import { ChooserWidgetView } from './ChooserWidgetView'; @@ -31,21 +31,21 @@ export const FurniChooserWidgetView: FC<{}> = props => CreateEventDispatcherHook(RoomWidgetChooserContentEvent.FURNI_CHOOSER_CONTENT, eventDispatcher, onRoomWidgetChooserContentEvent); - const onRoomWidgetRoomObjectUpdateEvent = useCallback((event: RoomWidgetRoomObjectUpdateEvent) => + const onRoomWidgetRoomObjectUpdateEvent = useCallback((event: RoomWidgetUpdateRoomObjectEvent) => { if(!isVisible) return; switch(event.type) { - case RoomWidgetRoomObjectUpdateEvent.FURNI_ADDED: - case RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED: + case RoomWidgetUpdateRoomObjectEvent.FURNI_ADDED: + case RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED: refreshChooser(); return; } }, [ isVisible, refreshChooser ]); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_ADDED, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.FURNI_ADDED, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); const close = useCallback(() => { diff --git a/src/views/room/widgets/choosers/UserChooserWidgetView.tsx b/src/views/room/widgets/choosers/UserChooserWidgetView.tsx index 327b7bd2..b9ab2460 100644 --- a/src/views/room/widgets/choosers/UserChooserWidgetView.tsx +++ b/src/views/room/widgets/choosers/UserChooserWidgetView.tsx @@ -1,5 +1,5 @@ import { FC, useCallback, useState } from 'react'; -import { LocalizeText, RoomObjectItem, RoomWidgetChooserContentEvent, RoomWidgetRequestWidgetMessage, RoomWidgetRoomObjectUpdateEvent } from '../../../../api'; +import { LocalizeText, RoomObjectItem, RoomWidgetChooserContentEvent, RoomWidgetRequestWidgetMessage, RoomWidgetUpdateRoomObjectEvent } from '../../../../api'; import { CreateEventDispatcherHook } from '../../../../hooks'; import { useRoomContext } from '../../context/RoomContext'; import { ChooserWidgetView } from './ChooserWidgetView'; @@ -31,21 +31,21 @@ export const UserChooserWidgetView: FC<{}> = props => CreateEventDispatcherHook(RoomWidgetChooserContentEvent.USER_CHOOSER_CONTENT, eventDispatcher, onRoomWidgetChooserContentEvent); - const onRoomWidgetRoomObjectUpdateEvent = useCallback((event: RoomWidgetRoomObjectUpdateEvent) => + const onRoomWidgetRoomObjectUpdateEvent = useCallback((event: RoomWidgetUpdateRoomObjectEvent) => { if(!isVisible) return; switch(event.type) { - case RoomWidgetRoomObjectUpdateEvent.USER_ADDED: - case RoomWidgetRoomObjectUpdateEvent.USER_REMOVED: + case RoomWidgetUpdateRoomObjectEvent.USER_ADDED: + case RoomWidgetUpdateRoomObjectEvent.USER_REMOVED: refreshChooser(); return; } }, [ isVisible, refreshChooser ]); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.USER_ADDED, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.USER_REMOVED, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.USER_ADDED, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.USER_REMOVED, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); const close = useCallback(() => { diff --git a/src/views/room/widgets/context-menu/ContextMenu.scss b/src/views/room/widgets/context-menu/ContextMenu.scss index 4df6199d..b9e313d1 100644 --- a/src/views/room/widgets/context-menu/ContextMenu.scss +++ b/src/views/room/widgets/context-menu/ContextMenu.scss @@ -7,6 +7,7 @@ border-radius: $border-radius; font-size: $font-size-sm; z-index: $context-menu-zindex; + pointer-events: all; &.name-only { background-color: rgba($black, 0.5); @@ -34,8 +35,7 @@ .menu-header { background-color: $william; color: $white; - width: 117px; - max-width: 117px; + min-width: 117px; height: 25px; max-height: 25px; font-size: 16px; diff --git a/src/views/room/widgets/furniture/FurnitureWidgets.scss b/src/views/room/widgets/furniture/FurnitureWidgets.scss index 49ef274a..55ebd35f 100644 --- a/src/views/room/widgets/furniture/FurnitureWidgets.scss +++ b/src/views/room/widgets/furniture/FurnitureWidgets.scss @@ -1,3 +1,9 @@ +.nitro-room-widgets { + pointer-events: none; +} + +@import './custom-stack-height/FurnitureCustomStackHeightView'; + @import './dimmer/FurnitureDimmerView'; @import './exchange-credit/FurnitureExchangeCreditView'; @import './external-image/FurnitureExternalImageView'; diff --git a/src/views/room/widgets/furniture/FurnitureWidgetsView.tsx b/src/views/room/widgets/furniture/FurnitureWidgetsView.tsx index eea155ff..553965d6 100644 --- a/src/views/room/widgets/furniture/FurnitureWidgetsView.tsx +++ b/src/views/room/widgets/furniture/FurnitureWidgetsView.tsx @@ -18,7 +18,7 @@ import { FurnitureYoutubeDisplayView } from './youtube-tv/FurnitureYoutubeDispla export const FurnitureWidgetsView: FC<{}> = props => { return ( -
+
diff --git a/src/views/room/widgets/furniture/background-color/FurnitureBackgroundColorView.tsx b/src/views/room/widgets/furniture/background-color/FurnitureBackgroundColorView.tsx index 7b2e335e..a29c5454 100644 --- a/src/views/room/widgets/furniture/background-color/FurnitureBackgroundColorView.tsx +++ b/src/views/room/widgets/furniture/background-color/FurnitureBackgroundColorView.tsx @@ -1,7 +1,7 @@ import { ApplyTonerComposer, RoomControllerLevel, RoomEngineObjectEvent, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useState } from 'react'; import ReactSlider from 'react-slider'; -import { GetRoomEngine, GetSessionDataManager, LocalizeText, RoomWidgetRoomObjectUpdateEvent, RoomWidgetUpdateBackgroundColorPreviewEvent } from '../../../../../api'; +import { GetRoomEngine, GetSessionDataManager, LocalizeText, RoomWidgetUpdateBackgroundColorPreviewEvent, RoomWidgetUpdateRoomObjectEvent } from '../../../../../api'; import { SendMessageHook } from '../../../../../hooks'; import { CreateEventDispatcherHook, useRoomEngineEvent } from '../../../../../hooks/events'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout'; @@ -48,7 +48,7 @@ export const FurnitureBackgroundColorView: FC<{}> = props => return; } - case RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED: { + case RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED: { if(objectId !== event.objectId) return; close(); @@ -58,7 +58,7 @@ export const FurnitureBackgroundColorView: FC<{}> = props => }, [ objectId, canOpenBackgroundToner, close ]); useRoomEngineEvent(RoomEngineTriggerWidgetEvent.REQUEST_BACKGROUND_COLOR, onRoomEngineObjectEvent); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED, eventDispatcher, onRoomEngineObjectEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED, eventDispatcher, onRoomEngineObjectEvent); const processAction = useCallback((name: string) => { diff --git a/src/views/room/widgets/furniture/badge-display/FurnitureBadgeDisplayView.tsx b/src/views/room/widgets/furniture/badge-display/FurnitureBadgeDisplayView.tsx index c9c5fbde..9615bce6 100644 --- a/src/views/room/widgets/furniture/badge-display/FurnitureBadgeDisplayView.tsx +++ b/src/views/room/widgets/furniture/badge-display/FurnitureBadgeDisplayView.tsx @@ -1,6 +1,6 @@ import { NitroEvent, RoomEngineTriggerWidgetEvent, StringDataType } from '@nitrots/nitro-renderer'; import { FC, useCallback, useState } from 'react'; -import { GetRoomEngine, LocalizeBadgeDescription, LocalizeBadgeName, RoomWidgetRoomObjectUpdateEvent } from '../../../../../api'; +import { GetRoomEngine, LocalizeBadgeDescription, LocalizeBadgeName, RoomWidgetUpdateRoomObjectEvent } from '../../../../../api'; import { CreateEventDispatcherHook } from '../../../../../hooks'; import { useRoomEngineEvent } from '../../../../../hooks/events/nitro/room/room-engine-event'; import { NitroLayoutTrophyView } from '../../../../../layout'; @@ -36,8 +36,8 @@ export const FurnitureBadgeDisplayView: FC<{}> = props => setTrophyData(new FurnitureTrophyData(widgetEvent.objectId, widgetEvent.category, '1', senderName, date, badgeDesc, badgeName)); return; } - case RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED: { - const widgetEvent = (event as RoomWidgetRoomObjectUpdateEvent); + case RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED: { + const widgetEvent = (event as RoomWidgetUpdateRoomObjectEvent); setTrophyData(prevState => { @@ -52,7 +52,7 @@ export const FurnitureBadgeDisplayView: FC<{}> = props => useRoomEngineEvent(RoomEngineTriggerWidgetEvent.REQUEST_BADGE_DISPLAY_ENGRAVING, onNitroEvent); useRoomEngineEvent(RoomEngineTriggerWidgetEvent.REQUEST_ACHIEVEMENT_RESOLUTION_ENGRAVING, onNitroEvent); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED, widgetHandler.eventDispatcher, onNitroEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED, widgetHandler.eventDispatcher, onNitroEvent); const processAction = useCallback((type: string, value: string = null) => { diff --git a/src/views/room/widgets/furniture/custom-stack-height/FurnitureCustomStackHeightView.scss b/src/views/room/widgets/furniture/custom-stack-height/FurnitureCustomStackHeightView.scss new file mode 100644 index 00000000..755c73d1 --- /dev/null +++ b/src/views/room/widgets/furniture/custom-stack-height/FurnitureCustomStackHeightView.scss @@ -0,0 +1,4 @@ +.nitro-widget-custom-stack-height { + width: $nitro-widget-custom-stack-height-width; + height: $nitro-widget-custom-stack-height-height; +} diff --git a/src/views/room/widgets/furniture/custom-stack-height/FurnitureCustomStackHeightView.tsx b/src/views/room/widgets/furniture/custom-stack-height/FurnitureCustomStackHeightView.tsx index 16f976a2..0b7c7ee1 100644 --- a/src/views/room/widgets/furniture/custom-stack-height/FurnitureCustomStackHeightView.tsx +++ b/src/views/room/widgets/furniture/custom-stack-height/FurnitureCustomStackHeightView.tsx @@ -2,9 +2,10 @@ import { FurnitureStackHeightComposer, FurnitureStackHeightEvent } from '@nitrot import { FC, useCallback, useEffect, useState } from 'react'; import ReactSlider from 'react-slider'; import { LocalizeText, RoomWidgetUpdateCustomStackHeightEvent } from '../../../../../api'; -import { CreateMessageHook, SendMessageHook } from '../../../../../hooks'; +import { BatchUpdates, CreateMessageHook, SendMessageHook } from '../../../../../hooks'; import { CreateEventDispatcherHook } from '../../../../../hooks/events'; -import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutButton, NitroLayoutFlex, NitroLayoutFlexColumn, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../layout'; +import { NitroLayoutBase } from '../../../../../layout/base'; import { useRoomContext } from '../../../context/RoomContext'; const MAX_HEIGHT: number = 40; @@ -14,13 +15,15 @@ export const FurnitureCustomStackHeightView: FC<{}> = props => const [ objectId, setObjectId ] = useState(-1); const [ height, setHeight ] = useState(0); const [ pendingHeight, setPendingHeight ] = useState(-1); - - const { roomSession = null, eventDispatcher = null } = useRoomContext(); + const { eventDispatcher = null } = useRoomContext(); const close = useCallback(() => { - setObjectId(-1); - setHeight(0); + BatchUpdates(() => + { + setObjectId(-1); + setHeight(0); + }); }, []); const updateHeight = useCallback((height: number, fromServer: boolean = false) => @@ -31,9 +34,12 @@ export const FurnitureCustomStackHeightView: FC<{}> = props => if(!fromServer) ((height > MAX_HEIGHT) && (height = MAX_HEIGHT)); - setHeight(parseFloat(height.toFixed(2))); + BatchUpdates(() => + { + setHeight(parseFloat(height.toFixed(2))); - if(!fromServer) setPendingHeight(height * 100); + if(!fromServer) setPendingHeight(height * 100); + }); }, []); const onRoomWidgetUpdateCustomStackHeightEvent = useCallback((event: RoomWidgetUpdateCustomStackHeightEvent) => @@ -65,16 +71,6 @@ export const FurnitureCustomStackHeightView: FC<{}> = props => SendMessageHook(new FurnitureStackHeightComposer(objectId, ~~(height))); }, [ objectId ]); - const placeAboveStack = useCallback(() => - { - sendUpdate(-100); - }, [ sendUpdate ]); - - const placeAtFloor = useCallback(() => - { - sendUpdate(0); - }, [ sendUpdate ]); - useEffect(() => { if((objectId === -1) || (pendingHeight === -1)) return; @@ -87,27 +83,35 @@ export const FurnitureCustomStackHeightView: FC<{}> = props => if(objectId === -1) return null; return ( - + -
- - updateHeight(event) } - renderThumb={ (props, state) =>
{ state.valueNow }
} /> -
-
- updateHeight(parseFloat(event.target.value)) } /> -
-
- - -
+ + + + { LocalizeText('widget.custom.stack.height.text') } + + + + updateHeight(event) } + renderThumb={ (props, state) =>
{ state.valueNow }
} /> + updateHeight(parseFloat(event.target.value)) } /> +
+ sendUpdate(-100) }> + { LocalizeText('furniture.above.stack') } + + sendUpdate(0) }> + { LocalizeText('furniture.floor.level') } + +
+
+
); diff --git a/src/views/room/widgets/furniture/exchange-credit/FurnitureExchangeCreditView.scss b/src/views/room/widgets/furniture/exchange-credit/FurnitureExchangeCreditView.scss index 34b1b665..4c286c5e 100644 --- a/src/views/room/widgets/furniture/exchange-credit/FurnitureExchangeCreditView.scss +++ b/src/views/room/widgets/furniture/exchange-credit/FurnitureExchangeCreditView.scss @@ -1,3 +1,10 @@ -.nitro-exchange-credit { - width: 290px; +.nitro-widget-exchange-credit { + width: $nitro-widget-exchange-credit-width; + height: $nitro-widget-exchange-credit-height; + + .exchange-image { + background-image: url('../../../../../assets/images/room-widgets/exchange-credit/exchange-credit-image.png'); + width: 103px; + height: 103px; + } } diff --git a/src/views/room/widgets/furniture/exchange-credit/FurnitureExchangeCreditView.tsx b/src/views/room/widgets/furniture/exchange-credit/FurnitureExchangeCreditView.tsx index e8f6387b..2ee5d497 100644 --- a/src/views/room/widgets/furniture/exchange-credit/FurnitureExchangeCreditView.tsx +++ b/src/views/room/widgets/furniture/exchange-credit/FurnitureExchangeCreditView.tsx @@ -2,7 +2,8 @@ import { FC, useCallback, useState } from 'react'; import { LocalizeText, RoomWidgetCreditFurniRedeemMessage, RoomWidgetUpdateCreditFurniEvent } from '../../../../../api'; import { BatchUpdates } from '../../../../../hooks'; import { CreateEventDispatcherHook } from '../../../../../hooks/events/event-dispatcher.base'; -import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutButton, NitroLayoutFlexColumn, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../layout'; +import { NitroLayoutBase } from '../../../../../layout/base'; import { useRoomContext } from '../../../context/RoomContext'; export const FurnitureExchangeCreditView: FC<{}> = props => @@ -19,35 +20,46 @@ export const FurnitureExchangeCreditView: FC<{}> = props => CreateEventDispatcherHook(RoomWidgetUpdateCreditFurniEvent.CREDIT_FURNI_UPDATE, eventDispatcher, onRoomWidgetUpdateCreditFurniEvent); - const processAction = useCallback((type: string, value: string = null) => + const close = useCallback(() => { - switch(type) + BatchUpdates(() => { - case 'close': - BatchUpdates(() => - { - setObjectId(-1); - setValue(0); - }); - return; - case 'redeem': - widgetHandler.processWidgetMessage(new RoomWidgetCreditFurniRedeemMessage(RoomWidgetCreditFurniRedeemMessage.REDEEM, objectId)); + setObjectId(-1); + setValue(0); + }); + }, []); - processAction('close'); - return; - } - }, [ widgetHandler, objectId ]); + const redeem = useCallback(() => + { + widgetHandler.processWidgetMessage(new RoomWidgetCreditFurniRedeemMessage(RoomWidgetCreditFurniRedeemMessage.REDEEM, objectId)); + + close(); + }, [ widgetHandler, objectId, close ]); if(objectId === -1) return null; return ( - - processAction('close') } /> + + -
- { LocalizeText('widgets.furniture.credit.redeem.value', [ 'value' ], [ value.toString() ]) } -
- + + + + + + + + { LocalizeText('creditfurni.description', [ 'credits' ], [ value.toString() ]) } + + + { LocalizeText('creditfurni.prompt') } + + + + { LocalizeText('catalog.redeem.dialog.button.exchange') } + + +
); diff --git a/src/views/room/widgets/furniture/friend-furni/FurnitureFriendFurniView.tsx b/src/views/room/widgets/furniture/friend-furni/FurnitureFriendFurniView.tsx index c8b5eb0b..b9c8163c 100644 --- a/src/views/room/widgets/furniture/friend-furni/FurnitureFriendFurniView.tsx +++ b/src/views/room/widgets/furniture/friend-furni/FurnitureFriendFurniView.tsx @@ -1,6 +1,6 @@ import { FriendFurniConfirmLockMessageComposer, LoveLockFurniFinishedEvent, LoveLockFurniFriendConfirmedEvent, LoveLockFurniStartEvent, NitroEvent, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from '@nitrots/nitro-renderer'; import { FC, useCallback, useState } from 'react'; -import { GetRoomEngine, GetRoomSession, LocalizeText, RoomWidgetRoomObjectUpdateEvent } from '../../../../../api'; +import { GetRoomEngine, GetRoomSession, LocalizeText, RoomWidgetUpdateRoomObjectEvent } from '../../../../../api'; import { CreateEventDispatcherHook } from '../../../../../hooks/events/event-dispatcher.base'; import { useRoomEngineEvent } from '../../../../../hooks/events/nitro/room/room-engine-event'; import { CreateMessageHook } from '../../../../../hooks/messages/message-event'; @@ -37,8 +37,8 @@ export const FurnitureFriendFurniView: FC<{}> = props => } return; } - case RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED: { - const widgetEvent = (event as RoomWidgetRoomObjectUpdateEvent); + case RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED: { + const widgetEvent = (event as RoomWidgetUpdateRoomObjectEvent); setEngravingLockData(prevState => { @@ -52,7 +52,7 @@ export const FurnitureFriendFurniView: FC<{}> = props => }; useRoomEngineEvent(RoomEngineTriggerWidgetEvent.REQUEST_FRIEND_FURNITURE_ENGRAVING, onNitroEvent); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED, eventDispatcher, onNitroEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED, eventDispatcher, onNitroEvent); const onLoveLockFurniStartEvent = useCallback((event: LoveLockFurniStartEvent) => { diff --git a/src/views/room/widgets/furniture/gift-opening/FurnitureGiftOpeningView.tsx b/src/views/room/widgets/furniture/gift-opening/FurnitureGiftOpeningView.tsx index f0aabaf5..3348cada 100644 --- a/src/views/room/widgets/furniture/gift-opening/FurnitureGiftOpeningView.tsx +++ b/src/views/room/widgets/furniture/gift-opening/FurnitureGiftOpeningView.tsx @@ -1,8 +1,10 @@ import { RoomObjectCategory, RoomObjectOperationType } from '@nitrots/nitro-renderer'; import { FC, useCallback, useMemo, useState } from 'react'; -import { CreateLinkEvent, GetRoomEngine, GetSessionDataManager, LocalizeText, RoomWidgetPresentOpenMessage, RoomWidgetRoomObjectUpdateEvent, RoomWidgetUpdatePresentDataEvent } from '../../../../../api'; +import { CreateLinkEvent, GetRoomEngine, GetSessionDataManager, LocalizeText, RoomWidgetPresentOpenMessage, RoomWidgetUpdatePresentDataEvent, RoomWidgetUpdateRoomObjectEvent } from '../../../../../api'; +import { BatchUpdates } from '../../../../../hooks'; import { CreateEventDispatcherHook } from '../../../../../hooks/events/event-dispatcher.base'; -import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutGiftCardView } from '../../../../../layout'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutButton, NitroLayoutFlex, NitroLayoutFlexColumn, NitroLayoutGiftCardView, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../layout'; +import { NitroLayoutBase } from '../../../../../layout/base'; import { ProductTypeEnum } from '../../../../catalog/common/ProductTypeEnum'; import { useRoomContext } from '../../../context/RoomContext'; @@ -10,6 +12,11 @@ const FLOOR: string = 'floor'; const WALLPAPER: string = 'wallpaper'; const LANDSCAPE: string = 'landscape'; +const ACTION_GIVE_GIFT = 0; +const ACTION_OPEN = 1; +const ACTION_PLACE = 2; +const ACTION_INVENTORY = 3; + export const FurnitureGiftOpeningView: FC<{}> = props => { const [ objectId, setObjectId ] = useState(-1); @@ -44,57 +51,67 @@ export const FurnitureGiftOpeningView: FC<{}> = props => switch(event.type) { case RoomWidgetUpdatePresentDataEvent.PACKAGEINFO: { - setOpenRequested(false); - setObjectId(event.objectId); - setText(event.giftMessage); - setIsOwnerOfFurniture(event.isController); - setSenderName(event.purchaserName); - setSenderFigure(event.purchaserFigure); - setImageUrl(event.imageUrl); + BatchUpdates(() => + { + setOpenRequested(false); + setObjectId(event.objectId); + setText(event.giftMessage); + setIsOwnerOfFurniture(event.isController); + setSenderName(event.purchaserName); + setSenderFigure(event.purchaserFigure); + setImageUrl(event.imageUrl); + }); return; } case RoomWidgetUpdatePresentDataEvent.CONTENTS_FLOOR: case RoomWidgetUpdatePresentDataEvent.CONTENTS_LANDSCAPE: case RoomWidgetUpdatePresentDataEvent.CONTENTS_WALLPAPER: { - setObjectId(event.objectId); - setClassId(event.classId); - setItemType(event.itemType); - setText(event.giftMessage); - setIsOwnerOfFurniture(event.isController); - setPlacedItemId(event.placedItemId); - setPlacedItemType(event.placedItemType); - setPlacedInRoom(event.placedInRoom); - let imageType: string = null; if(event.type === RoomWidgetUpdatePresentDataEvent.CONTENTS_FLOOR) imageType = 'packagecard_icon_floor'; else if(event.type === RoomWidgetUpdatePresentDataEvent.CONTENTS_LANDSCAPE) imageType = 'packagecard_icon_landscape'; else if(event.type === RoomWidgetUpdatePresentDataEvent.CONTENTS_WALLPAPER) imageType = 'packagecard_icon_wallpaper'; - setImageUrl(getGiftImageUrl(imageType)); + BatchUpdates(() => + { + setObjectId(event.objectId); + setClassId(event.classId); + setItemType(event.itemType); + setText(event.giftMessage); + setIsOwnerOfFurniture(event.isController); + setPlacedItemId(event.placedItemId); + setPlacedItemType(event.placedItemType); + setPlacedInRoom(event.placedInRoom); + setImageUrl(getGiftImageUrl(imageType)); + }); return; } case RoomWidgetUpdatePresentDataEvent.CONTENTS_CLUB: { - setObjectId(event.objectId); - setClassId(event.classId); - setItemType(event.itemType); - setText(event.giftMessage); - setIsOwnerOfFurniture(event.isController); - setImageUrl(getGiftImageUrl('packagecard_icon_hc')); + BatchUpdates(() => + { + setObjectId(event.objectId); + setClassId(event.classId); + setItemType(event.itemType); + setText(event.giftMessage); + setIsOwnerOfFurniture(event.isController); + setImageUrl(getGiftImageUrl('packagecard_icon_hc')); + }); return; } case RoomWidgetUpdatePresentDataEvent.CONTENTS: { if(!openRequested) return; - setObjectId(event.objectId); - setClassId(event.classId); - setItemType(event.itemType); - setText(event.giftMessage); - setIsOwnerOfFurniture(event.isController); - setPlacedItemId(event.placedItemId); - setPlacedItemType(event.placedItemType); - setPlacedInRoom(event.placedInRoom); - + BatchUpdates(() => + { + setObjectId(event.objectId); + setClassId(event.classId); + setItemType(event.itemType); + setText(event.giftMessage); + setIsOwnerOfFurniture(event.isController); + setPlacedItemId(event.placedItemId); + setPlacedItemType(event.placedItemType); + setPlacedInRoom(event.placedInRoom); + }); return; } case RoomWidgetUpdatePresentDataEvent.CONTENTS_IMAGE: { @@ -113,7 +130,7 @@ export const FurnitureGiftOpeningView: FC<{}> = props => CreateEventDispatcherHook(RoomWidgetUpdatePresentDataEvent.CONTENTS_CLUB, eventDispatcher, onRoomWidgetUpdatePresentDataEvent); CreateEventDispatcherHook(RoomWidgetUpdatePresentDataEvent.CONTENTS_IMAGE, eventDispatcher, onRoomWidgetUpdatePresentDataEvent); - const onRoomWidgetRoomObjectUpdateEvent = useCallback((event: RoomWidgetRoomObjectUpdateEvent) => + const onRoomWidgetRoomObjectUpdateEvent = useCallback((event: RoomWidgetUpdateRoomObjectEvent) => { if(event.id === objectId) clearGift(); @@ -123,17 +140,20 @@ export const FurnitureGiftOpeningView: FC<{}> = props => } }, [ objectId, placedItemId, placedInRoom, clearGift ]); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); const close = useCallback(() => { - setObjectId(-1); - setOpenRequested(false); - setPlacedItemId(-1); - setPlacedInRoom(false); - setText(null); - setIsOwnerOfFurniture(false); - }, [ clearGift ]); + BatchUpdates(() => + { + setObjectId(-1); + setOpenRequested(false); + setPlacedItemId(-1); + setPlacedInRoom(false); + setText(null); + setIsOwnerOfFurniture(false); + }); + }, []); const isSpaces = useMemo(() => { @@ -152,26 +172,25 @@ export const FurnitureGiftOpeningView: FC<{}> = props => { if(objectId === -1) return ''; - if(isSpaces) - return 'widget.furni.present.spaces.message_opened'; + if(isSpaces) return 'widget.furni.present.spaces.message_opened'; return 'widget.furni.present.message_opened'; }, [ objectId, isSpaces ]); - const handleAction = useCallback((action: string) => + const handleAction = useCallback((action: number) => { switch(action) { - case 'give_gift': + case ACTION_GIVE_GIFT: CreateLinkEvent('catalog/open'); return; - case 'open': + case ACTION_OPEN: setOpenRequested(true); widgetHandler.processWidgetMessage(new RoomWidgetPresentOpenMessage(RoomWidgetPresentOpenMessage.OPEN_PRESENT, objectId)); return; - case 'room': + case ACTION_PLACE: return; - case 'inventory': + case ACTION_INVENTORY: if((placedItemId > 0) && placedInRoom) { if(placedItemType === ProductTypeEnum.PET) @@ -195,32 +214,48 @@ export const FurnitureGiftOpeningView: FC<{}> = props => return ( - + - { placedItemId === -1 && <> - - { isOwnerOfFurniture &&
- { senderName && } - -
} - } - { placedItemId !== -1 && <> -
-
- -
-
- { LocalizeText(productName, ['product'], [text]) } -
-
-
- - -
- { senderName && <> - - } - } + + { (placedItemId === -1) && + + + + + + { senderName && + handleAction(ACTION_GIVE_GIFT) }> + { LocalizeText('widget.furni.present.give_gift', [ 'name' ], [ senderName ]) } + } + handleAction(ACTION_OPEN) }> + { LocalizeText('widget.furni.present.open_gift') } + + + } + { (placedItemId > -1) && + + + + + { LocalizeText(productName, [ 'product' ], [ text ]) } + + + + + handleAction(ACTION_INVENTORY) }> + { LocalizeText('widget.furni.present.put_in_inventory') } + + handleAction(ACTION_PLACE) }> + { LocalizeText(placedInRoom ? 'widget.furni.present.keep_in_room' : 'widget.furni.present.place_in_room') } + + + { (senderName && senderName.length) && + handleAction(ACTION_GIVE_GIFT) }> + { LocalizeText('widget.furni.present.give_gift', [ 'name' ], [ senderName ]) } + } + + } +
); diff --git a/src/views/room/widgets/furniture/high-score/FurnitureHighScoreView.scss b/src/views/room/widgets/furniture/high-score/FurnitureHighScoreView.scss index 2635164f..a02f8b69 100644 --- a/src/views/room/widgets/furniture/high-score/FurnitureHighScoreView.scss +++ b/src/views/room/widgets/furniture/high-score/FurnitureHighScoreView.scss @@ -1,5 +1,6 @@ -.highscore-widget +.nitro-widget-high-score { - width: 150px; - max-width: 150px; + width: 250px; + max-width: 250px; + height: 200px; } diff --git a/src/views/room/widgets/furniture/high-score/FurnitureHighScoreView.tsx b/src/views/room/widgets/furniture/high-score/FurnitureHighScoreView.tsx index 3a322c20..981fe26a 100644 --- a/src/views/room/widgets/furniture/high-score/FurnitureHighScoreView.tsx +++ b/src/views/room/widgets/furniture/high-score/FurnitureHighScoreView.tsx @@ -2,13 +2,15 @@ import { HighScoreDataType, ObjectDataFactory, RoomEngineTriggerWidgetEvent, Roo import { FC, useCallback, useState } from 'react'; import { GetRoomEngine, LocalizeText } from '../../../../../api'; import { useRoomEngineEvent } from '../../../../../hooks'; +import { NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../layout'; +import { NitroLayoutBase } from '../../../../../layout/base'; import { useRoomContext } from '../../../context/RoomContext'; import { ContextMenuView } from '../../context-menu/ContextMenuView'; import { ContextMenuHeaderView } from '../../context-menu/views/header/ContextMenuHeaderView'; import { ContextMenuListView } from '../../context-menu/views/list/ContextMenuListView'; -const SCORE_TYPES = ['perteam', 'mostwins', 'classic']; -const CLEAR_TYPES = ['alltime', 'daily', 'weekly', 'monthly']; +const SCORE_TYPES = [ 'perteam', 'mostwins', 'classic' ]; +const CLEAR_TYPES = [ 'alltime', 'daily', 'weekly', 'monthly' ]; export const FurnitureHighScoreView: FC<{}> = props => { @@ -55,33 +57,48 @@ export const FurnitureHighScoreView: FC<{}> = props => if((objectId === -1) || !stuffData) return null; return ( - + { LocalizeText('high.score.display.caption', [ 'scoretype', 'cleartype' ], [LocalizeText(`high.score.display.scoretype.${ SCORE_TYPES[stuffData.scoreType] }`), LocalizeText(`high.score.display.cleartype.${ CLEAR_TYPES[stuffData.clearType] }`) ]) } -
-
{ LocalizeText('high.score.display.users.header') }
-
{ LocalizeText('high.score.display.score.header') }
-
-
-
-
- { stuffData.entries.map((entry, index) => - { - return
{entry.users.join()}
- }) - } -
-
-
+ + + + { LocalizeText('high.score.display.users.header') } + + + + + { LocalizeText('high.score.display.score.header') } + + + +
+ + { stuffData.entries.map((entry, index) => { - return
{entry.score}
+ return ( + + { entry.users.join(', ') } + + ); }) } -
-
+ + + { stuffData.entries.map((entry, index) => + { + return ( + + { entry.score } + + ); + }) + } + +
); diff --git a/src/views/room/widgets/furniture/manipulation-menu/FurnitureManipulationMenuView.tsx b/src/views/room/widgets/furniture/manipulation-menu/FurnitureManipulationMenuView.tsx index 33752c8f..5590392a 100644 --- a/src/views/room/widgets/furniture/manipulation-menu/FurnitureManipulationMenuView.tsx +++ b/src/views/room/widgets/furniture/manipulation-menu/FurnitureManipulationMenuView.tsx @@ -1,6 +1,6 @@ import { RoomObjectOperationType } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useState } from 'react'; -import { ProcessRoomObjectOperation, RoomWidgetRoomObjectUpdateEvent, RoomWidgetUpdateDecorateModeEvent } from '../../../../../api'; +import { ProcessRoomObjectOperation, RoomWidgetUpdateDecorateModeEvent, RoomWidgetUpdateRoomObjectEvent } from '../../../../../api'; import { BatchUpdates } from '../../../../../hooks'; import { CreateEventDispatcherHook } from '../../../../../hooks/events/event-dispatcher.base'; import { useRoomContext } from '../../../context/RoomContext'; @@ -28,11 +28,11 @@ export const FurnitureManipulationMenuView: FC<{}> = props => ProcessRoomObjectOperation(objectId, objectType, RoomObjectOperationType.OBJECT_PICKUP); }, [ objectId, objectType ]); - const onRoomWidgetRoomObjectUpdateEvent = useCallback((event: RoomWidgetRoomObjectUpdateEvent) => + const onRoomWidgetRoomObjectUpdateEvent = useCallback((event: RoomWidgetUpdateRoomObjectEvent) => { switch(event.type) { - case RoomWidgetRoomObjectUpdateEvent.OBJECT_REQUEST_MANIPULATION: { + case RoomWidgetUpdateRoomObjectEvent.OBJECT_REQUEST_MANIPULATION: { BatchUpdates(() => { setIsVisible(true); @@ -41,7 +41,7 @@ export const FurnitureManipulationMenuView: FC<{}> = props => }); return; } - case RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED: { + case RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED: { if(event.id === objectId) { BatchUpdates(() => @@ -53,7 +53,7 @@ export const FurnitureManipulationMenuView: FC<{}> = props => } return; } - case RoomWidgetRoomObjectUpdateEvent.OBJECT_DESELECTED: { + case RoomWidgetUpdateRoomObjectEvent.OBJECT_DESELECTED: { BatchUpdates(() => { setIsVisible(false); @@ -65,8 +65,8 @@ export const FurnitureManipulationMenuView: FC<{}> = props => } }, [ objectId ]); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.OBJECT_REQUEST_MANIPULATION, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.OBJECT_DESELECTED, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.OBJECT_REQUEST_MANIPULATION, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.OBJECT_DESELECTED, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent); const onRoomWidgetUpdateDecorateModeEvent = useCallback((event: RoomWidgetUpdateDecorateModeEvent) => { diff --git a/src/views/room/widgets/furniture/mannequin/FurnitureMannequinView.tsx b/src/views/room/widgets/furniture/mannequin/FurnitureMannequinView.tsx index 5e3a3972..b0ec3792 100644 --- a/src/views/room/widgets/furniture/mannequin/FurnitureMannequinView.tsx +++ b/src/views/room/widgets/furniture/mannequin/FurnitureMannequinView.tsx @@ -1,15 +1,26 @@ -import { AvatarFigurePartType, FurnitureMannequinSaveLookComposer, FurnitureMannequinSaveNameComposer, FurnitureMultiStateComposer, IAvatarFigureContainer, NitroEvent, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { AvatarFigurePartType, FurnitureMannequinSaveLookComposer, FurnitureMannequinSaveNameComposer, FurnitureMultiStateComposer, HabboClubLevelEnum, IAvatarFigureContainer, RoomControllerLevel } from '@nitrots/nitro-renderer'; import { FC, KeyboardEvent, useCallback, useEffect, useState } from 'react'; -import { GetAvatarRenderManager, GetNitroInstance, GetRoomEngine, GetRoomSession, GetSessionDataManager, LocalizeText, RoomWidgetRoomObjectUpdateEvent } from '../../../../../api'; +import { GetAvatarRenderManager, GetSessionDataManager, LocalizeText, RoomWidgetUpdateMannequinEvent } from '../../../../../api'; +import { BatchUpdates, SendMessageHook } from '../../../../../hooks'; import { CreateEventDispatcherHook } from '../../../../../hooks/events/event-dispatcher.base'; -import { useRoomEngineEvent } from '../../../../../hooks/events/nitro/room/room-engine-event'; -import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout'; -import { AvatarImageView } from '../../../../shared/avatar-image/AvatarImageView'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutButton, NitroLayoutFlex, NitroLayoutFlexColumn, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../layout'; +import { NitroLayoutBase } from '../../../../../layout/base'; import { useRoomContext } from '../../../context/RoomContext'; -import { MannequinViewMode } from './common/MannequinViewMode'; -import { FurnitureMannequinData } from './FurnitureMannequinData'; +import { FurnitureMannequinPreviewView } from './views/preview/FurnitureMannequinPreviewView'; -const parts = [ +const MODE_NONE: number = -1; +const MODE_CONTROLLER: number = 0; +const MODE_UPDATE: number = 1; +const MODE_PEER: number = 2; +const MODE_NO_CLUB: number = 3; +const MODE_WRONG_GENDER: number = 4; + +const ACTION_SET_NAME: number = 1; +const ACTION_WEAR: number = 2; +const ACTION_SAVE: number = 3; + +const MANNEQUIN_FIGURE = ['hd', 99999, [ 99998 ]]; +const MANNEQUIN_CLOTHING_PART_TYPES = [ AvatarFigurePartType.CHEST_ACCESSORY, AvatarFigurePartType.COAT_CHEST, AvatarFigurePartType.CHEST, @@ -17,197 +28,209 @@ const parts = [ AvatarFigurePartType.SHOES, AvatarFigurePartType.WAIST_ACCESSORY ]; -const baseAvatar = ['hd', 99999, 99998]; export const FurnitureMannequinView: FC<{}> = props => { - const { eventDispatcher = null } = useRoomContext(); - - const [ mannequinData, setMannequinData ] = useState(null); - const [ viewMode, setViewMode ] = useState(''); + const [ objectId, setObjectId ] = useState(-1); + const [ figure, setFigure ] = useState(null); + const [ gender, setGender ] = useState(null); + const [ name, setName ] = useState(null); + const [ clubLevel, setClubLevel ] = useState(HabboClubLevelEnum.NO_CLUB); + const [ renderedFigure, setRenderedFigure ] = useState(null); + const [ renderedClubLevel, setRenderedClubLevel ] = useState(HabboClubLevelEnum.NO_CLUB); + const [ mode, setMode ] = useState(MODE_NONE); + const { roomSession = null, eventDispatcher = null } = useRoomContext(); - const loadMannequinFigure = useCallback((figureContainer: IAvatarFigureContainer) => - { - for(const item of figureContainer.getPartTypeIds()) - { - if(parts.indexOf(item) === -1) - { - figureContainer.removePart(item); - } - } - - figureContainer.updatePart(baseAvatar[0].toString(), Number(baseAvatar[1]), [ Number(baseAvatar[2]) ]); - - setMannequinData(mannequinData => new FurnitureMannequinData(mannequinData.objectId, mannequinData.category, mannequinData.name, mannequinData.figure, mannequinData.gender, mannequinData.clubLevel, figureContainer.getFigureString())); - }, []); - - useEffect(() => + const onRoomWidgetUpdateMannequinEvent = useCallback((event: RoomWidgetUpdateMannequinEvent) => { - if(mannequinData && !mannequinData.renderedFigure) - { - const figureContainer = GetAvatarRenderManager().createFigureContainer(mannequinData.figure); - loadMannequinFigure(figureContainer); - } - }, [loadMannequinFigure, mannequinData]); + const figureContainer = GetAvatarRenderManager().createFigureContainer(event.figure); + const figureClubLevel = GetAvatarRenderManager().getFigureClubLevel(figureContainer, event.gender, MANNEQUIN_CLOTHING_PART_TYPES); - const loadViewMode = useCallback((mannequinData: FurnitureMannequinData) => - { - if(!mannequinData) return; - - const userCanEdit = (GetRoomSession().isRoomOwner || GetSessionDataManager().isModerator); - const userGender = GetNitroInstance().sessionDataManager.gender; - const userClubLevel = GetNitroInstance().sessionDataManager.clubLevel; - - if(userCanEdit) + BatchUpdates(() => { - setViewMode(MannequinViewMode.EDIT); - } - else - { - if(!mannequinData.figure || mannequinData.figure.length <= 1) return; + setObjectId(event.objectId); + setFigure(event.figure); + setGender(event.gender); + setName(event.name); + setClubLevel(figureClubLevel); - if(userGender.toUpperCase() !== mannequinData.gender.toUpperCase()) + if(roomSession.isRoomOwner || (roomSession.controllerLevel >= RoomControllerLevel.GUEST) || GetSessionDataManager().isModerator) { - setViewMode(MannequinViewMode.INCOMPATIBLE_GENDER); + setMode(MODE_CONTROLLER); } - else if(userClubLevel < mannequinData.clubLevel) + + else if(GetSessionDataManager().gender.toLowerCase() !== event.gender.toLowerCase()) { - setViewMode(MannequinViewMode.CLUB); + setMode(MODE_WRONG_GENDER); + } + + else if(GetSessionDataManager().clubLevel < figureClubLevel) + { + setMode(MODE_NO_CLUB); } else { - setViewMode(MannequinViewMode.DEFAULT); + setMode(MODE_PEER); } - } - }, []); + }); + }, [ roomSession ]); - const onNitroEvent = useCallback((event: NitroEvent) => + CreateEventDispatcherHook(RoomWidgetUpdateMannequinEvent.MANNEQUIN_UPDATE, eventDispatcher, onRoomWidgetUpdateMannequinEvent); + + const getMergedFigureContainer = (figure: string, targetFigure: string) => { - switch(event.type) + const figureContainer = GetAvatarRenderManager().createFigureContainer(figure); + const targetFigureContainer = GetAvatarRenderManager().createFigureContainer(targetFigure); + + for(const part of MANNEQUIN_CLOTHING_PART_TYPES) figureContainer.removePart(part); + + for(const part of targetFigureContainer.getPartTypeIds()) { - case RoomEngineTriggerWidgetEvent.REQUEST_MANNEQUIN: { - const widgetEvent = (event as RoomEngineTriggerWidgetEvent); - - const roomObject = GetRoomEngine().getRoomObject(widgetEvent.roomId, widgetEvent.objectId, widgetEvent.category); - - if(!roomObject) return; - - const figure = roomObject.model.getValue(RoomObjectVariable.FURNITURE_MANNEQUIN_FIGURE); - const gender = roomObject.model.getValue(RoomObjectVariable.FURNITURE_MANNEQUIN_GENDER); - const name = roomObject.model.getValue(RoomObjectVariable.FURNITURE_MANNEQUIN_NAME); - - const figureContainer = GetAvatarRenderManager().createFigureContainer(figure); - const clubLevel = GetAvatarRenderManager().getFigureClubLevel(figureContainer, gender, parts); - - const mannequinData = new FurnitureMannequinData(widgetEvent.objectId, widgetEvent.category, name, figure, gender, clubLevel); - - setMannequinData(mannequinData); - loadViewMode(mannequinData); - return; - } - case RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED: { - const widgetEvent = (event as RoomWidgetRoomObjectUpdateEvent); - - setMannequinData(prevState => - { - if(!prevState || (widgetEvent.id !== prevState.objectId) || (widgetEvent.category !== prevState.category)) return prevState; - - return null; - }); - return; - } + figureContainer.updatePart(part, targetFigureContainer.getPartSetId(part), targetFigureContainer.getPartColorIds(part)); } - }, [loadViewMode]); - useRoomEngineEvent(RoomEngineTriggerWidgetEvent.REQUEST_MANNEQUIN, onNitroEvent); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED, eventDispatcher, onNitroEvent); + return figureContainer; + } - const processAction = useCallback((type: string, value: string = null) => + const transformAsMannequinFigure = (figureContainer: IAvatarFigureContainer) => { - switch(type) + for(const part of figureContainer.getPartTypeIds()) { - case 'close': - setMannequinData(null); - return; - case 'set_name': - setMannequinData(mannequinData => new FurnitureMannequinData(mannequinData.objectId, mannequinData.category, value, mannequinData.figure, mannequinData.gender, mannequinData.clubLevel, mannequinData.renderedFigure)); - return; - case 'load_figure': - loadMannequinFigure(GetAvatarRenderManager().createFigureContainer(GetNitroInstance().sessionDataManager.figure)); - setViewMode(MannequinViewMode.SAVE); - return; - case 'back': - loadMannequinFigure(GetAvatarRenderManager().createFigureContainer(mannequinData.figure)); - setViewMode(MannequinViewMode.EDIT); - return; - case 'save_name': - GetRoomSession().connection.send(new FurnitureMannequinSaveNameComposer(mannequinData.objectId, mannequinData.name)); - return; - case 'save_figure': - GetRoomSession().connection.send(new FurnitureMannequinSaveLookComposer(mannequinData.objectId)); - processAction('save_name'); - processAction('close'); - return; - case 'wear': - GetRoomSession().connection.send(new FurnitureMultiStateComposer(mannequinData.objectId)); - processAction('close'); + if(MANNEQUIN_CLOTHING_PART_TYPES.indexOf(part) >= 0) continue; + + figureContainer.removePart(part); + } + + figureContainer.updatePart((MANNEQUIN_FIGURE[0] as string), (MANNEQUIN_FIGURE[1] as number), (MANNEQUIN_FIGURE[2] as number[])); + }; + + const processAction = useCallback((action: number, value: string = null) => + { + switch(action) + { + case ACTION_SAVE: + SendMessageHook(new FurnitureMannequinSaveLookComposer(objectId)); + break; + case ACTION_WEAR: + SendMessageHook(new FurnitureMultiStateComposer(objectId)); + break; + case ACTION_SET_NAME: + SendMessageHook(new FurnitureMannequinSaveNameComposer(objectId, name)); return; } - }, [ loadMannequinFigure, mannequinData ]); + + setMode(MODE_NONE); + }, [ objectId, name ]); const handleKeyDown = (event: KeyboardEvent) => { if(event.key !== 'Enter') return; - processAction('save_name'); + processAction(ACTION_SET_NAME); }; - if(!mannequinData) return null; + useEffect(() => + { + switch(mode) + { + case MODE_CONTROLLER: + case MODE_WRONG_GENDER: { + const figureContainer = GetAvatarRenderManager().createFigureContainer(figure); + + transformAsMannequinFigure(figureContainer); + + setRenderedFigure(figureContainer.getFigureString()); + setRenderedClubLevel(clubLevel); + break; + } + case MODE_UPDATE: { + const figureContainer = GetAvatarRenderManager().createFigureContainer(GetSessionDataManager().figure); + + transformAsMannequinFigure(figureContainer); + + setRenderedFigure(figureContainer.getFigureString()); + setRenderedClubLevel(GetAvatarRenderManager().getFigureClubLevel(figureContainer, GetSessionDataManager().gender, MANNEQUIN_CLOTHING_PART_TYPES)); + break; + } + case MODE_PEER: + case MODE_NO_CLUB: { + const figureContainer = getMergedFigureContainer(GetSessionDataManager().figure, figure); + + setRenderedFigure(figureContainer.getFigureString()); + setRenderedClubLevel(clubLevel); + break; + } + } + }, [ mode, figure, clubLevel ]); + + if(mode === MODE_NONE) return null; return ( - processAction('close') } /> + setMode(MODE_NONE) } /> -
-
-
- -
-
-
- { viewMode === MannequinViewMode.DEFAULT && + + + + + + { (mode === MODE_CONTROLLER) && <> -
-
{ mannequinData.name }
-
{ LocalizeText('mannequin.widget.weartext') }
-
-
processAction('wear') }>{ LocalizeText('mannequin.widget.wear') }
- } - { viewMode === MannequinViewMode.EDIT && - <> - processAction('set_name', event.target.value) } onKeyDown={ event => handleKeyDown(event) } /> -
-
processAction('load_figure') }>{ LocalizeText('mannequin.widget.style') }
-
processAction('wear') }>{ LocalizeText('mannequin.widget.wear') }
-
+ + setName(event.target.value) } onKeyDown={ event => handleKeyDown(event) } /> + + + setMode(MODE_UPDATE) }> + { LocalizeText('mannequin.widget.style') } + + processAction(ACTION_WEAR) }> + { LocalizeText('mannequin.widget.wear') } + + } - { viewMode === MannequinViewMode.SAVE && + { (mode === MODE_UPDATE) && <> -
-
{ mannequinData.name }
-
{ LocalizeText('mannequin.widget.savetext') }
-
-
-
processAction('back') }>{ LocalizeText('mannequin.widget.back') }
-
processAction('save_figure') }>{ LocalizeText('mannequin.widget.save') }
-
+ + + { name } + + + { LocalizeText('mannequin.widget.savetext') } + + + + setMode(MODE_CONTROLLER) }> + { LocalizeText('mannequin.widget.back') } + + processAction(ACTION_SAVE) }> + { LocalizeText('mannequin.widget.save') } + + } - { viewMode === MannequinViewMode.CLUB && -
{ LocalizeText('mannequin.widget.clubnotification') }
} - { viewMode === MannequinViewMode.INCOMPATIBLE_GENDER && -
{ LocalizeText('mannequin.widget.wronggender') }
} -
-
+ { (mode === MODE_PEER) && + <> + + + { name } + + + { LocalizeText('mannequin.widget.weartext') } + + + processAction(ACTION_WEAR) }> + { LocalizeText('mannequin.widget.wear') } + + } + { (mode === MODE_NO_CLUB) && + + { LocalizeText('mannequin.widget.clubnotification') } + } + { (mode === MODE_WRONG_GENDER) && + + { LocalizeText('mannequin.widget.wronggender') } + } + +
); diff --git a/src/views/room/widgets/furniture/mannequin/common/MannequinViewMode.ts b/src/views/room/widgets/furniture/mannequin/common/MannequinViewMode.ts deleted file mode 100644 index e588eea4..00000000 --- a/src/views/room/widgets/furniture/mannequin/common/MannequinViewMode.ts +++ /dev/null @@ -1,8 +0,0 @@ -export class MannequinViewMode -{ - public static readonly EDIT: string = 'edit'; - public static readonly SAVE: string = 'save'; - public static readonly CLUB: string = 'club'; - public static readonly DEFAULT: string = 'default'; - public static readonly INCOMPATIBLE_GENDER: string = 'incompatible_gender'; -} diff --git a/src/views/room/widgets/furniture/mannequin/views/preview/FurnitureMannequinPreviewView.tsx b/src/views/room/widgets/furniture/mannequin/views/preview/FurnitureMannequinPreviewView.tsx new file mode 100644 index 00000000..194700df --- /dev/null +++ b/src/views/room/widgets/furniture/mannequin/views/preview/FurnitureMannequinPreviewView.tsx @@ -0,0 +1,17 @@ +import { FC } from 'react'; +import { NitroLayoutBase } from '../../../../../../../layout/base'; +import { AvatarImageView } from '../../../../../../shared/avatar-image/AvatarImageView'; +import { CurrencyIcon } from '../../../../../../shared/currency-icon/CurrencyIcon'; +import { FurnitureMannequinPreviewViewProps } from './FurnitureMannequinPreviewView.types'; + +export const FurnitureMannequinPreviewView: FC = props => +{ + const { figure = null, clubLevel = 0 } = props; + + return ( + + + { (clubLevel > 0) && } + + ); +} diff --git a/src/views/room/widgets/furniture/mannequin/views/preview/FurnitureMannequinPreviewView.types.ts b/src/views/room/widgets/furniture/mannequin/views/preview/FurnitureMannequinPreviewView.types.ts new file mode 100644 index 00000000..1a5ee3fe --- /dev/null +++ b/src/views/room/widgets/furniture/mannequin/views/preview/FurnitureMannequinPreviewView.types.ts @@ -0,0 +1,5 @@ +export interface FurnitureMannequinPreviewViewProps +{ + figure: string; + clubLevel: number; +} diff --git a/src/views/room/widgets/furniture/stickie/FurnitureStickieView.scss b/src/views/room/widgets/furniture/stickie/FurnitureStickieView.scss index fc0c09da..95c8aecd 100644 --- a/src/views/room/widgets/furniture/stickie/FurnitureStickieView.scss +++ b/src/views/room/widgets/furniture/stickie/FurnitureStickieView.scss @@ -5,6 +5,7 @@ top: 25px; left: 25px; padding: 1px; + pointer-events: all; .stickie-header { width: 183px; diff --git a/src/views/room/widgets/furniture/stickie/FurnitureStickieView.tsx b/src/views/room/widgets/furniture/stickie/FurnitureStickieView.tsx index 1c4fb312..3e4718cf 100644 --- a/src/views/room/widgets/furniture/stickie/FurnitureStickieView.tsx +++ b/src/views/room/widgets/furniture/stickie/FurnitureStickieView.tsx @@ -1,6 +1,6 @@ import { NitroEvent, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from '@nitrots/nitro-renderer'; import { FC, useCallback, useState } from 'react'; -import { ColorUtils, GetRoomEngine, GetRoomSession, GetSessionDataManager, RoomWidgetRoomObjectUpdateEvent } from '../../../../../api'; +import { ColorUtils, GetRoomEngine, GetRoomSession, GetSessionDataManager, RoomWidgetUpdateRoomObjectEvent } from '../../../../../api'; import { CreateEventDispatcherHook } from '../../../../../hooks/events/event-dispatcher.base'; import { useRoomEngineEvent } from '../../../../../hooks/events/nitro/room/room-engine-event'; import { DraggableWindowPosition } from '../../../../../layout'; @@ -45,8 +45,8 @@ export const FurnitureStickieView: FC<{}> = props => setStickieData(new FurnitureStickieData(widgetEvent.objectId, widgetEvent.category, color, text, (GetRoomSession().isRoomOwner || GetSessionDataManager().isModerator), false)); return; } - case RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED: { - const widgetEvent = (event as RoomWidgetRoomObjectUpdateEvent); + case RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED: { + const widgetEvent = (event as RoomWidgetUpdateRoomObjectEvent); setStickieData(prevState => { @@ -60,7 +60,7 @@ export const FurnitureStickieView: FC<{}> = props => }, []); useRoomEngineEvent(RoomEngineTriggerWidgetEvent.REQUEST_STICKIE, onNitroEvent); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED, eventDispatcher, onNitroEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED, eventDispatcher, onNitroEvent); const processAction = useCallback((type: string, value: string = null) => { diff --git a/src/views/room/widgets/furniture/trophy/FurnitureTrophyView.tsx b/src/views/room/widgets/furniture/trophy/FurnitureTrophyView.tsx index 5f21e504..403a60e2 100644 --- a/src/views/room/widgets/furniture/trophy/FurnitureTrophyView.tsx +++ b/src/views/room/widgets/furniture/trophy/FurnitureTrophyView.tsx @@ -1,6 +1,6 @@ import { NitroEvent, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from '@nitrots/nitro-renderer'; import { FC, useCallback, useState } from 'react'; -import { GetRoomEngine, RoomWidgetRoomObjectUpdateEvent } from '../../../../../api'; +import { GetRoomEngine, RoomWidgetUpdateRoomObjectEvent } from '../../../../../api'; import { CreateEventDispatcherHook } from '../../../../../hooks/events/event-dispatcher.base'; import { useRoomEngineEvent } from '../../../../../hooks/events/nitro/room/room-engine-event'; import { NitroLayoutTrophyView } from '../../../../../layout'; @@ -9,6 +9,7 @@ import { FurnitureTrophyData } from './FurnitureTrophyData'; export const FurnitureTrophyView: FC<{}> = props => { + const { eventDispatcher = null, widgetHandler = null } = useRoomContext(); const [ trophyData, setTrophyData ] = useState(null); @@ -39,8 +40,8 @@ export const FurnitureTrophyView: FC<{}> = props => setTrophyData(new FurnitureTrophyData(widgetEvent.objectId, widgetEvent.category, color, ownerName, trophyDate, trophyText)); return; } - case RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED: { - const widgetEvent = (event as RoomWidgetRoomObjectUpdateEvent); + case RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED: { + const widgetEvent = (event as RoomWidgetUpdateRoomObjectEvent); setTrophyData(prevState => { @@ -54,7 +55,7 @@ export const FurnitureTrophyView: FC<{}> = props => }, []); useRoomEngineEvent(RoomEngineTriggerWidgetEvent.REQUEST_TROPHY, onNitroEvent); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED, widgetHandler.eventDispatcher, onNitroEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED, widgetHandler.eventDispatcher, onNitroEvent); const processAction = useCallback((type: string, value: string = null) => { diff --git a/src/views/room/widgets/infostand/InfoStandWidgetView.tsx b/src/views/room/widgets/infostand/InfoStandWidgetView.tsx index 01190223..4f240121 100644 --- a/src/views/room/widgets/infostand/InfoStandWidgetView.tsx +++ b/src/views/room/widgets/infostand/InfoStandWidgetView.tsx @@ -1,5 +1,5 @@ import { FC, useCallback, useState } from 'react'; -import { RoomWidgetRoomObjectMessage, RoomWidgetRoomObjectUpdateEvent, RoomWidgetUpdateEvent, RoomWidgetUpdateInfostandEvent, RoomWidgetUpdateInfostandFurniEvent, RoomWidgetUpdateInfostandPetEvent, RoomWidgetUpdateInfostandRentableBotEvent, RoomWidgetUpdateInfostandUserEvent } from '../../../../api'; +import { RoomWidgetRoomObjectMessage, RoomWidgetUpdateEvent, RoomWidgetUpdateInfostandEvent, RoomWidgetUpdateInfostandFurniEvent, RoomWidgetUpdateInfostandPetEvent, RoomWidgetUpdateInfostandRentableBotEvent, RoomWidgetUpdateInfostandUserEvent, RoomWidgetUpdateRoomObjectEvent } from '../../../../api'; import { CreateEventDispatcherHook } from '../../../../hooks/events/event-dispatcher.base'; import { useRoomContext } from '../../context/RoomContext'; import { InfoStandWidgetBotView } from './views/bot/InfoStandWidgetBotView'; @@ -22,33 +22,33 @@ export const InfoStandWidgetView: FC<{}> = props => { switch(event.type) { - case RoomWidgetRoomObjectUpdateEvent.OBJECT_SELECTED: { - const roomObjectEvent = (event as RoomWidgetRoomObjectUpdateEvent); + case RoomWidgetUpdateRoomObjectEvent.OBJECT_SELECTED: { + const roomObjectEvent = (event as RoomWidgetUpdateRoomObjectEvent); widgetHandler.processWidgetMessage(new RoomWidgetRoomObjectMessage(RoomWidgetRoomObjectMessage.GET_OBJECT_INFO, roomObjectEvent.id, roomObjectEvent.category)); return; } - case RoomWidgetRoomObjectUpdateEvent.OBJECT_DESELECTED: { - const roomObjectEvent = (event as RoomWidgetRoomObjectUpdateEvent); + case RoomWidgetUpdateRoomObjectEvent.OBJECT_DESELECTED: { + const roomObjectEvent = (event as RoomWidgetUpdateRoomObjectEvent); closeInfostand(); return; } - case RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED: - case RoomWidgetRoomObjectUpdateEvent.USER_REMOVED: { - const roomObjectEvent = (event as RoomWidgetRoomObjectUpdateEvent); + case RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED: + case RoomWidgetUpdateRoomObjectEvent.USER_REMOVED: { + const roomObjectEvent = (event as RoomWidgetUpdateRoomObjectEvent); setInfoStandEvent(prevValue => { switch(event.type) { - case RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED: + case RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED: if(prevValue instanceof RoomWidgetUpdateInfostandFurniEvent) { if(prevValue.id === roomObjectEvent.id) return null; } break; - case RoomWidgetRoomObjectUpdateEvent.USER_REMOVED: + case RoomWidgetUpdateRoomObjectEvent.USER_REMOVED: if(prevValue instanceof RoomWidgetUpdateInfostandUserEvent || prevValue instanceof RoomWidgetUpdateInfostandRentableBotEvent) { if(prevValue.roomIndex === roomObjectEvent.id) return null; @@ -80,10 +80,10 @@ export const InfoStandWidgetView: FC<{}> = props => } }, [ widgetHandler, closeInfostand ]); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.OBJECT_SELECTED, eventDispatcher, onRoomWidgetUpdateEvent); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.OBJECT_DESELECTED, eventDispatcher, onRoomWidgetUpdateEvent); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.USER_REMOVED, eventDispatcher, onRoomWidgetUpdateEvent); - CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED, eventDispatcher, onRoomWidgetUpdateEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.OBJECT_SELECTED, eventDispatcher, onRoomWidgetUpdateEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.OBJECT_DESELECTED, eventDispatcher, onRoomWidgetUpdateEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.USER_REMOVED, eventDispatcher, onRoomWidgetUpdateEvent); + CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED, eventDispatcher, onRoomWidgetUpdateEvent); CreateEventDispatcherHook(RoomWidgetUpdateInfostandFurniEvent.FURNI, eventDispatcher, onRoomWidgetUpdateEvent); CreateEventDispatcherHook(RoomWidgetUpdateInfostandUserEvent.OWN_USER, eventDispatcher, onRoomWidgetUpdateEvent); CreateEventDispatcherHook(RoomWidgetUpdateInfostandUserEvent.PEER, eventDispatcher, onRoomWidgetUpdateEvent); diff --git a/src/views/room/widgets/room-tools/RoomToolsWidgetView.scss b/src/views/room/widgets/room-tools/RoomToolsWidgetView.scss index 33313ed6..51e42d8f 100644 --- a/src/views/room/widgets/room-tools/RoomToolsWidgetView.scss +++ b/src/views/room/widgets/room-tools/RoomToolsWidgetView.scss @@ -1,5 +1,5 @@ .nitro-room-tools { - position: fixed; + position: absolute; bottom: 125px; left: -133px; background: rgba($dark,.95); diff --git a/src/views/room/widgets/room-tools/RoomToolsWidgetView.tsx b/src/views/room/widgets/room-tools/RoomToolsWidgetView.tsx index 250e979d..d52ef382 100644 --- a/src/views/room/widgets/room-tools/RoomToolsWidgetView.tsx +++ b/src/views/room/widgets/room-tools/RoomToolsWidgetView.tsx @@ -3,6 +3,7 @@ import classNames from 'classnames'; import { FC, useCallback, useState } from 'react'; import { LocalizeText, RoomWidgetZoomToggleMessage } from '../../../../api'; import { NavigatorEvent } from '../../../../events'; +import { ChatHistoryEvent } from '../../../../events/chat-history/ChatHistoryEvent'; import { dispatchUiEvent } from '../../../../hooks/events'; import { SendMessageHook } from '../../../../hooks/messages'; import { useRoomContext } from '../../context/RoomContext'; @@ -27,6 +28,8 @@ export const RoomToolsWidgetView: FC<{}> = props => setIsZoomedIn(value => !value); return; case 'chat_history': + dispatchUiEvent(new ChatHistoryEvent(ChatHistoryEvent.TOGGLE_CHAT_HISTORY)); + //setIsExpanded(false); close this ?? return; case 'like_room': if(isLiked) return; 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; } diff --git a/src/views/toolbar/ToolbarView.scss b/src/views/toolbar/ToolbarView.scss index 57401e81..779b8160 100644 --- a/src/views/toolbar/ToolbarView.scss +++ b/src/views/toolbar/ToolbarView.scss @@ -14,6 +14,11 @@ #toolbar-chat-input-container { margin: 0 10px; + + @include media-breakpoint-down(sm) { + width: 0px; + height: 0px + } } .navigation-items { diff --git a/src/views/toolbar/ToolbarView.tsx b/src/views/toolbar/ToolbarView.tsx index 4d10456b..1e9fbab2 100644 --- a/src/views/toolbar/ToolbarView.tsx +++ b/src/views/toolbar/ToolbarView.tsx @@ -1,8 +1,8 @@ import { Dispose, DropBounce, EaseOut, FigureUpdateEvent, JumpBy, Motions, NitroToolbarAnimateIconEvent, Queue, UserInfoDataParser, UserInfoEvent, UserProfileComposer, Wait } from '@nitrots/nitro-renderer'; import { FC, useCallback, useState } from 'react'; -import { GetRoomSession, GetRoomSessionManager, GetSessionDataManager, GoToDesktop, OpenMessengerChat } from '../../api'; +import { CreateLinkEvent, 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,10 @@ 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 isMod = GetSessionDataManager().isModerator; const unseenFriendListCount = 0; - const unseenAchievementsCount = 0; const onUserInfoEvent = useCallback((event: UserInfoEvent) => { @@ -64,6 +65,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,22 +168,22 @@ export const ToolbarView: FC = props => return (
- +
setMeExpanded(!isMeExpanded) }> - { (unseenAchievementsCount > 0) && -
{ unseenAchievementsCount }
} + { (unseenAchievementCount > 0) && +
{ unseenAchievementCount }
}
{ isInRoom && (
) } { !isInRoom && ( -
+
CreateLinkEvent('navigator/goto/home') }>
) }
handleToolbarItemClick(ToolbarViewItems.NAVIGATOR_ITEM) }> @@ -193,9 +201,10 @@ export const ToolbarView: FC = props =>
handleToolbarItemClick(ToolbarViewItems.CAMERA_ITEM) }>
) } -
handleToolbarItemClick(ToolbarViewItems.MOD_TOOLS_ITEM) }> + { isMod && ( +
handleToolbarItemClick(ToolbarViewItems.MOD_TOOLS_ITEM) }> -
+
) }
@@ -214,7 +223,7 @@ export const ToolbarView: FC = props =>
{ unseenFriendListCount }
}
}
-
+
diff --git a/src/views/toolbar/me/ToolbarMeView.tsx b/src/views/toolbar/me/ToolbarMeView.tsx index 790168b3..510e68ef 100644 --- a/src/views/toolbar/me/ToolbarMeView.tsx +++ b/src/views/toolbar/me/ToolbarMeView.tsx @@ -1,10 +1,21 @@ -import { FC } from 'react'; +import { RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { FC, useEffect } from 'react'; +import { GetRoomEngine, GetRoomSession } from '../../../api'; import { ToolbarViewItems } from '../ToolbarView.types'; import { ToolbarMeViewProps } from './ToolbarMeView.types'; export const ToolbarMeView: FC = props => { - const { handleToolbarItemClick = null } = props; + const { unseenAchievementCount = 0, handleToolbarItemClick = null } = props; + + useEffect(() => + { + const roomSession = GetRoomSession(); + + if(!roomSession) return; + + GetRoomEngine().selectRoomObject(roomSession.roomId, roomSession.ownRoomIndex, RoomObjectCategory.UNIT); + }, []); return (
@@ -17,6 +28,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; }