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/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/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/styles/icons.scss b/src/assets/styles/icons.scss index 930c447d..78679240 100644 --- a/src/assets/styles/icons.scss +++ b/src/assets/styles/icons.scss @@ -10,11 +10,11 @@ background-position: center; &.icon-nitro-light { - background-image: url('../images/nitro/nitro-n-light.svg'); + background-image: url("../images/nitro/nitro-n-light.svg"); } &.icon-nitro-dark { - background-image: url('../images/nitro/nitro-n-dark.svg'); + background-image: url("../images/nitro/nitro-n-dark.svg"); } &.icon-nitro-light, @@ -24,693 +24,771 @@ } &.icon-catalog { - background-image: url('../images/toolbar/icons/catalog.png'); + background-image: url("../images/toolbar/icons/catalog.png"); width: 37px; height: 36px; } &.icon-rooms { - background-image: url('../images/toolbar/icons/rooms.png'); + background-image: url("../images/toolbar/icons/rooms.png"); width: 44px; height: 30px; } &.icon-house { - background-image: url('../images/toolbar/icons/house.png'); + background-image: url("../images/toolbar/icons/house.png"); height: 30px; width: 32px; } &.icon-inventory { - background-image: url('../images/toolbar/icons/inventory.png'); + background-image: url("../images/toolbar/icons/inventory.png"); height: 41px; width: 44px; } &.icon-modtools { - background-image: url('../images/toolbar/icons/modtools.png'); + background-image: url("../images/toolbar/icons/modtools.png"); height: 34px; width: 29px; } &.icon-friendall { - background-image: url('../images/toolbar/icons/friend_all.png'); + background-image: url("../images/toolbar/icons/friend_all.png"); height: 33px; width: 32px; } &.icon-friendsearch { - background-image: url('../images/toolbar/icons/friend_search.png'); + background-image: url("../images/toolbar/icons/friend_search.png"); height: 33px; width: 29px; } &.icon-sendmessage { - background-image: url('../images/toolbar/icons/sendmessage.png'); + background-image: url("../images/toolbar/icons/sendmessage.png"); width: 20px; height: 21px; } &.icon-me-talents { - background-image: url('../images/toolbar/icons/me-menu/talents.png'); + background-image: url("../images/toolbar/icons/me-menu/talents.png"); width: 32px; height: 30px; } &.icon-me-helper-tool { - background-image: url('../images/toolbar/icons/me-menu/helper-tool.png'); + background-image: url("../images/toolbar/icons/me-menu/helper-tool.png"); width: 32px; height: 30px; } &.icon-me-profile { - background-image: url('../images/toolbar/icons/me-menu/profile.png'); + background-image: url("../images/toolbar/icons/me-menu/profile.png"); width: 32px; height: 30px; } &.icon-me-forums { - background-image: url('../images/toolbar/icons/me-menu/forums.png'); + background-image: url("../images/toolbar/icons/me-menu/forums.png"); width: 32px; height: 30px; } &.icon-me-rooms { - background-image: url('../images/toolbar/icons/me-menu/my-rooms.png'); + background-image: url("../images/toolbar/icons/me-menu/my-rooms.png"); width: 30px; height: 30px; } &.icon-me-achievements { - background-image: url('../images/toolbar/icons/me-menu/achievements.png'); + background-image: url("../images/toolbar/icons/me-menu/achievements.png"); width: 31px; height: 30px; } &.icon-me-clothing { - background-image: url('../images/toolbar/icons/me-menu/clothing.png'); + background-image: url("../images/toolbar/icons/me-menu/clothing.png"); width: 27px; height: 30px; } &.icon-me-settings { - background-image: url('../images/toolbar/icons/me-menu/cog.png'); + background-image: url("../images/toolbar/icons/me-menu/cog.png"); width: 28px; height: 34px; } &.icon-cog { - background: url('../images/icons/icon_cog.png'); + background: url("../images/icons/icon_cog.png"); width: 14px; height: 15px; } &.icon-help { - background: url('../images/icons/help.png'); + background: url("../images/icons/help.png"); width: 13px; height: 23px; } &.icon-joinroom { - background-image: url('../images/toolbar/icons/joinroom.png'); + background-image: url("../images/toolbar/icons/joinroom.png"); width: 21px; height: 21px; } &.icon-habbo { - background-image: url('../images/toolbar/icons/habbo.png'); + background-image: url("../images/toolbar/icons/habbo.png"); width: 28px; height: 28px; } &.icon-camera { - background-image: url('../images/toolbar/icons/camera.png'); + background-image: url("../images/toolbar/icons/camera.png"); width: 38px; height: 45px; } &.icon-message { - background-image: url('../images/toolbar/icons/message.png'); + background-image: url("../images/toolbar/icons/message.png"); width: 36px; height: 32px; &.is-unseen { - background-image: url('../images/toolbar/icons/message_unsee.gif'); + background-image: url("../images/toolbar/icons/message_unsee.gif"); } } &.icon-deny { - background: url('../images/icons/deny.png'); + background: url("../images/icons/deny.png"); width: 13px; height: 14px; } &.icon-accept { - background: url('../images/icons/accept.png'); + background: url("../images/icons/accept.png"); width: 13px; height: 14px; } &.icon-wired-trigger { - background-image: url('../images/wired/icon_trigger.png'); + background-image: url("../images/wired/icon_trigger.png"); width: 13px; height: 14px; } &.icon-wired-condition { - background-image: url('../images/wired/icon_condition.png'); + background-image: url("../images/wired/icon_condition.png"); width: 13px; height: 14px; } &.icon-wired-action { - background-image: url('../images/wired/icon_action.png'); + background-image: url("../images/wired/icon_action.png"); width: 13px; height: 14px; } &.arrow-left-icon { - background-image: url('../images/avatareditor/arrow-left-icon.png'); + background-image: url("../images/avatareditor/arrow-left-icon.png"); width: 28px; height: 21px; } &.arrow-right-icon { - background-image: url('../images/avatareditor/arrow-right-icon.png'); + background-image: url("../images/avatareditor/arrow-right-icon.png"); width: 28px; height: 21px; } &.clear-icon { - background-image: url('../images/avatareditor/clear-icon.png'); + background-image: url("../images/avatareditor/clear-icon.png"); width: 27px; height: 27px; } &.ca-icon { - background-image: url('../images/avatareditor/ca-icon.png'); + background-image: url("../images/avatareditor/ca-icon.png"); width: 25px; height: 25px; &.selected { - background-image: url('../images/avatareditor/ca-selected-icon.png'); + background-image: url("../images/avatareditor/ca-selected-icon.png"); } } &.cc-icon { - background-image: url('../images/avatareditor/cc-icon.png'); + background-image: url("../images/avatareditor/cc-icon.png"); width: 31px; height: 29px; &.selected { - background-image: url('../images/avatareditor/cc-selected-icon.png'); + background-image: url("../images/avatareditor/cc-selected-icon.png"); } } &.ch-icon { - background-image: url('../images/avatareditor/ch-icon.png'); + background-image: url("../images/avatareditor/ch-icon.png"); width: 29px; height: 24px; &.selected { - background-image: url('../images/avatareditor/ch-selected-icon.png'); + background-image: url("../images/avatareditor/ch-selected-icon.png"); } } &.cp-icon { - background-image: url('../images/avatareditor/cp-icon.png'); + background-image: url("../images/avatareditor/cp-icon.png"); width: 30px; height: 24px; &.selected { - background-image: url('../images/avatareditor/cp-selected-icon.png'); + background-image: url("../images/avatareditor/cp-selected-icon.png"); } } &.ea-icon { - background-image: url('../images/avatareditor/ea-icon.png'); + background-image: url("../images/avatareditor/ea-icon.png"); width: 35px; height: 16px; &.selected { - background-image: url('../images/avatareditor/ea-selected-icon.png'); + background-image: url("../images/avatareditor/ea-selected-icon.png"); } } &.fa-icon { - background-image: url('../images/avatareditor/fa-icon.png'); + background-image: url("../images/avatareditor/fa-icon.png"); width: 27px; height: 20px; &.selected { - background-image: url('../images/avatareditor/fa-selected-icon.png'); + background-image: url("../images/avatareditor/fa-selected-icon.png"); } } &.female-icon { - background-image: url('../images/avatareditor/female-icon.png'); + background-image: url("../images/avatareditor/female-icon.png"); width: 18px; height: 27px; &.selected { - background-image: url('../images/avatareditor/female-selected-icon.png'); + background-image: url("../images/avatareditor/female-selected-icon.png"); } } &.ha-icon { - background-image: url('../images/avatareditor/ha-icon.png'); + background-image: url("../images/avatareditor/ha-icon.png"); width: 25px; height: 22px; &.selected { - background-image: url('../images/avatareditor/ha-selected-icon.png'); + background-image: url("../images/avatareditor/ha-selected-icon.png"); } } &.he-icon { - background-image: url('../images/avatareditor/he-icon.png'); + background-image: url("../images/avatareditor/he-icon.png"); width: 31px; height: 27px; &.selected { - background-image: url('../images/avatareditor/he-selected-icon.png'); + background-image: url("../images/avatareditor/he-selected-icon.png"); } } &.hr-icon { - background-image: url('../images/avatareditor/hr-icon.png'); + background-image: url("../images/avatareditor/hr-icon.png"); width: 29px; height: 25px; &.selected { - background-image: url('../images/avatareditor/hr-selected-icon.png'); + background-image: url("../images/avatareditor/hr-selected-icon.png"); } } &.lg-icon { - background-image: url('../images/avatareditor/lg-icon.png'); + background-image: url("../images/avatareditor/lg-icon.png"); width: 19px; height: 20px; &.selected { - background-image: url('../images/avatareditor/lg-selected-icon.png'); + background-image: url("../images/avatareditor/lg-selected-icon.png"); } } &.loading-icon { - background-image: url('../images/icons/loading-icon.png'); + background-image: url("../images/icons/loading-icon.png"); width: 17px; height: 21px; } &.male-icon { - background-image: url('../images/avatareditor/male-icon.png'); + background-image: url("../images/avatareditor/male-icon.png"); width: 21px; height: 21px; &.selected { - background-image: url('../images/avatareditor/male-selected-icon.png'); + background-image: url("../images/avatareditor/male-selected-icon.png"); } } &.sh-icon { - background-image: url('../images/avatareditor/sh-icon.png'); + background-image: url("../images/avatareditor/sh-icon.png"); width: 37px; height: 10px; &.selected { - background-image: url('../images/avatareditor/sh-selected-icon.png'); + background-image: url("../images/avatareditor/sh-selected-icon.png"); } } &.wa-icon { - background-image: url('../images/avatareditor/wa-icon.png'); + background-image: url("../images/avatareditor/wa-icon.png"); width: 36px; height: 18px; &.selected { - background-image: url('../images/avatareditor/wa-selected-icon.png'); + background-image: url("../images/avatareditor/wa-selected-icon.png"); } } &.sellable-icon { - background-image: url('../images/avatareditor/sellable-icon.png'); + background-image: url("../images/avatareditor/sellable-icon.png"); width: 17px; height: 15px; } &.chatstyles-icon { - background-image: url('../images/chat/styles-icon.png'); + background-image: url("../images/chat/styles-icon.png"); width: 17px; height: 19px; } &.pencil-icon { - background-image: url('../images/infostand/pencil-icon.png'); + background-image: url("../images/infostand/pencil-icon.png"); width: 17px; height: 18px; } &.trade-locked-icon { - background-image: url('../images/inventory/trading/locked-icon.png'); + background-image: url("../images/inventory/trading/locked-icon.png"); width: 29px; height: 43px; } &.trade-unlocked-icon { - background-image: url('../images/inventory/trading/unlocked-icon.png'); + background-image: url("../images/inventory/trading/unlocked-icon.png"); width: 29px; height: 43px; } &.modtool-room-icon { - background-image: url('../images/modtool/room.png'); + background-image: url("../images/modtool/room.png"); width: 20px; height: 15px; } &.modtool-chatlog-icon { - background-image: url('../images/modtool/chatlog.gif'); + background-image: url("../images/modtool/chatlog.gif"); width: 20px; height: 15px; } - &.modtool-user-icon{ - background-image: url('../images/modtool/user.gif'); + &.modtool-user-icon { + background-image: url("../images/modtool/user.gif"); width: 20px; height: 15px; } &.modtool-reports-icon { - background-image: url('../images/modtool/reports.png'); + background-image: url("../images/modtool/reports.png"); width: 20px; height: 15px; } &.modtool-wrench-icon { - background-image: url('../images/modtool/wrench.gif'); + background-image: url("../images/modtool/wrench.gif"); width: 20px; height: 15px; } &.modtool-key-icon { - background-image: url('../images/modtool/key.gif'); + background-image: url("../images/modtool/key.gif"); width: 20px; height: 15px; } &.icon-catalogue-hc_small { - background-image: url('../images/catalog/hc_small.png'); + background-image: url("../images/catalog/hc_small.png"); height: 17px; width: 31px; } &.icon-catalogue-hc_big { - background: url('../images/catalog/hc_big.png'); + background: url("../images/catalog/hc_big.png"); width: 68px; height: 40px; } &.icon-sign-exclamation { - background: url('../images/icons/sign-exclamation.png'); + background: url("../images/icons/sign-exclamation.png"); width: 7px; height: 17px; } &.icon-sign-heart { - background: url('../images/icons/sign-heart.png'); + background: url("../images/icons/sign-heart.png"); width: 15px; height: 13px; } &.icon-sign-red { - background: url('../images/icons/sign-red.png'); + background: url("../images/icons/sign-red.png"); width: 11px; height: 19px; } &.icon-sign-yellow { - background: url('../images/icons/sign-yellow.png'); + background: url("../images/icons/sign-yellow.png"); width: 11px; height: 19px; } &.icon-sign-skull { - background: url('../images/icons/sign-skull.png'); + background: url("../images/icons/sign-skull.png"); width: 12px; height: 12px; } &.icon-sign-smile { - background: url('../images/icons/sign-smile.png'); + background: url("../images/icons/sign-smile.png"); width: 7px; height: 14px; } - + &.icon-sign-soccer { - background: url('../images/icons/sign-soccer.png'); + background: url("../images/icons/sign-soccer.png"); width: 20px; height: 20px; } - + &.icon-house-small { - background: url('../images/icons/house-small.png'); + background: url("../images/icons/house-small.png"); width: 19px; height: 14px; } &.icon-camera-small { - background: url('../images/icons/camera-small.png'); + background: url("../images/icons/camera-small.png"); width: 17px; height: 15px; } &.icon-arrows { - background: url('../images/icons/arrows.png'); + background: url("../images/icons/arrows.png"); width: 17px; height: 15px; } - + &.icon-fb-profile { - background: url('../images/toolbar/icons/friend-bar/profile.png'); + background: url("../images/toolbar/icons/friend-bar/profile.png"); width: 21px; height: 21px; } &.icon-camera-colormatrix { - background: url('../images/icons/camera-colormatrix.png'); + background: url("../images/icons/camera-colormatrix.png"); width: 32px; height: 21px; } &.icon-camera-composite { - background: url('../images/icons/camera-composite.png'); + background: url("../images/icons/camera-composite.png"); width: 32px; height: 21px; } &.icon-user-profile { - background: url('../images/icons/user-profile.png'); + background: url("../images/icons/user-profile.png"); width: 13px; height: 11px; &:hover { - background: url('../images/icons/user-profile-hover.png'); + background: url("../images/icons/user-profile-hover.png"); } } &.icon-fb-profile { - background: url('../images/toolbar/icons/friend-bar/profile.png'); + background: url("../images/toolbar/icons/friend-bar/profile.png"); width: 21px; height: 21px; } &.icon-fb-chat { - background: url('../images/toolbar/icons/friend-bar/chat.png'); + background: url("../images/toolbar/icons/friend-bar/chat.png"); width: 20px; height: 21px; } &.icon-fb-visit { - background: url('../images/toolbar/icons/friend-bar/visit.png'); + background: url("../images/toolbar/icons/friend-bar/visit.png"); width: 21px; height: 21px; } &.icon-pf-online { - background: url('../images/profile/icons/online.gif'); + background: url("../images/profile/icons/online.gif"); width: 40px; height: 16px; } &.icon-pf-offline { - background: url('../images/profile/icons/offline.png'); + background: url("../images/profile/icons/offline.png"); width: 39px; height: 16px; } &.icon-pf-tick { - background: url('../images/profile/icons/tick.png'); + background: url("../images/profile/icons/tick.png"); width: 11px; height: 10px; } &.icon-relationship-none { - background: url('../images/friendlist/icons/icon_relationship_none.png'); + background: url("../images/friendlist/icons/icon_relationship_none.png"); width: 16px; height: 14px; } &.icon-relationship-heart { - background: url('../images/profile/icons/heart.png'); + background: url("../images/profile/icons/heart.png"); width: 16px; height: 14px; } &.icon-relationship-bobba { - background: url('../images/profile/icons/bobba.png'); + background: url("../images/profile/icons/bobba.png"); width: 16px; height: 14px; } &.icon-relationship-smile { - background: url('../images/profile/icons/smile.png'); + background: url("../images/profile/icons/smile.png"); width: 16px; height: 14px; } &.icon-group-type-0 { - background: url('../images/groups/icons/grouptype_icon_0.png'); + background: url("../images/groups/icons/grouptype_icon_0.png"); width: 16px; height: 16px; } &.icon-group-type-1 { - background: url('../images/groups/icons/grouptype_icon_1.png'); + background: url("../images/groups/icons/grouptype_icon_1.png"); width: 16px; height: 16px; } &.icon-group-type-2 { - background: url('../images/groups/icons/grouptype_icon_2.png'); + background: url("../images/groups/icons/grouptype_icon_2.png"); width: 16px; height: 16px; } &.icon-group-decorate { - background: url('../images/groups/icons/group_decorate_icon.png'); + background: url("../images/groups/icons/group_decorate_icon.png"); width: 15px; height: 15px; } &.icon-group-member { - background: url('../images/groups/icons/group_icon_big_member.png'); + background: url("../images/groups/icons/group_icon_big_member.png"); width: 20px; height: 20px; } &.icon-group-admin { - background: url('../images/groups/icons/group_icon_big_admin.png'); + background: url("../images/groups/icons/group_icon_big_admin.png"); width: 20px; height: 20px; } &.icon-group-owner { - background: url('../images/groups/icons/group_icon_big_owner.png'); + background: url("../images/groups/icons/group_icon_big_owner.png"); width: 20px; height: 20px; } &.icon-group-favorite { - background: url('../images/groups/icons/group_favorite.png'); + background: url("../images/groups/icons/group_favorite.png"); width: 14px; height: 14px; } &.icon-group-not-favorite { - background: url('../images/groups/icons/group_notfavorite.png'); + background: url("../images/groups/icons/group_notfavorite.png"); width: 14px; height: 14px; } &.icon-group-small-admin { - background: url('../images/groups/icons/group_icon_admin.png'); + background: url("../images/groups/icons/group_icon_admin.png"); width: 11px; height: 13px; } &.icon-group-small-not-admin { - background: url('../images/groups/icons/group_icon_not_admin.png'); + background: url("../images/groups/icons/group_icon_not_admin.png"); width: 11px; height: 13px; } &.icon-group-small-owner { - background: url('../images/groups/icons/group_icon_small_owner.png'); + background: url("../images/groups/icons/group_icon_small_owner.png"); width: 13px; height: 13px; } &.icon-navigator-info { - background: url('../images/navigator/icons/info.png'); + background: url("../images/navigator/icons/info.png"); width: 18px; height: 18px; } &.icon-navigator-room-locked { - background: url('../images/navigator/icons/room_locked.png'); + background: url("../images/navigator/icons/room_locked.png"); width: 13px; height: 16px; } &.icon-navigator-room-password { - background: url('../images/navigator/icons/room_password.png'); + background: url("../images/navigator/icons/room_password.png"); width: 13px; height: 16px; } &.icon-navigator-room-invisible { - background: url('../images/navigator/icons/room_invisible.png'); + background: url("../images/navigator/icons/room_invisible.png"); width: 13px; height: 16px; } &.icon-navigator-room-group { - background: url('../images/navigator/icons/room_group.png'); + background: url("../images/navigator/icons/room_group.png"); width: 13px; height: 11px; } &.icon-friendlist-follow { - background: url('../images/friendlist/icons/icon_follow.png'); + background: url("../images/friendlist/icons/icon_follow.png"); width: 16px; height: 14px; } &.icon-friendlist-chat { - background: url('../images/friendlist/icons/icon_chat.png'); + background: url("../images/friendlist/icons/icon_chat.png"); width: 17px; height: 16px; } &.icon-friendlist-warning { - background: url('../images/friendlist/icons/icon_warning.png'); + background: url("../images/friendlist/icons/icon_warning.png"); width: 23px; height: 21px; } &.icon-friendlist-new-message { - background: url('../images/friendlist/icons/icon_new_message.png'); + background: url("../images/friendlist/icons/icon_new_message.png"); width: 14px; height: 16px; } &.icon-hc-banner { - background: url('../images/catalog/hc_big.png'); + 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/events/chat-history/ChatHistoryEvent.ts b/src/events/chat-history/ChatHistoryEvent.ts index bf057561..e038f22e 100644 --- a/src/events/chat-history/ChatHistoryEvent.ts +++ b/src/events/chat-history/ChatHistoryEvent.ts @@ -5,5 +5,4 @@ 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'; - public static CHAT_HISTORY_CHANGED: string = 'CHE_CHAT_HISTORY_CHANGED'; } 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/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/views/Styles.scss b/src/views/Styles.scss index 77dc121f..6369f546 100644 --- a/src/views/Styles.scss +++ b/src/views/Styles.scss @@ -22,3 +22,4 @@ @import './user-profile/UserProfileVew'; @import './chat-history/ChatHistoryView'; @import './help/HelpView'; +@import './floorplan-editor/FloorplanEditorView'; diff --git a/src/views/chat-history/ChatHistoryView.tsx b/src/views/chat-history/ChatHistoryView.tsx index 2a29cdd8..f80ddbbd 100644 --- a/src/views/chat-history/ChatHistoryView.tsx +++ b/src/views/chat-history/ChatHistoryView.tsx @@ -11,8 +11,6 @@ import { RoomHistoryState } from './common/RoomHistoryState'; import { ChatHistoryContextProvider } from './context/ChatHistoryContext'; import { ChatEntryType } from './context/ChatHistoryContext.types'; - - export const ChatHistoryView: FC<{}> = props => { const [ isVisible, setIsVisible ] = useState(false); @@ -52,18 +50,15 @@ export const ChatHistoryView: FC<{}> = props => case ChatHistoryEvent.TOGGLE_CHAT_HISTORY: setIsVisible(!isVisible); break; - case ChatHistoryEvent.CHAT_HISTORY_CHANGED: - break; } }, [isVisible]); useUiEvent(ChatHistoryEvent.HIDE_CHAT_HISTORY, onChatHistoryEvent); useUiEvent(ChatHistoryEvent.SHOW_CHAT_HISTORY, onChatHistoryEvent); useUiEvent(ChatHistoryEvent.TOGGLE_CHAT_HISTORY, onChatHistoryEvent); - useUiEvent(ChatHistoryEvent.CHAT_HISTORY_CHANGED, onChatHistoryEvent); const cache = useMemo(() => -{ + { return new CellMeasurerCache({ defaultHeight: 25, fixedWidth: true, diff --git a/src/views/floorplan-editor/FloorplanEditorView.scss b/src/views/floorplan-editor/FloorplanEditorView.scss new file mode 100644 index 00000000..b11b5107 --- /dev/null +++ b/src/views/floorplan-editor/FloorplanEditorView.scss @@ -0,0 +1,21 @@ +.nitro-floorplan-editor { + width: 760px; + height: 575px; + + .editor-area { + width: 100%; + height: 300px; + min-height: 300px; + overflow-x: scroll; + } + + .color { + height: 50px; + width: 10px; + } +} + +.floorplan-import-export { + width: 500px; + height: 475px; +} diff --git a/src/views/floorplan-editor/FloorplanEditorView.tsx b/src/views/floorplan-editor/FloorplanEditorView.tsx new file mode 100644 index 00000000..48a7d34b --- /dev/null +++ b/src/views/floorplan-editor/FloorplanEditorView.tsx @@ -0,0 +1,151 @@ +import { FloorHeightMapEvent, NitroPoint, RoomEngineEvent, RoomVisualizationSettingsEvent, UpdateFloorPropertiesMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useState } from 'react'; +import { LocalizeText } from '../../api'; +import { FloorplanEditorEvent } from '../../events/floorplan-editor/FloorplanEditorEvent'; +import { CreateMessageHook, SendMessageHook, UseMountEffect, useRoomEngineEvent, useUiEvent } from '../../hooks'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutFlex, NitroLayoutGrid, NitroLayoutGridColumn } from '../../layout'; +import { FloorplanEditor } from './common/FloorplanEditor'; +import { convertNumbersForSaving, convertSettingToNumber } from './common/Utils'; +import { FloorplanEditorContextProvider } from './context/FloorplanEditorContext'; +import { IFloorplanSettings, initialFloorplanSettings, IVisualizationSettings } from './context/FloorplanEditorContext.types'; +import { FloorplanCanvasView } from './views/FloorplanCanvasView'; +import { FloorplanImportExportView } from './views/FloorplanImportExportView'; +import { FloorplanOptionsView } from './views/FloorplanOptionsView'; + +export const FloorplanEditorView: FC<{}> = props => +{ + const [isVisible, setIsVisible] = useState(false); + const [ importExportVisible, setImportExportVisible ] = useState(false); + const [originalFloorplanSettings, setOriginalFloorplanSettings] = useState(initialFloorplanSettings); + const [visualizationSettings, setVisualizationSettings] = useState( + { + entryPointDir: 2, + wallHeight: -1, + thicknessWall: 1, + thicknessFloor: 1 + }); + + const onFloorplanEditorEvent = useCallback((event: FloorplanEditorEvent) => + { + switch(event.type) + { + case FloorplanEditorEvent.HIDE_FLOORPLAN_EDITOR: + setIsVisible(false); + break; + case FloorplanEditorEvent.SHOW_FLOORPLAN_EDITOR: + setIsVisible(true); + break; + case FloorplanEditorEvent.TOGGLE_FLOORPLAN_EDITOR: + setIsVisible(!isVisible); + break; + } + }, [isVisible]); + + useUiEvent(FloorplanEditorEvent.HIDE_FLOORPLAN_EDITOR, onFloorplanEditorEvent); + useUiEvent(FloorplanEditorEvent.SHOW_FLOORPLAN_EDITOR, onFloorplanEditorEvent); + useUiEvent(FloorplanEditorEvent.TOGGLE_FLOORPLAN_EDITOR, onFloorplanEditorEvent); + + UseMountEffect(() => + { + FloorplanEditor.instance.initialize(); + }); + + const onRoomEngineEvent = useCallback((event: RoomEngineEvent) => + { + setIsVisible(false); + }, []); + + useRoomEngineEvent(RoomEngineEvent.DISPOSED, onRoomEngineEvent); + + const onFloorHeightMapEvent = useCallback((event: FloorHeightMapEvent) => + { + const parser = event.getParser(); + + if(!parser) return; + + const settings = Object.assign({}, originalFloorplanSettings); + settings.tilemap = parser.model; + settings.wallHeight = parser.wallHeight + 1; + setOriginalFloorplanSettings(settings); + + const vSettings = Object.assign({}, visualizationSettings); + vSettings.wallHeight = parser.wallHeight + 1; + setVisualizationSettings(vSettings); + + }, [originalFloorplanSettings, visualizationSettings]); + + CreateMessageHook(FloorHeightMapEvent, onFloorHeightMapEvent); + + const onRoomVisualizationSettingsEvent = useCallback((event: RoomVisualizationSettingsEvent) => + { + const parser = event.getParser(); + + if(!parser) return; + + const settings = Object.assign({}, originalFloorplanSettings); + settings.thicknessFloor = convertSettingToNumber(parser.thicknessFloor); + settings.thicknessWall = convertSettingToNumber(parser.thicknessWall); + + setOriginalFloorplanSettings(settings); + + const vSettings = Object.assign({}, visualizationSettings); + vSettings.thicknessFloor = convertSettingToNumber(parser.thicknessFloor); + vSettings.thicknessWall = convertSettingToNumber(parser.thicknessWall); + setVisualizationSettings(vSettings); + }, [originalFloorplanSettings, visualizationSettings]); + + CreateMessageHook(RoomVisualizationSettingsEvent, onRoomVisualizationSettingsEvent); + + const saveFloorChanges = useCallback(() => + { + SendMessageHook(new UpdateFloorPropertiesMessageComposer( + FloorplanEditor.instance.getCurrentTilemapString(), + FloorplanEditor.instance.doorLocation.x, + FloorplanEditor.instance.doorLocation.y, + visualizationSettings.entryPointDir, + convertNumbersForSaving(visualizationSettings.thicknessWall), + convertNumbersForSaving(visualizationSettings.thicknessFloor), + visualizationSettings.wallHeight - 1 + )); + }, [visualizationSettings.entryPointDir, visualizationSettings.thicknessFloor, visualizationSettings.thicknessWall, visualizationSettings.wallHeight]); + + const revertChanges = useCallback(() => + { + setVisualizationSettings({ wallHeight: originalFloorplanSettings.wallHeight, thicknessWall: originalFloorplanSettings.thicknessWall, thicknessFloor: originalFloorplanSettings.thicknessFloor, entryPointDir: originalFloorplanSettings.entryPointDir }); + + FloorplanEditor.instance.doorLocation = new NitroPoint(originalFloorplanSettings.entryPoint[0], originalFloorplanSettings.entryPoint[1]); + FloorplanEditor.instance.setTilemap(originalFloorplanSettings.tilemap, originalFloorplanSettings.reservedTiles); + FloorplanEditor.instance.renderTiles(); + }, [originalFloorplanSettings.entryPoint, originalFloorplanSettings.entryPointDir, originalFloorplanSettings.reservedTiles, originalFloorplanSettings.thicknessFloor, originalFloorplanSettings.thicknessWall, originalFloorplanSettings.tilemap, originalFloorplanSettings.wallHeight]) + + return ( + <> + + {isVisible && + + setIsVisible(false)} /> + + + + + + +
+ +
+
+ + + +
+
+
+
+
+
+ } + {importExportVisible && setImportExportVisible(false)}/>} +
+ + ); +} diff --git a/src/views/floorplan-editor/common/ActionSettings.ts b/src/views/floorplan-editor/common/ActionSettings.ts new file mode 100644 index 00000000..8ac38251 --- /dev/null +++ b/src/views/floorplan-editor/common/ActionSettings.ts @@ -0,0 +1,39 @@ +import { FloorAction, HEIGHT_SCHEME } from './Constants'; + +export class ActionSettings +{ + private _currentAction: number; + private _currentHeight: string; + + constructor() + { + this._currentAction = FloorAction.SET; + this._currentHeight = HEIGHT_SCHEME[1]; + } + + public get currentAction(): number + { + return this._currentAction; + } + + public set currentAction(value: number) + { + this._currentAction = value; + } + + public get currentHeight(): string + { + return this._currentHeight; + } + + public set currentHeight(value: string) + { + this._currentHeight = value; + } + + public clear(): void + { + this._currentAction = FloorAction.SET; + this._currentHeight = HEIGHT_SCHEME[1]; + } +} diff --git a/src/views/floorplan-editor/common/Constants.ts b/src/views/floorplan-editor/common/Constants.ts new file mode 100644 index 00000000..8f52f282 --- /dev/null +++ b/src/views/floorplan-editor/common/Constants.ts @@ -0,0 +1,44 @@ +export const TILE_SIZE = 32; +export const MAX_NUM_TILE_PER_AXIS = 64; + +export const HEIGHT_SCHEME: string = 'x0123456789abcdefghijklmnopq'; + +export class FloorAction +{ + public static readonly DOOR = 0; + public static readonly UP = 1; + public static readonly DOWN = 2; + public static readonly SET = 3; + public static readonly UNSET = 4; +} + +export const COLORMAP: object = { + 'x': '101010', + '0': '0065ff', + '1': '0091ff', + '2': '00bcff', + '3': '00e8ff', + '4': '00ffea', + '5': '00ffbf', + '6': '00ff93', + '7': '00ff68', + '8': '00ff3d', + '9': '19ff00', + 'a': '44ff00', + 'b': '70ff00', + 'c': '9bff00', + 'd': 'f2ff00', + 'e': 'ffe000', + 'f': 'ffb500', + 'g': 'ff8900', + 'h': 'ff5e00', + 'i': 'ff3200', + 'j': 'ff0700', + 'k': 'ff0023', + 'l': 'ff007a', + 'm': 'ff00a5', + 'n': 'ff00d1', + 'o': 'ff00fc', + 'p': 'd600ff', + 'q': 'aa00ff' +}; diff --git a/src/views/floorplan-editor/common/FloorplanEditor.ts b/src/views/floorplan-editor/common/FloorplanEditor.ts new file mode 100644 index 00000000..99cdd12c --- /dev/null +++ b/src/views/floorplan-editor/common/FloorplanEditor.ts @@ -0,0 +1,417 @@ +import { NitroPoint, NitroTilemap, PixiApplicationProxy, PixiInteractionEventProxy, POINT_STRUCT_SIZE } from '@nitrots/nitro-renderer'; +import { GetConfiguration } from '../../../api'; +import { ActionSettings } from './ActionSettings'; +import { FloorAction, HEIGHT_SCHEME, MAX_NUM_TILE_PER_AXIS, TILE_SIZE } from './Constants'; +import { Tile } from './Tile'; +import { getScreenPositionForTile, getTileFromScreenPosition } from './Utils'; + +export class FloorplanEditor extends PixiApplicationProxy +{ + private static _instance: FloorplanEditor = new FloorplanEditor(); + + private static readonly TILE_BLOCKED = 'r_blocked'; + private static readonly TILE_DOOR = 'r_door'; + + private _tilemap: Tile[][]; + private _width: number; + private _height: number; + private _isHolding: boolean; + private _doorLocation: NitroPoint; + private _lastUsedTile: NitroPoint; + private _tilemapRenderer: NitroTilemap; + private _actionSettings: ActionSettings; + private _isInitialized: boolean; + + private constructor() + { + const width = TILE_SIZE * MAX_NUM_TILE_PER_AXIS + 20; + const height = (TILE_SIZE * MAX_NUM_TILE_PER_AXIS) / 2 + 100; + + super({ + width: width, + height: height, + backgroundColor: 0x2b2b2b, + antialias: true, + autoDensity: true, + resolution: 1, + sharedLoader: true, + sharedTicker: true + }); + + this._tilemap = []; + this._doorLocation = new NitroPoint(0, 0); + this._width = 0; + this._height = 0; + this._isHolding = false; + this._lastUsedTile = new NitroPoint(-1,-1); + this._actionSettings = new ActionSettings(); + } + + public initialize(): void + { + if(!this._isInitialized) + { + this.loader.add('tiles', GetConfiguration('floorplan.tile.url')); + + this.loader.load((_, resources) => + { + this._tilemapRenderer = new NitroTilemap(resources['tiles'].spritesheet.baseTexture); + this.registerEventListeners(); + this.stage.addChild(this._tilemapRenderer); + }); + this._isInitialized = true; + } + } + + private registerEventListeners(): void + { + //this._tilemapRenderer.interactive = true; + + const tempPoint = new NitroPoint(); + // @ts-ignore + this._tilemapRenderer.containsPoint = (position) => + { + this._tilemapRenderer.worldTransform.applyInverse(position, tempPoint); + return this.tileHitDettection(tempPoint, false); + }; + + this._tilemapRenderer.on('pointerup', () => + { + this._isHolding = false; + }); + + this._tilemapRenderer.on('pointerout', () => + { + this._isHolding = false; + }); + + this._tilemapRenderer.on('pointerdown', (event: PixiInteractionEventProxy) => + { + if(!(event.data.originalEvent instanceof PointerEvent)) return; + + const pointerEvent = event.data.originalEvent; + if(pointerEvent.button === 2) return; + + + const location = event.data.global; + this.tileHitDettection(location, true); + }); + + this._tilemapRenderer.on('click', (event: PixiInteractionEventProxy) => + { + if(!(event.data.originalEvent instanceof PointerEvent)) return; + + const pointerEvent = event.data.originalEvent; + if(pointerEvent.button === 2) return; + + const location = event.data.global; + this.tileHitDettection(location, true, true); + }); + } + + private tileHitDettection(tempPoint: NitroPoint, setHolding: boolean, isClick: boolean = false): boolean + { + // @ts-ignore + const buffer = this._tilemapRenderer.pointsBuf; + const bufSize = POINT_STRUCT_SIZE; + + const len = buffer.length; + + if(setHolding) + { + this._isHolding = true; + } + + for(let j = 0; j < len; j += bufSize) + { + const bufIndex = j + bufSize; + const data = buffer.slice(j, bufIndex); + + const width = data[4]; + const height = data[5]; + + + const mousePositionX = Math.floor(tempPoint.x); + const mousePositionY = Math.floor(tempPoint.y); + + const tileStartX = data[2]; + const tileStartY = data[3]; + + + const centreX = tileStartX + (width / 2); + const centreY = tileStartY + (height / 2); + + const dx = Math.abs(mousePositionX - centreX - 2); + const dy = Math.abs(mousePositionY - centreY - 2); + + const solution = (dx / (width * 0.5) + dy / (height * 0.5) <= 1);//todo: improve this + if(solution) + { + if(this._isHolding) + { + const [realX, realY] = getTileFromScreenPosition(tileStartX, tileStartY); + + if(isClick) + { + this.onClick(realX, realY); + } + + else if(this._lastUsedTile.x !== realX || this._lastUsedTile.y !== realY) + { + this._lastUsedTile.x = realX; + this._lastUsedTile.y = realY; + this.onClick(realX, realY); + } + + } + return true; + } + + } + return false; + } + + + private onClick(x: number, y: number): void + { + const tile = this._tilemap[y][x]; + const heightIndex = HEIGHT_SCHEME.indexOf(tile.height); + + let futureHeightIndex = 0; + + switch(this._actionSettings.currentAction) + { + case FloorAction.DOOR: + + if(tile.height !== 'x') + { + this._doorLocation.x = x; + this._doorLocation.y = y; + this.renderTiles(); + } + return; + case FloorAction.UP: + futureHeightIndex = heightIndex + 1; + break; + case FloorAction.DOWN: + futureHeightIndex = heightIndex - 1; + break; + case FloorAction.SET: + futureHeightIndex = HEIGHT_SCHEME.indexOf(this._actionSettings.currentHeight); + break; + case FloorAction.UNSET: + futureHeightIndex = 0; + break; + } + + if(futureHeightIndex === -1) return; + + if(heightIndex === futureHeightIndex) return; + + if(futureHeightIndex > 0) + { + if((x + 1) > this._width) this._width = x + 1; + + if( (y + 1) > this._height) this._height = y + 1; + } + + const newHeight = HEIGHT_SCHEME[futureHeightIndex]; + + if(!newHeight) return; + + if(tile.isBlocked) return; + + this._tilemap[y][x].height = newHeight; + + this.renderTiles(); + } + + public renderTiles(): void + { + this.tilemapRenderer.clear(); + + for(let y = 0; y < this._tilemap.length; y++) + { + for(let x = 0; x < this.tilemap[y].length; x++) + { + const tile = this.tilemap[y][x]; + let assetName = tile.height; + + if(this._doorLocation.x === x && this._doorLocation.y === y) + assetName = FloorplanEditor.TILE_DOOR; + + if(tile.isBlocked) assetName = FloorplanEditor.TILE_BLOCKED; + + //if((tile.height === 'x') || tile.height === 'X') continue; + const [positionX, positionY ] = getScreenPositionForTile(x, y); + this._tilemapRenderer.tile(`${assetName}.png`, positionX, positionY); + } + } + } + + public setTilemap(map: string, blockedTiles: boolean[][]): void + { + this._tilemap = []; + const roomMapStringSplit = map.split('\r'); + + let width = 0; + let height = roomMapStringSplit.length; + + // find the map width, height + for(let y = 0; y < height; y++) + { + const originalRow = roomMapStringSplit[y]; + + if(originalRow.length === 0) + { + roomMapStringSplit.splice(y, 1); + height = roomMapStringSplit.length; + y--; + continue; + } + + if(originalRow.length > width) + { + width = originalRow.length; + } + } + // fill map with room heightmap tiles + for(let y = 0; y < height; y++) + { + this._tilemap[y] = []; + const rowString = roomMapStringSplit[y]; + + for(let x = 0; x < width; x++) + { + const blocked = (blockedTiles[y] && blockedTiles[y][x]) || false; + + const char = rowString[x]; + if(((!(char === 'x')) && (!(char === 'X')) && char)) + { + this._tilemap[y][x] = new Tile(char, blocked); + } + else + { + this._tilemap[y][x] = new Tile('x', blocked); + } + } + + for(let x = width; x < MAX_NUM_TILE_PER_AXIS; x++) + { + this.tilemap[y][x] = new Tile('x', false); + } + } + + // fill remaining map with empty tiles + for(let y = height; y < MAX_NUM_TILE_PER_AXIS; y++) + { + if(!this.tilemap[y]) this.tilemap[y] = []; + for(let x = 0; x < MAX_NUM_TILE_PER_AXIS; x++) + { + this.tilemap[y][x] = new Tile('x', false); + } + } + + this._width = width; + this._height = height; + } + + public getCurrentTilemapString(): string + { + const highestTile = this._tilemap[this._height - 1][this._width - 1]; + + if(highestTile.height === 'x') + { + this._width = -1; + this._height = -1; + + for(let y = MAX_NUM_TILE_PER_AXIS - 1; y >= 0; y--) + { + if(!this._tilemap[y]) continue; + + for(let x = MAX_NUM_TILE_PER_AXIS - 1; x >= 0; x--) + { + if(!this._tilemap[y][x]) continue; + + const tile = this._tilemap[y][x]; + + if(tile.height !== 'x') + { + if( (x + 1) > this._width) + this._width = x + 1; + + if( (y + 1) > this._height) + this._height = y + 1; + } + } + } + } + + + const rows = []; + + for(let y = 0; y < this._height; y++) + { + const row = []; + + for(let x = 0; x < this._width; x++) + { + const tile = this._tilemap[y][x]; + + row[x] = tile.height; + } + + rows[y] = row.join(''); + } + + return rows.join('\r'); + } + + public clear(): void + { + this._tilemapRenderer.interactive = false; + this._tilemap = []; + this._doorLocation.set(-1, -1); + this._width = 0; + this._height = 0; + this._isHolding = false; + this._lastUsedTile.set(-1, -1); + this._actionSettings.clear(); + this._tilemapRenderer.clear(); + } + + public get tilemapRenderer(): NitroTilemap + { + return this._tilemapRenderer; + } + + public get tilemap(): Tile[][] + { + return this._tilemap; + } + + public get doorLocation(): NitroPoint + { + return this._doorLocation; + } + + public set doorLocation(value: NitroPoint) + { + this._doorLocation = value; + } + + public get actionSettings(): ActionSettings + { + return this._actionSettings; + } + + public static get instance(): FloorplanEditor + { + if(!FloorplanEditor._instance) + { + FloorplanEditor._instance = new FloorplanEditor(); + } + + return FloorplanEditor._instance; + } +} diff --git a/src/views/floorplan-editor/common/Tile.ts b/src/views/floorplan-editor/common/Tile.ts new file mode 100644 index 00000000..fd9c0596 --- /dev/null +++ b/src/views/floorplan-editor/common/Tile.ts @@ -0,0 +1,31 @@ +export class Tile +{ + private _height: string; + private _isBlocked: boolean; + + constructor(height: string, isBlocked: boolean) + { + this._height = height; + this._isBlocked = isBlocked; + } + + public get height(): string + { + return this._height; + } + + public set height(height: string) + { + this._height = height; + } + + public get isBlocked(): boolean + { + return this._isBlocked; + } + + public set isBlocked(val: boolean) + { + this._isBlocked = val; + } +} diff --git a/src/views/floorplan-editor/common/Utils.ts b/src/views/floorplan-editor/common/Utils.ts new file mode 100644 index 00000000..6ffa52de --- /dev/null +++ b/src/views/floorplan-editor/common/Utils.ts @@ -0,0 +1,53 @@ +import { TILE_SIZE } from './Constants'; + +export const getScreenPositionForTile = (x: number, y: number): [number , number] => +{ + let positionX = (x * TILE_SIZE / 2) - (y * TILE_SIZE / 2); + const positionY = (x * TILE_SIZE / 4) + (y * TILE_SIZE / 4); + + positionX = positionX + 1024; // center the map in the canvas + + return [positionX, positionY]; +} + +export const getTileFromScreenPosition = (x: number, y: number): [number, number] => +{ + const translatedX = x - 1024; // after centering translation + + const realX = ((translatedX /(TILE_SIZE / 2)) + (y / (TILE_SIZE / 4))) / 2; + const realY = ((y /(TILE_SIZE / 4)) - (translatedX / (TILE_SIZE / 2))) / 2; + + return [realX, realY]; +} + +export const convertNumbersForSaving = (value: number): number => +{ + value = parseInt(value.toString()); + switch(value) + { + case 0: + return -2; + case 1: + return -1; + case 3: + return 1; + default: + return 0; + + } +} + +export const convertSettingToNumber = (value: number): number => +{ + switch(value) + { + case 0.25: + return 0; + case 0.5: + return 1; + case 2: + return 3; + default: + return 2; + } +} diff --git a/src/views/floorplan-editor/context/FloorplanEditorContext.tsx b/src/views/floorplan-editor/context/FloorplanEditorContext.tsx new file mode 100644 index 00000000..8a8ce970 --- /dev/null +++ b/src/views/floorplan-editor/context/FloorplanEditorContext.tsx @@ -0,0 +1,16 @@ +import { createContext, FC, useContext } from 'react'; +import { FloorplanEditorContextProps, IFloorplanEditorContext } from './FloorplanEditorContext.types'; + +const FloorplanEditorContext = createContext({ + originalFloorplanSettings: null, + setOriginalFloorplanSettings: null, + visualizationSettings: null, + setVisualizationSettings: null +}); + +export const FloorplanEditorContextProvider: FC = props => +{ + return { props.children } +} + +export const useFloorplanEditorContext = () => useContext(FloorplanEditorContext); diff --git a/src/views/floorplan-editor/context/FloorplanEditorContext.types.ts b/src/views/floorplan-editor/context/FloorplanEditorContext.types.ts new file mode 100644 index 00000000..581ff6af --- /dev/null +++ b/src/views/floorplan-editor/context/FloorplanEditorContext.types.ts @@ -0,0 +1,37 @@ +import { ProviderProps } from 'react'; + +export interface IFloorplanEditorContext +{ + originalFloorplanSettings: IFloorplanSettings; + setOriginalFloorplanSettings: React.Dispatch>; + visualizationSettings: IVisualizationSettings; + setVisualizationSettings: React.Dispatch>; +} + +export interface IFloorplanSettings extends IVisualizationSettings { + tilemap: string; + reservedTiles: boolean[][]; + entryPoint: [number, number]; +} + +export interface IVisualizationSettings { + entryPointDir: number; + wallHeight: number; + thicknessWall: number; + thicknessFloor: number; +} + +export const initialFloorplanSettings: IFloorplanSettings = { + tilemap: '', + reservedTiles: [], + entryPoint: [0, 0], + entryPointDir: 2, + wallHeight: -1, + thicknessWall: 1, + thicknessFloor: 1 +} + +export interface FloorplanEditorContextProps extends ProviderProps +{ + +} diff --git a/src/views/floorplan-editor/views/FloorplanCanvasView.tsx b/src/views/floorplan-editor/views/FloorplanCanvasView.tsx new file mode 100644 index 00000000..f68a9248 --- /dev/null +++ b/src/views/floorplan-editor/views/FloorplanCanvasView.tsx @@ -0,0 +1,80 @@ +import { GetOccupiedTilesMessageComposer, GetRoomEntryTileMessageComposer, NitroPoint, RoomEntryTileMessageEvent, RoomOccupiedTilesMessageEvent } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useRef, useState } from 'react'; +import { CreateMessageHook, SendMessageHook, UseMountEffect } from '../../../hooks'; +import { FloorplanEditor } from '../common/FloorplanEditor'; +import { useFloorplanEditorContext } from '../context/FloorplanEditorContext'; + +export const FloorplanCanvasView: FC<{}> = props => +{ + const { originalFloorplanSettings = null, setOriginalFloorplanSettings = null, visualizationSettings = null, setVisualizationSettings = null } = useFloorplanEditorContext(); + const [ occupiedTilesReceived , setOccupiedTilesReceived ] = useState(false); + const [ entryTileReceived, setEntryTileReceived ] = useState(false); + const elementRef = useRef(null); + + useEffect(() => + { + return ( () => + { + FloorplanEditor.instance.clear(); + setVisualizationSettings( prev => {return { wallHeight: originalFloorplanSettings.wallHeight, thicknessWall: originalFloorplanSettings.thicknessWall, thicknessFloor: originalFloorplanSettings.thicknessFloor, entryPointDir: prev.entryPointDir } }); + }); + }, [originalFloorplanSettings.thicknessFloor, originalFloorplanSettings.thicknessWall, originalFloorplanSettings.wallHeight, setVisualizationSettings]); + + UseMountEffect(() => + { + SendMessageHook(new GetRoomEntryTileMessageComposer()); + SendMessageHook(new GetOccupiedTilesMessageComposer()); + FloorplanEditor.instance.tilemapRenderer.interactive = true; + elementRef.current.appendChild(FloorplanEditor.instance.renderer.view); + }); + + const onRoomOccupiedTilesMessageEvent = useCallback((event: RoomOccupiedTilesMessageEvent) => + { + const parser = event.getParser(); + + if(!parser) return; + + const settings = Object.assign({}, originalFloorplanSettings); + settings.reservedTiles = parser.blockedTilesMap; + setOriginalFloorplanSettings(settings); + + FloorplanEditor.instance.setTilemap(originalFloorplanSettings.tilemap, parser.blockedTilesMap); + + setOccupiedTilesReceived(true); + + elementRef.current.scrollTo(FloorplanEditor.instance.view.width / 3, 0); + }, [originalFloorplanSettings, setOriginalFloorplanSettings]); + + CreateMessageHook(RoomOccupiedTilesMessageEvent, onRoomOccupiedTilesMessageEvent); + + const onRoomEntryTileMessageEvent = useCallback((event: RoomEntryTileMessageEvent) => + { + const parser = event.getParser(); + + if(!parser) return; + + const settings = Object.assign({}, originalFloorplanSettings); + settings.entryPoint = [parser.x, parser.y]; + settings.entryPointDir = parser.direction; + setOriginalFloorplanSettings(settings); + + const vSettings = Object.assign({}, visualizationSettings); + vSettings.entryPointDir = parser.direction; + setVisualizationSettings(vSettings); + + FloorplanEditor.instance.doorLocation = new NitroPoint(parser.x, parser.y); + setEntryTileReceived(true); + }, [originalFloorplanSettings, setOriginalFloorplanSettings, setVisualizationSettings, visualizationSettings]); + + CreateMessageHook(RoomEntryTileMessageEvent, onRoomEntryTileMessageEvent); + + useEffect(() => + { + if(entryTileReceived && occupiedTilesReceived) + FloorplanEditor.instance.renderTiles(); + }, [entryTileReceived, occupiedTilesReceived]) + + return ( +
+ ); +} diff --git a/src/views/floorplan-editor/views/FloorplanImportExportView.tsx b/src/views/floorplan-editor/views/FloorplanImportExportView.tsx new file mode 100644 index 00000000..274d5ec6 --- /dev/null +++ b/src/views/floorplan-editor/views/FloorplanImportExportView.tsx @@ -0,0 +1,68 @@ +import { UpdateFloorPropertiesMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useState } from 'react'; +import { LocalizeText } from '../../../api'; +import { SendMessageHook, UseMountEffect } from '../../../hooks'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutFlex, NitroLayoutGridColumn } from '../../../layout'; +import { convertNumbersForSaving } from '../common/Utils'; +import { useFloorplanEditorContext } from '../context/FloorplanEditorContext'; + +export const FloorplanImportExportView: FC = props => +{ + const { originalFloorplanSettings = null, setOriginalFloorplanSettings = null } = useFloorplanEditorContext(); + + const { onCloseClick = null } = props; + const [ map, setMap ] = useState(''); + + const convertMapToString = useCallback((map: string) => + { + return map.replace(/\r\n|\r|\n/g, '\n').toLowerCase(); + }, []); + + const revertChanges= useCallback(() => + { + setMap(convertMapToString(originalFloorplanSettings.tilemap)); + }, [convertMapToString, originalFloorplanSettings.tilemap]); + + const saveFloorChanges = useCallback(() => + { + SendMessageHook(new UpdateFloorPropertiesMessageComposer( + map.split('\n').join('\r'), + originalFloorplanSettings.entryPoint[0], + originalFloorplanSettings.entryPoint[1], + originalFloorplanSettings.entryPointDir, + convertNumbersForSaving(originalFloorplanSettings.thicknessWall), + convertNumbersForSaving(originalFloorplanSettings.thicknessFloor), + originalFloorplanSettings.wallHeight - 1 + )); + }, [map, originalFloorplanSettings.entryPoint, originalFloorplanSettings.entryPointDir, originalFloorplanSettings.thicknessFloor, originalFloorplanSettings.thicknessWall, originalFloorplanSettings.wallHeight]); + + UseMountEffect(() => + { + revertChanges(); + }); + + 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/main/MainView.tsx b/src/views/main/MainView.tsx index 2a7e1427..7a5eccdd 100644 --- a/src/views/main/MainView.tsx +++ b/src/views/main/MainView.tsx @@ -8,6 +8,7 @@ import { AvatarEditorView } from '../avatar-editor/AvatarEditorView'; import { CameraWidgetView } from '../camera/CameraWidgetView'; import { CatalogView } from '../catalog/CatalogView'; import { ChatHistoryView } from '../chat-history/ChatHistoryView'; +import { FloorplanEditorView } from '../floorplan-editor/FloorplanEditorView'; import { FriendsView } from '../friends/FriendsView'; import { GroupsView } from '../groups/GroupsView'; import { HelpView } from '../help/HelpView'; @@ -73,6 +74,7 @@ export const MainView: FC = props => +
); } 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/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 () + }} + +
);