🆙 Fixed Floorplan

This commit is contained in:
duckietm
2026-02-18 11:16:52 +01:00
parent 8ea127afc4
commit 325453db36
14 changed files with 83 additions and 957 deletions
@@ -12,6 +12,9 @@ import { FloorplanCanvasView } from './views/FloorplanCanvasView';
import { FloorplanImportExportView } from './views/FloorplanImportExportView';
import { FloorplanOptionsView } from './views/FloorplanOptionsView';
type ScrollDirection = 'up' | 'down' | 'left' | 'right';
export const FloorplanEditorView: FC<{}> = props =>
{
const [ isVisible, setIsVisible ] = useState(false);
@@ -31,6 +34,7 @@ export const FloorplanEditorView: FC<{}> = props =>
thicknessWall: 1,
thicknessFloor: 1
});
const [ canvasScrollHandler, setCanvasScrollHandler ] = useState<((direction: ScrollDirection) => void) | null>(null);
const saveFloorChanges = () =>
{
@@ -141,8 +145,8 @@ export const FloorplanEditorView: FC<{}> = props =>
<NitroCardView uniqueKey="floorpan-editor" className="nitro-floorplan-editor" theme="primary-slim">
<NitroCardHeaderView headerText={ LocalizeText('floor.plan.editor.title') } onCloseClick={ () => setIsVisible(false) } />
<NitroCardContentView overflow="hidden">
<FloorplanOptionsView />
<FloorplanCanvasView overflow="hidden" />
<FloorplanOptionsView onCanvasScroll={ direction => canvasScrollHandler && canvasScrollHandler(direction) } />
<FloorplanCanvasView overflow="hidden" setScrollHandler={ setCanvasScrollHandler } />
<Flex justifyContent="between">
<Button onClick={ revertChanges }>{ LocalizeText('floor.plan.editor.reload') }</Button>
<ButtonGroup>
@@ -1,39 +0,0 @@
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];
}
}
@@ -1,44 +0,0 @@
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'
};
@@ -1 +0,0 @@
export const ConvertTileMapToString = (map: string) => map.replace(/\r\n|\r|\n/g, '\n').toLowerCase();
@@ -1,442 +0,0 @@
import { ActionSettings } from './ActionSettings';
import { FloorAction, HEIGHT_SCHEME, MAX_NUM_TILE_PER_AXIS, TILE_SIZE } from './Constants';
import { imageBase64, spritesheet } from './FloorplanResource';
import { Tile } from './Tile';
import { getScreenPositionForTile, getTileFromScreenPosition } from './Utils';
export class FloorplanEditor {
private static _INSTANCE: FloorplanEditor = null;
public static readonly TILE_BLOCKED = 'r_blocked';
public static readonly TILE_DOOR = 'r_door';
private _tilemap: Tile[][];
private _width: number;
private _height: number;
private _isPointerDown: boolean;
private _doorLocation: { x: number, y: number };
private _lastUsedTile: { x: number, y: number };
private _renderer: CanvasRenderingContext2D;
private _actionSettings: ActionSettings;
private _image: HTMLImageElement;
private _zoomLevel: number = 1.0;
private _squareSelectMode: boolean = false;
private _selectionStart: { x: number, y: number } | null = null;
private _selectionEnd: { x: number, y: number } | null = null;
constructor() {
const width = TILE_SIZE * MAX_NUM_TILE_PER_AXIS + 20;
const height = (TILE_SIZE * MAX_NUM_TILE_PER_AXIS) / 2 + 100;
const canvas = document.createElement('canvas');
canvas.height = height;
canvas.width = width;
canvas.style.touchAction = 'none';
canvas.oncontextmenu = (e) => { e.preventDefault(); }; // Added from old code
this._renderer = canvas.getContext('2d')!;
this._image = new Image();
this._image.src = imageBase64;
this._tilemap = [];
this._doorLocation = { x: 0, y: 0 };
this._width = 0;
this._height = 0;
this._isPointerDown = false;
this._lastUsedTile = { x: -1, y: -1 };
this._actionSettings = new ActionSettings();
}
public setSquareSelectMode(enabled: boolean): void {
this._squareSelectMode = enabled;
if (!enabled) {
this._selectionStart = null;
this._selectionEnd = null;
}
}
public get squareSelectMode(): boolean {
return this._squareSelectMode;
}
public onPointerRelease(): void {
this._isPointerDown = false;
if (this._squareSelectMode && this._selectionStart) {
this.finalizeSquareSelection();
}
}
public onPointerDown(event: PointerEvent): void {
if (this._squareSelectMode) {
event.preventDefault();
const location = { x: event.offsetX / this._zoomLevel, y: event.offsetY / this._zoomLevel };
const [tileX, tileY] = getTileFromScreenPosition(location.x, location.y);
const roundedX = Math.floor(tileX);
const roundedY = Math.floor(tileY);
this._selectionStart = { x: roundedX, y: roundedY };
this._selectionEnd = { x: roundedX, y: roundedY };
this._isPointerDown = true;
return;
}
if (event.button === 2) return;
const location = { x: event.offsetX / this._zoomLevel, y: event.offsetY / this._zoomLevel };
this._isPointerDown = true;
this.tileHitDetection(location, true);
}
public onPointerMove(event: PointerEvent): void {
if (!this._isPointerDown) return;
const location = { x: event.offsetX / this._zoomLevel, y: event.offsetY / this._zoomLevel };
if (this._squareSelectMode && this._selectionStart) {
const [tileX, tileY] = getTileFromScreenPosition(location.x, location.y);
this._selectionEnd!.x = Math.floor(tileX);
this._selectionEnd!.y = Math.floor(tileY);
this.renderTiles();
return;
}
this.tileHitDetection(location, false);
}
private tileHitDetection(tempPoint: { x: number, y: number }, isClick: boolean = false): boolean {
const mousePositionX = Math.floor(tempPoint.x);
const mousePositionY = Math.floor(tempPoint.y);
const width = TILE_SIZE;
const height = TILE_SIZE / 2;
for (let y = 0; y < this._tilemap.length; y++) {
for (let x = 0; x < this._tilemap[y].length; x++) {
const [tileStartX, tileStartY] = getScreenPositionForTile(x, y);
const centreX = tileStartX + (width / 2);
const centreY = tileStartY + (height / 2);
const dx = Math.abs(mousePositionX - centreX);
const dy = Math.abs(mousePositionY - centreY);
const solution = (dx / (width * 0.5) + dy / (height * 0.5) <= 1);
if (solution) {
if (this._isPointerDown) {
if (isClick) {
this.onClick(x, y);
} else if (this._lastUsedTile.x !== x || this._lastUsedTile.y !== y) {
this._lastUsedTile.x = x;
this._lastUsedTile.y = y;
this.onClick(x, y);
}
}
return true;
}
}
}
return false;
}
private onClick(x: number, y: number, render: boolean = true, force: boolean = false): void { // Updated from old code
const tile = this._tilemap[y][x];
let currentHeightIndex = (tile.height === 'x' && force) ? 0 : HEIGHT_SCHEME.indexOf(tile.height);
let futureHeightIndex = 0;
switch (this._actionSettings.currentAction) {
case FloorAction.DOOR:
if (!force && tile.height !== 'x') {
this._doorLocation.x = x;
this._doorLocation.y = y;
if (render) this.renderTiles();
}
return;
case FloorAction.UP:
if (!force && tile.height === 'x') return;
futureHeightIndex = currentHeightIndex + 1;
break;
case FloorAction.DOWN:
if (!force && (tile.height === 'x' || (currentHeightIndex <= 1))) return;
futureHeightIndex = currentHeightIndex - 1;
break;
case FloorAction.SET:
futureHeightIndex = HEIGHT_SCHEME.indexOf(this._actionSettings.currentHeight);
break;
case FloorAction.UNSET:
futureHeightIndex = 0;
break;
}
if (futureHeightIndex === -1) return;
if (currentHeightIndex === futureHeightIndex) return;
if (!force && 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;
this._tilemap[y][x].height = newHeight;
if (render) this.renderTiles();
}
public renderTiles(): void {
this.clearCanvas();
this._renderer.save();
this._renderer.scale(this._zoomLevel, this._zoomLevel);
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') && tile.isBlocked) assetName = 'x';
const [positionX, positionY] = getScreenPositionForTile(x, y);
const asset = spritesheet.frames[assetName];
if (asset === undefined) {
console.warn(`Asset "${assetName}" not found in spritesheet.`);
continue;
}
this._renderer.drawImage(
this._image,
asset.frame.x,
asset.frame.y,
asset.frame.w,
asset.frame.h,
positionX,
positionY,
asset.frame.w,
asset.frame.h
);
if (this._squareSelectMode && this._isPointerDown && this._selectionStart && this._selectionEnd) {
const selMinX = Math.min(this._selectionStart.x, this._selectionEnd.x);
const selMaxX = Math.max(this._selectionStart.x, this._selectionEnd.x);
const selMinY = Math.min(this._selectionStart.y, this._selectionEnd.y);
const selMaxY = Math.max(this._selectionStart.y, this._selectionEnd.y);
if (x >= selMinX && x <= selMaxX && y >= selMinY && y <= selMaxY) {
this._renderer.fillStyle = 'rgba(0, 255, 0, 0.3)';
this._renderer.fillRect(positionX, positionY, asset.frame.w, asset.frame.h);
continue;
}
}
if (tile.selected) {
this._renderer.fillStyle = tile.isBlocked ? 'rgb(128, 0, 128)' : 'rgba(0, 0, 255, 0.3)';
this._renderer.fillRect(positionX, positionY, asset.frame.w, asset.frame.h);
}
}
}
this._renderer.restore();
}
public toggleSelectAll(): void { // Added from old code
for (let y = 0; y < this._tilemap.length; y++) {
for (let x = 0; x < this._tilemap[y].length; x++) {
this._tilemap[y][x].selected = true;
if (this._actionSettings.currentAction !== FloorAction.DOOR) {
const tile = this._tilemap[y][x];
let currentHeightIndex = tile.height === 'x' ? 0 : HEIGHT_SCHEME.indexOf(tile.height);
let futureHeightIndex = 0;
switch (this._actionSettings.currentAction) {
case FloorAction.UP:
if (tile.height === 'x') continue;
futureHeightIndex = currentHeightIndex + 1;
break;
case FloorAction.DOWN:
if (tile.height === 'x' || currentHeightIndex <= 1) continue;
futureHeightIndex = currentHeightIndex - 1;
break;
case FloorAction.SET:
futureHeightIndex = HEIGHT_SCHEME.indexOf(this._actionSettings.currentHeight);
break;
case FloorAction.UNSET:
futureHeightIndex = 0;
break;
default:
continue;
}
if (futureHeightIndex !== -1 && currentHeightIndex !== futureHeightIndex) {
const newHeight = HEIGHT_SCHEME[futureHeightIndex];
if (newHeight) {
this._tilemap[y][x].height = newHeight;
if ((x + 1) > this._width) this._width = x + 1;
if ((y + 1) > this._height) this._height = y + 1;
}
}
}
}
}
this.recalcActiveArea();
this.renderTiles();
}
private finalizeSquareSelection(): void { // Updated from old code
const startX = Math.floor(this._selectionStart!.x);
const startY = Math.floor(this._selectionStart!.y);
const endX = Math.floor(this._selectionEnd!.x);
const endY = Math.floor(this._selectionEnd!.y);
const minX = Math.min(startX, endX);
const maxX = Math.max(startX, endX);
const minY = Math.min(startY, endY);
const maxY = Math.max(startY, endY);
this.selectSquareField(minX, minY, maxX, maxY);
this._selectionStart = null;
this._selectionEnd = null;
this.renderTiles();
}
private selectSquareField(x1: number, y1: number, x2: number, y2: number): void { // Added from old code
for (let y = y1; y <= y2; y++) {
for (let x = x1; x <= x2; x++) {
if (this._tilemap[y] && this._tilemap[y][x]) {
this._tilemap[y][x].selected = true;
this.onClick(x, y, false, true);
}
}
}
this.recalcActiveArea();
this.renderTiles();
}
private recalcActiveArea(): void { // Added from old code
this._width = 0;
this._height = 0;
for (let y = 0; y < this._tilemap.length; y++) {
for (let x = 0; x < this._tilemap[y].length; x++) {
if (this._tilemap[y][x].height !== 'x') {
if ((x + 1) > this._width) this._width = x + 1;
if ((y + 1) > this._height) this._height = y + 1;
}
}
}
}
public setTilemap(map: string, blockedTiles: boolean[][]): void {
this._tilemap = [];
const roomMapStringSplit = map.split('\r');
let width = 0;
let height = roomMapStringSplit.length;
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;
}
}
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);
}
}
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._tilemap = [];
this._doorLocation = { x: -1, y: -1 }; // Updated from old code (no .set method)
this._width = 0;
this._height = 0;
this._isPointerDown = false;
this._lastUsedTile = { x: -1, y: -1 }; // Updated from old code (no .set method)
this._actionSettings.clear();
this.clearCanvas();
}
public clearCanvas(): void {
this._renderer.fillStyle = '#000000';
this._renderer.fillRect(0, 0, this._renderer.canvas.width, this._renderer.canvas.height);
}
public zoomIn(): void { // Added from old code
this._zoomLevel = Math.min(this._zoomLevel + 0.1, 2.0);
this.adjustCanvasSize();
this.renderTiles();
}
public zoomOut(): void { // Added from old code
this._zoomLevel = Math.max(this._zoomLevel - 0.1, 0.5);
this.adjustCanvasSize();
this.renderTiles();
}
private adjustCanvasSize(): void { // Added from old code
const baseWidth = TILE_SIZE * MAX_NUM_TILE_PER_AXIS + 20;
const baseHeight = (TILE_SIZE * MAX_NUM_TILE_PER_AXIS) / 2 + 100;
this._renderer.canvas.width = baseWidth * this._zoomLevel;
this._renderer.canvas.height = baseHeight * this._zoomLevel;
}
public get zoomLevel(): number { // Added from old code
return this._zoomLevel;
}
public get renderer(): CanvasRenderingContext2D {
return this._renderer;
}
public get tilemap(): Tile[][] {
return this._tilemap;
}
public get doorLocation(): { x: number, y: number } {
return this._doorLocation;
}
public set doorLocation(value: { x: number, y: number }) {
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;
}
}
@@ -1,217 +0,0 @@
export const imageBase64 =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGwAAAC+CAMAAADnThrbAAAAYFBMVEUAAAAiIiIAZf8A6P8A/5MZ/wCb/wD/tQD/MgD/AHr/APxDXocAkf8A/+oA/2hE/wDy/wD/iQD/BwD/AKXWAP////8AvP8A/78A/z1w/wD/4AD/XgD/ACP/ANGqAP8QEBBSz3qJAAAAAXRSTlMAQObYZgAAAAlwSFlzAAALEwAACxMBAJqcGAAABJxJREFUeNrt1tuOszoMBeDpD7QFBloKDGf6/m+51UTj1PUhN1uWOup3vSQLkSzn6+vV4fAV9X9lLIcdDv/+xWKHw/d3PPPzE8k8Rj1osceoBz3z4yiZ31HauN9R2rjfUcq451HSuOdR0rjnUcK411HcuNdR3LjXUcw4btTrOG7U6zhu1Ms4adTzOGnU8zhpVBh30Eb5cQ/fKp/5UR1+51l9mfU/Mz6NxvfMuEGMu9G49f/y8vzLz4Ikif/8qopnhiF6FhNHP9aVo2cGR71lCZAvbAXkzADE/kgQvooqhM8MCNuMCUFLtiJoZiBI5ycsvD4qFs4MLLTNElFYjJUoZAYRPAsSlV/5lcpnBhU8C6y+zPqfGZ9G43tm3CDG3Wjc+p9N/Z7PgjSN//y6jmfGMXoWU0c/1rWjZ0ZHvWUpkC9sDeTMCMT+SBG+imqEz4wI24wpQUu2JmhmJEjnpyy8PmoWzowstM1SUViMtShkRhE8C1KVX/m1ymdGFTwLrL7M+p8Zn0bje2bcIMbdaNz6n039ns+CLIv//Mslnpmm6FnMHP1YXxw9MznqLcuAfGEvQM5MQOyPDOGr6ILwmQlhmzEjaMleCJqZCNL5GQuvjwsLZyYW2maZKCzGiyhkJhE8CzKVX/kXlc9MKngWWH2Z9T8zPo3G98y4QYy70bj1P5v6PZ8Fx2P851+v8cw8R8/i0dGP9dXRM7Oj3rIjkC/sFciZGYj9cUT4KroifGZG2GY8ErRkrwTNzATp/CMLr48rC2dmFtpmR1FYjFdRyMwieBYcVX7lX1U+M6vgWWD1Zdb/zPg0Gt8z4wYx7kbj1v9s6vd8FpxO8Z/fNPHMskTP4snRj3Xj6JnFUW/ZCcgXtgFyZgFif5wQvooahM8sCNuMJ4KWbEPQzEKQzj+x8PpoWDizsNA2O4nCYmxEIbOI4FlwUvmV36h8ZlHBs8Dqy6z/mfFpNL5nxg1i3I3Grf/Z1O/5LDif4z//dotn1jV6Fs+Ofqxvjp5ZHfWWnYF8YW9AzqxA7I8zwlfRDeEzK8I245mgJXsjaGYlSOefWXh93Fg4s7LQNjuLwmK8iUJmFcGz4KzyK/+m8plVBc8Cqy+z/mfGp9H4nhk3iHE3Grf+Z1O/57Mgz+M/v23jmW2LnsXc0Y916+iZzVFvWQ7kC9sCObMBsT9yhK+iFuEzG8I2Y07Qkm0JmtkI0vk5C6+PloUzGwtts1wUFmMrCplNBM+CXOVXfqvymU0FzwKrL7P+Z8an0fieGTeIcTcat/5nU7/ns6Ao4j+/6+KZfY+excLRj3Xn6JndUW9ZAeQL2wE5swOxPwqEr6IO4TM7wjZjQdCS7Qia2QnS+QULr4+OhTM7C22zQhQWYycKmV0Ez4JC5Vd+p/KZXQXPAqsvs/5nxqfR+J4ZN4hxNxq3/mdTv+ezoCzjP7/v45n7PXoWS0c/1r2jZ+6OestKIF/YHsiZOxD7o0T4KuoRPnNH2GYsCVqyPUEzd4J0fsnC66Nn4cydhbZZKQqLsReFzF0Ez4JS5Vd+r/KZuwqeBVZfZv3PjE+j8T0zbhDjbjRu/b+0PP8DZwi9QurvbfwAAAAASUVORK5CYII=';
export const spritesheet = {
frames: {
'0': {
frame: { x: 1, y: 1, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'1': {
frame: { x: 37, y: 1, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'2': {
frame: { x: 73, y: 1, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'3': {
frame: { x: 1, y: 20, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'4': {
frame: { x: 37, y: 20, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'5': {
frame: { x: 73, y: 20, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'6': {
frame: { x: 1, y: 39, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'7': {
frame: { x: 37, y: 39, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'8': {
frame: { x: 73, y: 39, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'9': {
frame: { x: 1, y: 58, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'a': {
frame: { x: 37, y: 58, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'b': {
frame: { x: 73, y: 58, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'c': {
frame: { x: 1, y: 77, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'd': {
frame: { x: 37, y: 77, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'e': {
frame: { x: 73, y: 77, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'f': {
frame: { x: 1, y: 96, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'g': {
frame: { x: 37, y: 96, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'h': {
frame: { x: 73, y: 96, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'i': {
frame: { x: 1, y: 115, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'j': {
frame: { x: 37, y: 115, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'k': {
frame: { x: 73, y: 115, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'l': {
frame: { x: 1, y: 134, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'm': {
frame: { x: 37, y: 134, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'n': {
frame: { x: 73, y: 134, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'o': {
frame: { x: 1, y: 153, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'p': {
frame: { x: 37, y: 153, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'q': {
frame: { x: 73, y: 153, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'r_blocked': {
frame: { x: 1, y: 172, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'r_door': {
frame: { x: 37, y: 172, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
'x': {
frame: { x: 73, y: 172, w: 34, h: 17 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 34, h: 17 },
sourceSize: { w: 34, h: 17 },
},
}
};
@@ -1,8 +0,0 @@
import { IVisualizationSettings } from './IVisualizationSettings';
export interface IFloorplanSettings extends IVisualizationSettings
{
tilemap: string;
reservedTiles: boolean[][];
entryPoint: [ number, number ];
}
@@ -1,7 +0,0 @@
export interface IVisualizationSettings
{
entryPointDir: number;
wallHeight: number;
thicknessWall: number;
thicknessFloor: number;
}
@@ -1,31 +0,0 @@
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;
}
}
@@ -1,53 +0,0 @@
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;
}
};
@@ -1,15 +1,21 @@
import { GetOccupiedTilesMessageComposer, GetRoomEntryTileMessageComposer, RoomEntryTileMessageEvent, RoomOccupiedTilesMessageEvent } from '@nitrots/nitro-renderer';
import { FC, useEffect, useRef, useState } from 'react';
import { FaArrowDown, FaArrowLeft, FaArrowRight, FaArrowUp } from 'react-icons/fa';
import { SendMessageComposer } from '../../../api';
import { Base, Button, Column, ColumnProps, Flex, Grid } from '../../../common';
import { Base, Column, ColumnProps } from '../../../common';
import { useMessageEvent } from '../../../hooks';
import { useFloorplanEditorContext } from '../FloorplanEditorContext';
import { FloorplanEditor } from '@nitrots/nitro-renderer';
export const FloorplanCanvasView: FC<ColumnProps> = props =>
type ScrollDirection = 'up' | 'down' | 'left' | 'right';
interface FloorplanCanvasViewProps extends ColumnProps
{
const { gap = 1, children = null, ...rest } = props;
setScrollHandler(handler: ((direction: ScrollDirection) => void) | null): void;
}
export const FloorplanCanvasView: FC<FloorplanCanvasViewProps> = props =>
{
const { gap = 1, children = null, setScrollHandler = null, ...rest } = props;
const [ occupiedTilesReceived , setOccupiedTilesReceived ] = useState(false);
const [ entryTileReceived, setEntryTileReceived ] = useState(false);
const { originalFloorplanSettings = null, setOriginalFloorplanSettings = null, setVisualizationSettings = null } = useFloorplanEditorContext();
@@ -63,7 +69,7 @@ export const FloorplanCanvasView: FC<ColumnProps> = props =>
setEntryTileReceived(true);
});
const onClickArrowButton = (scrollDirection: string) =>
const onClickArrowButton = (scrollDirection: ScrollDirection) =>
{
const element = elementRef.current;
@@ -164,35 +170,18 @@ export const FloorplanCanvasView: FC<ColumnProps> = props =>
}
}, []);
const isSmallScreen = () => window.innerWidth < 768;
useEffect(() =>
{
if(!setScrollHandler) return;
setScrollHandler(() => onClickArrowButton);
return () => setScrollHandler(null);
}, [ setScrollHandler ]);
return (
<Column gap={ gap } { ...rest }>
<Grid overflow="hidden" gap={ 1 }>
<Column center size={ 1 } className="d-md-none">
<Button className="d-md-none" onClick={ event => onClickArrowButton('left') }>
<FaArrowLeft className="fa-icon" />
</Button>
</Column>
<Column overflow="hidden" size={ isSmallScreen() ? 10: 12 } gap={ 1 }>
<Flex justifyContent="center" className="d-md-none">
<Button shrink onClick={ event => onClickArrowButton('up') }>
<FaArrowUp className="fa-icon" />
</Button>
</Flex>
<Base overflow="auto" innerRef={ elementRef } />
<Flex justifyContent="center" className="d-md-none">
<Button shrink onClick={ event => onClickArrowButton('down') }>
<FaArrowDown className="fa-icon" />
</Button>
</Flex>
</Column>
<Column center size={ 1 } className="d-md-none">
<Button className="d-md-none" onClick={ event => onClickArrowButton('right') }>
<FaArrowRight className="fa-icon" />
</Button>
</Column>
</Grid>
<Base overflow="auto" innerRef={ elementRef } />
{ children }
</Column>
);
@@ -1,8 +1,8 @@
import { FC, useState } from 'react';
import { FaCaretLeft, FaCaretRight } from 'react-icons/fa';
import { FaArrowDown, FaArrowLeft, FaArrowRight, FaArrowUp, FaCaretLeft, FaCaretRight } from 'react-icons/fa';
import ReactSlider from 'react-slider';
import { LocalizeText } from '../../../api';
import { Column, Flex, LayoutGridItem, Text } from '../../../common';
import { Button, Column, Flex, LayoutGridItem, Text } from '../../../common';
import { COLORMAP, FloorAction } from '@nitrots/nitro-renderer';
import { FloorplanEditor } from '@nitrots/nitro-renderer';
import { useFloorplanEditorContext } from '../FloorplanEditorContext';
@@ -13,8 +13,16 @@ const MAX_WALL_HEIGHT: number = 16;
const MIN_FLOOR_HEIGHT: number = 0;
const MAX_FLOOR_HEIGHT: number = 26;
export const FloorplanOptionsView: FC<{}> = props =>
type ScrollDirection = 'up' | 'down' | 'left' | 'right';
interface FloorplanOptionsViewProps
{
onCanvasScroll?(direction: ScrollDirection): void;
}
export const FloorplanOptionsView: FC<FloorplanOptionsViewProps> = props =>
{
const { onCanvasScroll = () => {} } = props;
const { visualizationSettings = null, setVisualizationSettings = null } = useFloorplanEditorContext();
const [ floorAction, setFloorAction ] = useState(FloorAction.SET);
const [ floorHeight, setFloorHeight ] = useState(0);
@@ -27,7 +35,6 @@ export const FloorplanOptionsView: FC<{}> = props =>
FloorplanEditor.instance.actionSettings.currentAction = action;
}
const toggleSquareSelectMode = () =>
{
const nextValue = FloorplanEditor.instance.toggleSquareSelectMode();
@@ -168,24 +175,6 @@ export const FloorplanOptionsView: FC<{}> = props =>
<FaCaretRight className="cursor-pointer fa-icon" onClick={ increaseWallHeight } />
</Flex>
</Column>
</Flex>
<Flex gap={ 1 }>
<Column size={ 6 }>
<Text bold>{ LocalizeText('floor.plan.editor.tile.height') }: { floorHeight }</Text>
<ReactSlider
className="nitro-slider"
min={ MIN_FLOOR_HEIGHT }
max={ MAX_FLOOR_HEIGHT }
step={ 1 }
value={ floorHeight }
onChange={ event => onFloorHeightChange(event) }
renderThumb={ (props, state) =>
{
const { key, style, ...rest } = (props as Record<string, any>);
return <div key={ key } style={ { backgroundColor: `#${ COLORMAP[state.valueNow.toString(33)] }`, ...style } } { ...rest }>{ state.valueNow }</div>;
} } />
</Column>
<Column size={ 6 }>
<Text bold>{ LocalizeText('floor.plan.editor.room.options') }</Text>
<Flex className="align-items-center">
@@ -204,6 +193,47 @@ export const FloorplanOptionsView: FC<{}> = props =>
</Flex>
</Column>
</Flex>
<Flex gap={ 2 } alignItems="center" justifyContent="between">
<Column size={ 6 }>
<Text bold>{ LocalizeText('floor.plan.editor.tile.height') }: { floorHeight }</Text>
<div style={ { width: '100%', maxWidth: 240 } }>
<ReactSlider
className="nitro-slider"
min={ MIN_FLOOR_HEIGHT }
max={ MAX_FLOOR_HEIGHT }
step={ 1 }
value={ floorHeight }
onChange={ event => onFloorHeightChange(event) }
renderThumb={ (props, state) =>
{
const { key, style, ...rest } = (props as Record<string, any>);
return <div key={ key } style={ { backgroundColor: `#${ COLORMAP[state.valueNow.toString(33)] }`, ...style } } { ...rest }>{ state.valueNow }</div>;
} } />
</div>
</Column>
<Column gap={ 1 }>
<Flex justifyContent="center">
<Button shrink onClick={ event => onCanvasScroll('up') }>
<FaArrowUp className="fa-icon" />
</Button>
</Flex>
<Flex alignItems="center" justifyContent="center" gap={ 1 }>
<Button shrink onClick={ event => onCanvasScroll('left') }>
<FaArrowLeft className="fa-icon" />
</Button>
<div style={ { width: 28 } } />
<Button shrink onClick={ event => onCanvasScroll('right') }>
<FaArrowRight className="fa-icon" />
</Button>
</Flex>
<Flex justifyContent="center">
<Button shrink onClick={ event => onCanvasScroll('down') }>
<FaArrowDown className="fa-icon" />
</Button>
</Flex>
</Column>
</Flex>
</Column>
);
}
}
-55
View File
@@ -335,61 +335,6 @@ body {
}
}
.nitro-slider {
display: flex;
align-items: center;
width: 100%;
height: 25px;
.track {
height: 3px;
border-radius: .25rem;
overflow: hidden;
&.track-0 {
background-color: #1e7295;
}
&.track-1 {
background-color: #b6bec5;
}
}
.thumb {
border-radius: 50%;
width: 25px;
height: 25px;
background-color: gray;
font-size: 10px;
text-align: center;
line-height: 25px;
padding: 0 3px;
&:hover,
.active {
cursor: pointer;
}
&.active {
outline: none;
}
&.degree {
&:after {
content: '\00b0'
}
}
&.percent {
&:after {
content: '\0025'
}
}
}
}
.layout-grid-item {
height: var(--nitro-grid-column-min-height, unset);
background-position: center;
+4 -4
View File
@@ -6,15 +6,15 @@
.track {
height: 3px;
border-radius: $border-radius;
border-radius: .25rem;
overflow: hidden;
&.track-0 {
background-color: $primary;
background-color: #1e7295;
}
&.track-1 {
background-color: $muted;
background-color: #b6bec5;
}
}
@@ -51,4 +51,4 @@
}
}
}
}
}