Merge pull request #7 from billsonnn/feature/floorplan-editor

Feature/floorplan editor
This commit is contained in:
Bill 2021-11-12 00:16:54 -05:00 committed by GitHub
commit b6b7100e0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1396 additions and 148 deletions

View File

@ -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": {

View File

@ -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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 756 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

View File

@ -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');
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;
}

View File

@ -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';
}

View File

@ -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';
}

View File

@ -7,7 +7,7 @@ export const NitroLayoutButton: FC<NitroLayoutButtonProps> = 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 }`;

View File

@ -22,3 +22,4 @@
@import './user-profile/UserProfileVew';
@import './chat-history/ChatHistoryView';
@import './help/HelpView';
@import './floorplan-editor/FloorplanEditorView';

View File

@ -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,15 +50,12 @@ 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(() =>
{

View File

@ -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;
}

View File

@ -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<IFloorplanSettings>(initialFloorplanSettings);
const [visualizationSettings, setVisualizationSettings] = useState<IVisualizationSettings>(
{
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 (
<>
<FloorplanEditorContextProvider value={{ originalFloorplanSettings: originalFloorplanSettings, setOriginalFloorplanSettings: setOriginalFloorplanSettings, visualizationSettings: visualizationSettings, setVisualizationSettings: setVisualizationSettings }}>
{isVisible &&
<NitroCardView className="nitro-floorplan-editor">
<NitroCardHeaderView headerText={LocalizeText('floor.plan.editor.title')} onCloseClick={() => setIsVisible(false)} />
<NitroCardContentView>
<NitroLayoutGrid>
<NitroLayoutGridColumn size={12}>
<FloorplanOptionsView />
<FloorplanCanvasView />
<NitroLayoutFlex className="justify-content-between">
<div className="btn-group">
<button className="btn btn-primary" onClick={revertChanges}>{LocalizeText('floor.plan.editor.reload')}</button>
</div>
<div className="btn-group">
<button className="btn btn-primary" disabled={true}>{LocalizeText('floor.plan.editor.preview')}</button>
<button className="btn btn-primary" onClick={ () => setImportExportVisible(true) }>{LocalizeText('floor.plan.editor.import.export')}</button>
<button className="btn btn-primary" onClick={saveFloorChanges}>{LocalizeText('floor.plan.editor.save')}</button>
</div>
</NitroLayoutFlex>
</NitroLayoutGridColumn>
</NitroLayoutGrid>
</NitroCardContentView>
</NitroCardView>
}
{importExportVisible && <FloorplanImportExportView onCloseClick={ () => setImportExportVisible(false)}/>}
</FloorplanEditorContextProvider>
</>
);
}

View File

@ -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];
}
}

View File

@ -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'
};

View File

@ -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<string>('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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,16 @@
import { createContext, FC, useContext } from 'react';
import { FloorplanEditorContextProps, IFloorplanEditorContext } from './FloorplanEditorContext.types';
const FloorplanEditorContext = createContext<IFloorplanEditorContext>({
originalFloorplanSettings: null,
setOriginalFloorplanSettings: null,
visualizationSettings: null,
setVisualizationSettings: null
});
export const FloorplanEditorContextProvider: FC<FloorplanEditorContextProps> = props =>
{
return <FloorplanEditorContext.Provider value={ props.value }>{ props.children }</FloorplanEditorContext.Provider>
}
export const useFloorplanEditorContext = () => useContext(FloorplanEditorContext);

View File

@ -0,0 +1,37 @@
import { ProviderProps } from 'react';
export interface IFloorplanEditorContext
{
originalFloorplanSettings: IFloorplanSettings;
setOriginalFloorplanSettings: React.Dispatch<React.SetStateAction<IFloorplanSettings>>;
visualizationSettings: IVisualizationSettings;
setVisualizationSettings: React.Dispatch<React.SetStateAction<IVisualizationSettings>>;
}
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<IFloorplanEditorContext>
{
}

View File

@ -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<HTMLDivElement>(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 (
<div ref={elementRef} className="editor-area" />
);
}

View File

@ -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<FloorplanImportExportViewProps> = props =>
{
const { originalFloorplanSettings = null, setOriginalFloorplanSettings = null } = useFloorplanEditorContext();
const { onCloseClick = null } = props;
const [ map, setMap ] = useState<string>('');
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 (
<NitroCardView simple={true} className="floorplan-import-export">
<NitroCardHeaderView headerText={LocalizeText('floor.plan.editor.import.export')} onCloseClick={ onCloseClick } />
<NitroCardContentView>
<NitroLayoutGridColumn size={ 12 } className="h-100">
<textarea className="h-100" value={map} onChange={ event => setMap(event.target.value) }></textarea>
<NitroLayoutFlex className="justify-content-between">
<div className="btn-group">
<button className="btn btn-primary" onClick={revertChanges}>{LocalizeText('floor.plan.editor.revert.to.last.received.map')}</button>
</div>
<div className="btn-group">
<button className="btn btn-primary" onClick={saveFloorChanges}>{LocalizeText('floor.plan.editor.save')}</button>
</div>
</NitroLayoutFlex>
</NitroLayoutGridColumn>
</NitroCardContentView>
</NitroCardView>
)
}
export interface FloorplanImportExportViewProps
{
onCloseClick(): void;
}

View File

@ -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 (
<NitroLayoutGrid className="h-auto">
<NitroLayoutGridColumn size={ 5 }>
<NitroLayoutFlexColumn gap={ 2 } overflow="hidden">
<NitroLayoutBase className="flex-shrink-0 fw-bold text-black text-truncate">{ LocalizeText('floor.plan.editor.draw.mode') }</NitroLayoutBase>
<NitroCardGridView>
<NitroCardGridItemView itemActive={ (floorAction === FloorAction.SET) } onClick={ event => selectAction(FloorAction.SET) }>
<i className="icon icon-set-tile" />
</NitroCardGridItemView>
<NitroCardGridItemView itemActive={ (floorAction === FloorAction.UNSET) } onClick={ event => selectAction(FloorAction.UNSET) }>
<i className="icon icon-unset-tile" />
</NitroCardGridItemView>
<NitroCardGridItemView itemActive={ (floorAction === FloorAction.UP) } onClick={ event => selectAction(FloorAction.UP) }>
<i className="icon icon-increase-height" />
</NitroCardGridItemView>
<NitroCardGridItemView itemActive={ (floorAction === FloorAction.DOWN) } onClick={ event => selectAction(FloorAction.DOWN) }>
<i className="icon icon-decrease-height" />
</NitroCardGridItemView>
<NitroCardGridItemView itemActive={ (floorAction === FloorAction.DOOR) } onClick={ event => selectAction(FloorAction.DOOR) }>
<i className="icon icon-set-door" />
</NitroCardGridItemView>
</NitroCardGridView>
</NitroLayoutFlexColumn>
</NitroLayoutGridColumn>
<NitroLayoutGridColumn className="align-items-center overflow-hidden" size={ 4 }>
<NitroLayoutFlexColumn gap={ 2 } overflow="hidden">
<NitroLayoutBase className="flex-shrink-0 fw-bold text-black text-truncate">{ LocalizeText('floor.plan.editor.enter.direction') }</NitroLayoutBase>
<i className={ `icon icon-door-direction-${ visualizationSettings.entryPointDir } cursor-pointer` } onClick={ changeDoorDirection } />
</NitroLayoutFlexColumn>
</NitroLayoutGridColumn>
<NitroLayoutGridColumn className="align-items-center" size={ 3 }>
<NitroLayoutFlexColumn gap={ 2 } overflow="hidden">
<NitroLayoutBase className="flex-shrink-0 fw-bold text-black text-truncate">{ LocalizeText('floor.editor.wall.height') }</NitroLayoutBase>
<NitroLayoutFlex className="align-items-center">
<i className="fas fa-caret-left cursor-pointer me-1 text-black" onClick={ decreaseWallHeight } />
<input type="number" className="form-control form-control-sm quantity-input" value={ visualizationSettings.wallHeight } onChange={ event => onWallHeightChange(event.target.valueAsNumber)} />
<i className="fas fa-caret-right cursor-pointer ms-1 text-black" onClick={ increaseWallHeight } />
</NitroLayoutFlex>
</NitroLayoutFlexColumn>
</NitroLayoutGridColumn>
<NitroLayoutGridColumn size={ 5 }>
<NitroLayoutFlexColumn gap={ 2 } overflow="hidden">
<NitroLayoutBase className="flex-shrink-0 fw-bold text-black text-truncate">{ LocalizeText('floor.plan.editor.tile.height') }: { floorHeight }</NitroLayoutBase>
<ReactSlider
className="nitro-slider"
min={ MIN_FLOOR_HEIGHT }
max={ MAX_FLOOR_HEIGHT }
step={ 1 }
value={ floorHeight }
onChange={ event => onFloorHeightChange(event) }
renderThumb={ ({ style, ...rest }, state) => <div style={ { backgroundColor: `#${Object.entries(COLORMAP)[state.valueNow + 1][1]}`, ...style } } { ...rest }>{ state.valueNow }</div> } />
</NitroLayoutFlexColumn>
</NitroLayoutGridColumn>
<NitroLayoutGridColumn size={5}>
<NitroLayoutFlexColumn gap={ 2 } overflow="hidden">
<NitroLayoutBase className="flex-shrink-0 fw-bold text-black text-truncate">{ LocalizeText('floor.plan.editor.room.options') }</NitroLayoutBase>
<NitroLayoutFlex className="align-items-center">
<select className="form-control form-control-sm" value={visualizationSettings.thicknessWall} onChange={event => onWallThicknessChange(parseInt(event.target.value))}>
<option value={0}>{ LocalizeText('navigator.roomsettings.wall_thickness.thinnest') }</option>
<option value={1}>{ LocalizeText('navigator.roomsettings.wall_thickness.thin') }</option>
<option value={2}>{ LocalizeText('navigator.roomsettings.wall_thickness.normal') }</option>
<option value={3}>{ LocalizeText('navigator.roomsettings.wall_thickness.thick') }</option>
</select>
<select className="form-control form-control-sm" value={visualizationSettings.thicknessFloor} onChange={event => onFloorThicknessChange(parseInt(event.target.value))}>
<option value={0}>{ LocalizeText('navigator.roomsettings.floor_thickness.thinnest') }</option>
<option value={1}>{ LocalizeText('navigator.roomsettings.floor_thickness.thin') }</option>
<option value={2}>{ LocalizeText('navigator.roomsettings.floor_thickness.normal') }</option>
<option value={3}>{ LocalizeText('navigator.roomsettings.floor_thickness.thick') }</option>
</select>
</NitroLayoutFlex>
</NitroLayoutFlexColumn>
</NitroLayoutGridColumn>
</NitroLayoutGrid>
);
}

View File

@ -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<MainViewProps> = props =>
<GroupsView />
<CameraWidgetView />
<HelpView />
<FloorplanEditorView />
</div>
);
}

View File

@ -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<NavigatorRoomInfoViewProps> = 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<NavigatorRoomInfoViewProps> = props =>
</div>
{ hasPermission('settings') && <>
<button className="btn btn-sm btn-primary w-100 mb-1" onClick={ () => processAction('open_room_settings') }>{ LocalizeText('navigator.room.popup.info.room.settings') }</button>
<button className="btn btn-sm btn-primary w-100 mb-1" disabled={ true }>{ LocalizeText('open.floor.plan.editor') }</button>
<button className="btn btn-sm btn-primary w-100 mb-1" onClick={ () => processAction('open_floorplan_editor') }>{ LocalizeText('open.floor.plan.editor') }</button>
</> }
{ hasPermission('staff_pick') && <button className="btn btn-sm btn-primary w-100 mb-1" onClick={ () => processAction('toggle_pick') }>{ LocalizeText(isRoomPicked ? 'navigator.staffpicks.unpick' : 'navigator.staffpicks.pick') }</button> }
<button className="btn btn-sm btn-danger w-100 mb-1" disabled={ true }>{ LocalizeText('help.emergency.main.report.room') }</button>

View File

@ -1,5 +1,9 @@
.nitro-chooser-widget {
.chooser-container {
min-height: 150px;
.list-item {
color: black;
overflow: hidden;
@ -9,3 +13,4 @@
}
}
}
}

View File

@ -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<ChooserWidgetViewProps> = props =>
<input type="text" className="form-control form-control-sm" placeholder={ LocalizeText('generic.search') } value={searchValue} onChange={event => setSearchValue(event.target.value)} />
</div>
</div>
<List
width={ 150 }
height={ 150 }
<div className="row w-100 h-100 chooser-container">
<AutoSizer defaultWidth={150} defaultHeight={150}>
{({ height, width }) =>
{
return (<List
width={ width }
height={ height }
rowCount={ filteredItems.length }
rowHeight={ 20 }
rowRenderer={ rowRenderer } />
rowRenderer={ rowRenderer } />)
}}
</AutoSizer>
</div>
</NitroCardContentView>
</NitroCardView>
);