summaryrefslogtreecommitdiff
path: root/src/scripts/views
diff options
context:
space:
mode:
authorGeorgios Andreadis <g.andreadis@student.tudelft.nl>2017-01-24 12:06:09 +0100
committerGeorgios Andreadis <g.andreadis@student.tudelft.nl>2017-01-24 12:06:09 +0100
commitc96e6ffafb62bde1e08987b1fdf3c0786487f6ec (patch)
tree37eaf4cf199ca77dc131b4212c526b707adf2e30 /src/scripts/views
Initial commit
Diffstat (limited to 'src/scripts/views')
-rw-r--r--src/scripts/views/layers/dcobject.ts252
-rw-r--r--src/scripts/views/layers/dcprogressbar.ts99
-rw-r--r--src/scripts/views/layers/gray.ts145
-rw-r--r--src/scripts/views/layers/grid.ts59
-rw-r--r--src/scripts/views/layers/hover.ts129
-rw-r--r--src/scripts/views/layers/layer.ts8
-rw-r--r--src/scripts/views/layers/room.ts177
-rw-r--r--src/scripts/views/layers/roomtext.ts68
-rw-r--r--src/scripts/views/layers/wall.ts62
-rw-r--r--src/scripts/views/mapview.ts373
10 files changed, 1372 insertions, 0 deletions
diff --git a/src/scripts/views/layers/dcobject.ts b/src/scripts/views/layers/dcobject.ts
new file mode 100644
index 00000000..6cec1f7e
--- /dev/null
+++ b/src/scripts/views/layers/dcobject.ts
@@ -0,0 +1,252 @@
+import {Colors} from "../../colors";
+import {Util, IntensityLevel} from "../../util";
+import {MapView} from "../mapview";
+import {DCProgressBar} from "./dcprogressbar";
+import {Layer} from "./layer";
+import {CELL_SIZE} from "../../controllers/mapcontroller";
+
+
+export class DCObjectLayer implements Layer {
+ public static ITEM_MARGIN = CELL_SIZE / 7.0;
+ public static ITEM_PADDING = CELL_SIZE / 10.0;
+ public static STROKE_WIDTH = CELL_SIZE / 20.0;
+ public static PROGRESS_BAR_DISTANCE = CELL_SIZE / 17.0;
+ public static CONTENT_SIZE = CELL_SIZE - DCObjectLayer.ITEM_MARGIN * 2 - DCObjectLayer.ITEM_PADDING * 3;
+
+ public container: createjs.Container;
+ public detailedMode: boolean;
+ public coloringMode: boolean;
+ public intensityLevels: { [key: number]: IntensityLevel; } = {};
+
+ private mapView: MapView;
+ private preload: createjs.LoadQueue;
+ private rackSpaceBitmap: createjs.Bitmap;
+ private rackEnergyBitmap: createjs.Bitmap;
+ private psuBitmap: createjs.Bitmap;
+ private coolingItemBitmap: createjs.Bitmap;
+
+ // This associative lookup object keeps all DC display objects with as property name the index of the global map
+ // array that they are located in.
+ private dcObjectMap: { [key: number]: any; };
+
+
+ public static drawHoverRack(position: IGridPosition): createjs.Container {
+ let result = new createjs.Container();
+
+ DCObjectLayer.drawItemRectangle(
+ position, Colors.RACK_BACKGROUND, Colors.RACK_BORDER, result
+ );
+ DCProgressBar.drawItemProgressRectangle(
+ position, Colors.RACK_SPACE_BAR_BACKGROUND, result, 0, 1
+ );
+ DCProgressBar.drawItemProgressRectangle(
+ position, Colors.RACK_ENERGY_BAR_BACKGROUND, result, 1, 1
+ );
+
+ return result;
+ }
+
+ public static drawHoverPSU(position: IGridPosition): createjs.Container {
+ let result = new createjs.Container();
+
+ DCObjectLayer.drawItemRectangle(
+ position, Colors.PSU_BACKGROUND, Colors.PSU_BORDER, result
+ );
+
+ return result;
+ }
+
+ public static drawHoverCoolingItem(position: IGridPosition): createjs.Container {
+ let result = new createjs.Container();
+
+ DCObjectLayer.drawItemRectangle(
+ position, Colors.COOLING_ITEM_BACKGROUND, Colors.COOLING_ITEM_BORDER, result
+ );
+
+ return result;
+ }
+
+ /**
+ * Draws an object rectangle in a given grid cell, with margin around its border.
+ *
+ * @param position The coordinates of the grid cell in which it should be located
+ * @param color The background color of the item
+ * @param borderColor The border color
+ * @param container The container to which it should be drawn
+ * @returns {createjs.Shape} The drawn shape
+ */
+ private static drawItemRectangle(position: IGridPosition, color: string, borderColor: string,
+ container: createjs.Container): createjs.Shape {
+ let shape = new createjs.Shape();
+ shape.graphics.beginStroke(borderColor);
+ shape.graphics.setStrokeStyle(DCObjectLayer.STROKE_WIDTH);
+ shape.graphics.beginFill(color);
+ shape.graphics.drawRect(
+ position.x * CELL_SIZE + DCObjectLayer.ITEM_MARGIN,
+ position.y * CELL_SIZE + DCObjectLayer.ITEM_MARGIN,
+ CELL_SIZE - DCObjectLayer.ITEM_MARGIN * 2,
+ CELL_SIZE - DCObjectLayer.ITEM_MARGIN * 2
+ );
+ container.addChild(shape);
+ return shape;
+ }
+
+ /**
+ * Draws an bitmap in item format.
+ *
+ * @param position The coordinates of the grid cell in which it should be located
+ * @param container The container to which it should be drawn
+ * @param originBitmap The bitmap that should be drawn
+ * @returns {createjs.Bitmap} The drawn bitmap
+ */
+ private static drawItemIcon(position: IGridPosition, container: createjs.Container,
+ originBitmap: createjs.Bitmap): createjs.Bitmap {
+ let bitmap = originBitmap.clone();
+ container.addChild(bitmap);
+ bitmap.x = position.x * CELL_SIZE + DCObjectLayer.ITEM_MARGIN + DCObjectLayer.ITEM_PADDING * 1.5;
+ bitmap.y = position.y * CELL_SIZE + DCObjectLayer.ITEM_MARGIN + DCObjectLayer.ITEM_PADDING * 1.5;
+ return bitmap;
+ }
+
+ constructor(mapView: MapView) {
+ this.mapView = mapView;
+ this.container = new createjs.Container();
+
+ this.detailedMode = true;
+ this.coloringMode = false;
+
+ this.preload = new createjs.LoadQueue();
+ this.preload.addEventListener("complete", () => {
+ this.rackSpaceBitmap = new createjs.Bitmap(<HTMLImageElement>this.preload.getResult("rack-space"));
+ this.rackEnergyBitmap = new createjs.Bitmap(<HTMLImageElement>this.preload.getResult("rack-energy"));
+ this.psuBitmap = new createjs.Bitmap(<HTMLImageElement>this.preload.getResult("psu"));
+ this.coolingItemBitmap = new createjs.Bitmap(<HTMLImageElement>this.preload.getResult("coolingitem"));
+
+ // Scale the images
+ this.rackSpaceBitmap.scaleX = DCProgressBar.PROGRESS_BAR_WIDTH / this.rackSpaceBitmap.image.width;
+ this.rackSpaceBitmap.scaleY = DCProgressBar.PROGRESS_BAR_WIDTH / this.rackSpaceBitmap.image.height;
+
+ this.rackEnergyBitmap.scaleX = DCProgressBar.PROGRESS_BAR_WIDTH / this.rackEnergyBitmap.image.width;
+ this.rackEnergyBitmap.scaleY = DCProgressBar.PROGRESS_BAR_WIDTH / this.rackEnergyBitmap.image.height;
+
+ this.psuBitmap.scaleX = DCObjectLayer.CONTENT_SIZE / this.psuBitmap.image.width;
+ this.psuBitmap.scaleY = DCObjectLayer.CONTENT_SIZE / this.psuBitmap.image.height;
+
+ this.coolingItemBitmap.scaleX = DCObjectLayer.CONTENT_SIZE / this.coolingItemBitmap.image.width;
+ this.coolingItemBitmap.scaleY = DCObjectLayer.CONTENT_SIZE / this.coolingItemBitmap.image.height;
+
+
+ this.populateObjectList();
+ this.draw();
+
+ this.mapView.updateScene = true;
+ });
+
+ this.preload.loadFile({id: "rack-space", src: 'img/app/rack-space.png'});
+ this.preload.loadFile({id: "rack-energy", src: 'img/app/rack-energy.png'});
+ this.preload.loadFile({id: "psu", src: 'img/app/psu.png'});
+ this.preload.loadFile({id: "coolingitem", src: 'img/app/coolingitem.png'});
+ }
+
+ /**
+ * Generates a list of DC objects with their associated display objects.
+ */
+ public populateObjectList(): void {
+ this.dcObjectMap = {};
+
+ this.mapView.currentDatacenter.rooms.forEach((room: IRoom) => {
+ room.tiles.forEach((tile: ITile) => {
+ if (tile.object !== undefined) {
+ let index = tile.position.y * MapView.MAP_SIZE + tile.position.x;
+
+ switch (tile.objectType) {
+ case "RACK":
+ this.dcObjectMap[index] = {
+ spaceBar: new DCProgressBar(this.container,
+ Colors.RACK_SPACE_BAR_BACKGROUND, Colors.RACK_SPACE_BAR_FILL,
+ this.rackSpaceBitmap, tile.position, 0,
+ Util.getFillRatio((<IRack>tile.object).machines)),
+ energyBar: new DCProgressBar(this.container,
+ Colors.RACK_ENERGY_BAR_BACKGROUND, Colors.RACK_ENERGY_BAR_FILL,
+ this.rackEnergyBitmap, tile.position, 1,
+ Util.getFillRatio((<IRack>tile.object).machines)),
+ itemRect: createjs.Shape,
+ tile: tile, model: tile.object, position: tile.position, type: tile.objectType
+ };
+
+ break;
+ case "COOLING_ITEM":
+ this.dcObjectMap[index] = {
+ itemRect: createjs.Shape, batteryIcon: createjs.Bitmap,
+ tile: tile, model: tile.object, position: tile.position, type: tile.objectType
+ };
+ break;
+ case "PSU":
+ this.dcObjectMap[index] = {
+ itemRect: createjs.Shape, freezeIcon: createjs.Bitmap,
+ tile: tile, model: tile.object, position: tile.position, type: tile.objectType
+ };
+ break;
+ }
+ }
+ });
+ });
+ }
+
+ public draw(): void {
+ let currentObject;
+
+ this.container.removeAllChildren();
+
+ this.container.cursor = "pointer";
+
+ for (let property in this.dcObjectMap) {
+ if (this.dcObjectMap.hasOwnProperty(property)) {
+ currentObject = this.dcObjectMap[property];
+
+ switch (currentObject.type) {
+ case "RACK":
+ let color = Colors.RACK_BACKGROUND;
+
+ if (this.coloringMode && currentObject.tile.roomId ===
+ this.mapView.mapController.roomModeController.currentRoom.id) {
+ color = Util.convertIntensityToColor(this.intensityLevels[currentObject.model.id]);
+ }
+
+ currentObject.itemRect = DCObjectLayer.drawItemRectangle(
+ currentObject.position, color, Colors.RACK_BORDER, this.container
+ );
+
+ if (this.detailedMode) {
+ currentObject.spaceBar.fillRatio = Util.getFillRatio(currentObject.model.machines);
+ currentObject.energyBar.fillRatio = Util.getEnergyRatio(currentObject.model);
+
+ currentObject.spaceBar.draw();
+ currentObject.energyBar.draw();
+ }
+ break;
+ case "COOLING_ITEM":
+ currentObject.itemRect = DCObjectLayer.drawItemRectangle(
+ currentObject.position, Colors.COOLING_ITEM_BACKGROUND, Colors.COOLING_ITEM_BORDER,
+ this.container
+ );
+
+ currentObject.freezeIcon = DCObjectLayer.drawItemIcon(currentObject.position, this.container,
+ this.coolingItemBitmap);
+ break;
+ case "PSU":
+ currentObject.itemRect = DCObjectLayer.drawItemRectangle(
+ currentObject.position, Colors.PSU_BACKGROUND, Colors.PSU_BORDER,
+ this.container
+ );
+
+ currentObject.batteryIcon = DCObjectLayer.drawItemIcon(currentObject.position, this.container,
+ this.psuBitmap);
+ break;
+ }
+ }
+ }
+
+ this.mapView.updateScene = true;
+ }
+} \ No newline at end of file
diff --git a/src/scripts/views/layers/dcprogressbar.ts b/src/scripts/views/layers/dcprogressbar.ts
new file mode 100644
index 00000000..d0ec4397
--- /dev/null
+++ b/src/scripts/views/layers/dcprogressbar.ts
@@ -0,0 +1,99 @@
+import {DCObjectLayer} from "./dcobject";
+import {CELL_SIZE} from "../../controllers/mapcontroller";
+
+
+export class DCProgressBar {
+ public static PROGRESS_BAR_WIDTH = CELL_SIZE / 7.0;
+
+ public container: createjs.Container;
+ public fillRatio: number;
+
+ private backgroundRect: createjs.Shape;
+ private backgroundColor: string;
+ private fillRect: createjs.Shape;
+ private fillColor: string;
+ private bitmap: createjs.Bitmap;
+ private position: IGridPosition;
+ private distanceFromBottom: number;
+
+
+ /**
+ * Draws a progress rectangle with rounded ends.
+ *
+ * @param position The coordinates of the grid cell in which it should be located
+ * @param color The background color of the item
+ * @param container The container to which it should be drawn
+ * @param distanceFromBottom The index of its vertical position, counted from the bottom (0 is the lowest position)
+ * @param fractionFilled The fraction of the available horizontal space that the progress bar should take up
+ * @returns {createjs.Shape} The drawn shape
+ */
+ public static drawItemProgressRectangle(position: IGridPosition, color: string,
+ container: createjs.Container, distanceFromBottom: number,
+ fractionFilled: number): createjs.Shape {
+ let shape = new createjs.Shape();
+ shape.graphics.beginFill(color);
+ let x = position.x * CELL_SIZE + DCObjectLayer.ITEM_MARGIN + DCObjectLayer.ITEM_PADDING;
+ let y = (position.y + 1) * CELL_SIZE - DCObjectLayer.ITEM_MARGIN - DCObjectLayer.ITEM_PADDING -
+ DCProgressBar.PROGRESS_BAR_WIDTH - distanceFromBottom *
+ (DCProgressBar.PROGRESS_BAR_WIDTH + DCObjectLayer.PROGRESS_BAR_DISTANCE);
+ let width = (CELL_SIZE - (DCObjectLayer.ITEM_MARGIN + DCObjectLayer.ITEM_PADDING) * 2) * fractionFilled;
+ let height;
+ let radius;
+
+ if (width < DCProgressBar.PROGRESS_BAR_WIDTH) {
+ height = width;
+ radius = width / 2;
+ y += (DCProgressBar.PROGRESS_BAR_WIDTH - height) / 2;
+ } else {
+ height = DCProgressBar.PROGRESS_BAR_WIDTH;
+ radius = DCProgressBar.PROGRESS_BAR_WIDTH / 2;
+ }
+
+ shape.graphics.drawRoundRect(
+ x, y, width, height, radius
+ );
+ container.addChild(shape);
+ return shape;
+ }
+
+ /**
+ * Draws an bitmap in progressbar format.
+ *
+ * @param position The coordinates of the grid cell in which it should be located
+ * @param container The container to which it should be drawn
+ * @param originBitmap The bitmap that should be drawn
+ * @param distanceFromBottom The index of its vertical position, counted from the bottom (0 is the lowest position)
+ * @returns {createjs.Bitmap} The drawn bitmap
+ */
+ public static drawProgressbarIcon(position: IGridPosition, container: createjs.Container, originBitmap: createjs.Bitmap,
+ distanceFromBottom: number): createjs.Bitmap {
+ let bitmap = originBitmap.clone();
+ container.addChild(bitmap);
+ bitmap.x = (position.x + 0.5) * CELL_SIZE - DCProgressBar.PROGRESS_BAR_WIDTH * 0.5;
+ bitmap.y = (position.y + 1) * CELL_SIZE - DCObjectLayer.ITEM_MARGIN - DCObjectLayer.ITEM_PADDING -
+ DCProgressBar.PROGRESS_BAR_WIDTH - distanceFromBottom *
+ (DCProgressBar.PROGRESS_BAR_WIDTH + DCObjectLayer.PROGRESS_BAR_DISTANCE);
+ return bitmap;
+ }
+
+ constructor(container: createjs.Container, backgroundColor: string,
+ fillColor: string, bitmap: createjs.Bitmap, position: IGridPosition,
+ indexFromBottom: number, fillRatio: number) {
+ this.container = container;
+ this.backgroundColor = backgroundColor;
+ this.fillColor = fillColor;
+ this.bitmap = bitmap;
+ this.position = position;
+ this.distanceFromBottom = indexFromBottom;
+ this.fillRatio = fillRatio;
+ }
+
+ public draw() {
+ this.backgroundRect = DCProgressBar.drawItemProgressRectangle(this.position, this.backgroundColor,
+ this.container, this.distanceFromBottom, 1);
+ this.fillRect = DCProgressBar.drawItemProgressRectangle(this.position, this.fillColor, this.container,
+ this.distanceFromBottom, this.fillRatio);
+
+ DCProgressBar.drawProgressbarIcon(this.position, this.container, this.bitmap, this.distanceFromBottom);
+ }
+} \ No newline at end of file
diff --git a/src/scripts/views/layers/gray.ts b/src/scripts/views/layers/gray.ts
new file mode 100644
index 00000000..ed3c9429
--- /dev/null
+++ b/src/scripts/views/layers/gray.ts
@@ -0,0 +1,145 @@
+import {MapView} from "../mapview";
+import {Colors} from "../../colors";
+import {Util} from "../../util";
+import {Layer} from "./layer";
+
+
+/**
+ * Class responsible for graying out non-active UI elements.
+ */
+export class GrayLayer implements Layer {
+ public container: createjs.Container;
+ public currentRoom: IRoom;
+ public currentObjectTile: ITile;
+
+ private mapView: MapView;
+ private grayRoomShape: createjs.Shape;
+
+
+ constructor(mapView: MapView) {
+ this.mapView = mapView;
+ this.container = new createjs.Container();
+ }
+
+ /**
+ * Draws grayed out areas around a currently selected room.
+ *
+ * @param redraw Whether this is a redraw, or an initial draw action
+ */
+ public draw(redraw?: boolean): void {
+ if (this.currentRoom === undefined) {
+ return;
+ }
+
+ this.container.removeAllChildren();
+
+ let roomBounds = Util.calculateRoomBounds(this.currentRoom);
+
+ let shape = new createjs.Shape();
+ shape.graphics.beginFill(Colors.GRAYED_OUT_AREA);
+ shape.cursor = "pointer";
+
+ this.drawLargeRects(shape, roomBounds);
+ this.drawFineGrainedRects(shape, roomBounds);
+
+ this.container.addChild(shape);
+ if (redraw === true) {
+ shape.alpha = 1;
+ } else {
+ shape.alpha = 0;
+ this.mapView.animate(shape, {alpha: 1});
+ }
+
+ if (this.grayRoomShape !== undefined && !this.grayRoomShape.visible) {
+ this.grayRoomShape = undefined;
+ this.drawRackLevel(redraw);
+ }
+
+ this.mapView.updateScene = true;
+ }
+
+ private drawLargeRects(shape: createjs.Shape, roomBounds: IBounds): void {
+ if (roomBounds.min[0] > 0) {
+ MapView.drawRectangleToShape({x: 0, y: 0}, shape, roomBounds.min[0], MapView.MAP_SIZE);
+ }
+ if (roomBounds.min[1] > 0) {
+ MapView.drawRectangleToShape({x: roomBounds.min[0], y: 0}, shape, roomBounds.max[0] - roomBounds.min[0],
+ roomBounds.min[1]);
+ }
+ if (roomBounds.max[0] < MapView.MAP_SIZE - 1) {
+ MapView.drawRectangleToShape({x: roomBounds.max[0], y: 0}, shape, MapView.MAP_SIZE - roomBounds.max[0],
+ MapView.MAP_SIZE);
+ }
+ if (roomBounds.max[1] < MapView.MAP_SIZE - 1) {
+ MapView.drawRectangleToShape({x: roomBounds.min[0], y: roomBounds.max[1]}, shape,
+ roomBounds.max[0] - roomBounds.min[0], MapView.MAP_SIZE - roomBounds.max[1]);
+ }
+ }
+
+ private drawFineGrainedRects(shape: createjs.Shape, roomBounds: IBounds): void {
+ for (let x = roomBounds.min[0]; x < roomBounds.max[0]; x++) {
+ for (let y = roomBounds.min[1]; y < roomBounds.max[1]; y++) {
+ if (!Util.tileListContainsPosition(this.currentRoom.tiles, {x: x, y: y})) {
+ MapView.drawRectangleToShape({x: x, y: y}, shape);
+ }
+ }
+ }
+ }
+
+ public drawRackLevel(redraw?: boolean): void {
+ if (this.currentObjectTile === undefined) {
+ return;
+ }
+
+ this.grayRoomShape = new createjs.Shape();
+ this.grayRoomShape.graphics.beginFill(Colors.GRAYED_OUT_AREA);
+ this.grayRoomShape.cursor = "pointer";
+ this.grayRoomShape.alpha = 0;
+
+ this.currentRoom.tiles.forEach((tile: ITile) => {
+ if (this.currentObjectTile.position.x !== tile.position.x ||
+ this.currentObjectTile.position.y !== tile.position.y) {
+ MapView.drawRectangleToShape({x: tile.position.x, y: tile.position.y}, this.grayRoomShape);
+ }
+ });
+
+ this.container.addChild(this.grayRoomShape);
+ if (redraw === true) {
+ this.grayRoomShape.alpha = 1;
+ } else {
+ this.grayRoomShape.alpha = 0;
+ this.mapView.animate(this.grayRoomShape, {alpha: 1});
+ }
+ }
+
+ public hideRackLevel(): void {
+ if (this.currentObjectTile === undefined) {
+ return;
+ }
+
+ this.mapView.animate(this.grayRoomShape, {
+ alpha: 0, visible: false
+ });
+ }
+
+ /**
+ * Clears the container.
+ */
+ public clear(): void {
+ this.mapView.animate(this.container, {alpha: 0}, () => {
+ this.container.removeAllChildren();
+ this.container.alpha = 1;
+ });
+ this.grayRoomShape = undefined;
+ this.currentRoom = undefined;
+ }
+
+ /**
+ * Checks whether there is already an active room with grayed out areas around it.
+ *
+ * @returns {boolean} Whether the room is grayed out
+ */
+ public isGrayedOut(): boolean {
+ return this.currentRoom !== undefined;
+ }
+} \ No newline at end of file
diff --git a/src/scripts/views/layers/grid.ts b/src/scripts/views/layers/grid.ts
new file mode 100644
index 00000000..9a52b2af
--- /dev/null
+++ b/src/scripts/views/layers/grid.ts
@@ -0,0 +1,59 @@
+import {Layer} from "./layer";
+import {MapView} from "../mapview";
+import {Colors} from "../../colors";
+import {CELL_SIZE} from "../../controllers/mapcontroller";
+
+
+/**
+ * Class responsible for rendering the grid.
+ */
+export class GridLayer implements Layer {
+ public container: createjs.Container;
+ public gridPixelSize: number;
+
+ private mapView: MapView;
+ private gridLineWidth: number;
+
+
+ constructor(mapView: MapView) {
+ this.mapView = mapView;
+ this.container = new createjs.Container();
+
+ this.gridLineWidth = 0.5;
+ this.gridPixelSize = MapView.MAP_SIZE * CELL_SIZE;
+
+ this.draw();
+ }
+
+ /**
+ * Draws the entire grid (later to be navigated around with offsets).
+ */
+ public draw(): void {
+ this.container.removeAllChildren();
+
+ let currentCellX = 0;
+ let currentCellY = 0;
+
+ while (currentCellX <= MapView.MAP_SIZE) {
+ MapView.drawLine(
+ currentCellX * CELL_SIZE, 0,
+ currentCellX * CELL_SIZE, MapView.MAP_SIZE * CELL_SIZE,
+ this.gridLineWidth, Colors.GRID_COLOR, this.container);
+
+ currentCellX++;
+ }
+
+ while (currentCellY <= MapView.MAP_SIZE) {
+ MapView.drawLine(
+ 0, currentCellY * CELL_SIZE,
+ MapView.MAP_SIZE * CELL_SIZE, currentCellY * CELL_SIZE,
+ this.gridLineWidth, Colors.GRID_COLOR, this.container);
+
+ currentCellY++;
+ }
+ }
+
+ public setVisibility(value: boolean): void {
+ this.container.visible = value;
+ }
+} \ No newline at end of file
diff --git a/src/scripts/views/layers/hover.ts b/src/scripts/views/layers/hover.ts
new file mode 100644
index 00000000..b9f5509c
--- /dev/null
+++ b/src/scripts/views/layers/hover.ts
@@ -0,0 +1,129 @@
+import {Layer} from "./layer";
+import {MapView} from "../mapview";
+import {Colors} from "../../colors";
+import {DCObjectLayer} from "./dcobject";
+import {CELL_SIZE} from "../../controllers/mapcontroller";
+
+
+/**
+ * Class responsible for rendering the hover layer.
+ */
+export class HoverLayer implements Layer {
+ public container: createjs.Container;
+ public hoverTilePosition: IGridPosition;
+
+ private mapView: MapView;
+ private hoverTile: createjs.Shape;
+ private hoverRack: createjs.Container;
+ private hoverPSU: createjs.Container;
+ private hoverCoolingItem: createjs.Container;
+
+
+ constructor(mapView: MapView) {
+ this.mapView = mapView;
+ this.container = new createjs.Container();
+
+ this.initialDraw();
+ }
+
+ /**
+ * Draws the hover tile to the container at its current location and with its current color.
+ */
+ public draw(): void {
+ let color;
+
+ if (this.mapView.roomLayer.checkHoverTileValidity(this.hoverTilePosition)) {
+ color = Colors.ROOM_HOVER_VALID;
+ } else {
+ color = Colors.ROOM_HOVER_INVALID;
+ }
+
+ this.hoverTile.graphics.clear().beginFill(color)
+ .drawRect(this.hoverTilePosition.x * CELL_SIZE, this.hoverTilePosition.y * CELL_SIZE,
+ CELL_SIZE, CELL_SIZE)
+ .endFill();
+ if (this.hoverRack.visible) {
+ this.hoverRack.x = this.hoverTilePosition.x * CELL_SIZE;
+ this.hoverRack.y = this.hoverTilePosition.y * CELL_SIZE;
+ } else if (this.hoverPSU.visible) {
+ this.hoverPSU.x = this.hoverTilePosition.x * CELL_SIZE;
+ this.hoverPSU.y = this.hoverTilePosition.y * CELL_SIZE;
+ } else if (this.hoverCoolingItem.visible) {
+ this.hoverCoolingItem.x = this.hoverTilePosition.x * CELL_SIZE;
+ this.hoverCoolingItem.y = this.hoverTilePosition.y * CELL_SIZE;
+ }
+ }
+
+ /**
+ * Performs the initial drawing action.
+ */
+ public initialDraw(): void {
+ this.container.removeAllChildren();
+
+ this.hoverTile = new createjs.Shape();
+
+ this.hoverTilePosition = {x: 0, y: 0};
+
+ this.hoverTile = MapView.drawRectangle(this.hoverTilePosition, Colors.ROOM_HOVER_VALID, this.container);
+ this.hoverTile.visible = false;
+
+ this.hoverRack = DCObjectLayer.drawHoverRack(this.hoverTilePosition);
+ this.hoverPSU = DCObjectLayer.drawHoverPSU(this.hoverTilePosition);
+ this.hoverCoolingItem = DCObjectLayer.drawHoverCoolingItem(this.hoverTilePosition);
+
+ this.container.addChild(this.hoverRack);
+ this.container.addChild(this.hoverPSU);
+ this.container.addChild(this.hoverCoolingItem);
+
+ this.hoverRack.visible = false;
+ this.hoverPSU.visible = false;
+ this.hoverCoolingItem.visible = false;
+ }
+
+ /**
+ * Sets the hover tile visibility to true/false.
+ *
+ * @param value The visibility value
+ */
+ public setHoverTileVisibility(value: boolean): void {
+ this.hoverTile.visible = value;
+ this.mapView.updateScene = true;
+ }
+
+ /**
+ * Sets the hover item visibility to true/false.
+ *
+ * @param value The visibility value
+ * @param type The type of the object to be shown
+ */
+ public setHoverItemVisibility(value: boolean, type?: string): void {
+ if (value === true) {
+ this.hoverTile.visible = true;
+
+ this.setHoverItemVisibilities(type);
+ } else {
+ this.hoverTile.visible = false;
+ this.hoverRack.visible = false;
+ this.hoverPSU.visible = false;
+ this.hoverCoolingItem.visible = false;
+ }
+
+ this.mapView.updateScene = true;
+ }
+
+ private setHoverItemVisibilities(type: string): void {
+ if (type === "RACK") {
+ this.hoverRack.visible = true;
+ this.hoverPSU.visible = false;
+ this.hoverCoolingItem.visible = false;
+ } else if (type === "PSU") {
+ this.hoverRack.visible = false;
+ this.hoverPSU.visible = true;
+ this.hoverCoolingItem.visible = false;
+ } else if (type === "COOLING_ITEM") {
+ this.hoverRack.visible = false;
+ this.hoverPSU.visible = false;
+ this.hoverCoolingItem.visible = true;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/scripts/views/layers/layer.ts b/src/scripts/views/layers/layer.ts
new file mode 100644
index 00000000..5e5295ac
--- /dev/null
+++ b/src/scripts/views/layers/layer.ts
@@ -0,0 +1,8 @@
+/**
+ * Interface for a subview, representing a layer of the map view.
+ */
+export interface Layer {
+ container: createjs.Container;
+
+ draw(): void;
+}
diff --git a/src/scripts/views/layers/room.ts b/src/scripts/views/layers/room.ts
new file mode 100644
index 00000000..0e31fee0
--- /dev/null
+++ b/src/scripts/views/layers/room.ts
@@ -0,0 +1,177 @@
+import {InteractionLevel} from "../../controllers/mapcontroller";
+import {Util, IntensityLevel} from "../../util";
+import {Colors} from "../../colors";
+import {MapView} from "../mapview";
+import {Layer} from "./layer";
+
+
+/**
+ * Class responsible for rendering the rooms.
+ */
+export class RoomLayer implements Layer {
+ public container: createjs.Container;
+ public coloringMode: boolean;
+ public selectedTiles: ITile[];
+ public selectedTileObjects: TilePositionObject[];
+ public intensityLevels: { [key: number]: IntensityLevel; } = {};
+
+ private mapView: MapView;
+ private allRoomTileObjects: TilePositionObject[];
+ private validNextTilePositions: IGridPosition[];
+
+
+ constructor(mapView: MapView) {
+ this.mapView = mapView;
+ this.container = new createjs.Container();
+
+ this.allRoomTileObjects = [];
+ this.selectedTiles = [];
+ this.validNextTilePositions = [];
+ this.selectedTileObjects = [];
+ this.coloringMode = false;
+
+ this.draw();
+ }
+
+ /**
+ * Draws all rooms to the canvas.
+ */
+ public draw() {
+ this.container.removeAllChildren();
+
+ this.mapView.currentDatacenter.rooms.forEach((room: IRoom) => {
+ let color = Colors.ROOM_DEFAULT;
+
+ if (this.coloringMode && room.roomType === "SERVER" && this.intensityLevels[room.id] !== undefined) {
+ color = Util.convertIntensityToColor(this.intensityLevels[room.id]);
+ }
+
+ room.tiles.forEach((tile: ITile) => {
+ this.allRoomTileObjects.push({
+ position: tile.position,
+ tileObject: MapView.drawRectangle(tile.position, color, this.container)
+ });
+ });
+ });
+ }
+
+ /**
+ * Adds a newly selected tile to the list of selected tiles.
+ *
+ * If the tile was already selected beforehand, it is removed.
+ *
+ * @param tile The tile to be added
+ */
+ public addSelectedTile(tile: ITile): void {
+ this.selectedTiles.push(tile);
+
+ let tileObject = MapView.drawRectangle(tile.position, Colors.ROOM_SELECTED, this.container);
+ this.selectedTileObjects.push({
+ position: {x: tile.position.x, y: tile.position.y},
+ tileObject: tileObject
+ });
+
+ this.validNextTilePositions = Util.deriveValidNextTilePositions(
+ this.mapView.currentDatacenter.rooms, this.selectedTiles);
+
+ this.mapView.updateScene = true;
+ }
+
+ /**
+ * Removes a selected tile (upon being clicked on again).
+ *
+ * @param position The position at which a selected tile should be removed
+ * @param objectIndex The index of the tile in the selectedTileObjects array
+ */
+ public removeSelectedTile(position: IGridPosition, objectIndex: number): void {
+ let index = Util.tileListPositionIndexOf(this.selectedTiles, position);
+
+ // Check whether the given position doesn't belong to an already removed tile
+ if (index === -1) {
+ return;
+ }
+
+ this.selectedTiles.splice(index, 1);
+
+ this.container.removeChild(this.selectedTileObjects[objectIndex].tileObject);
+ this.selectedTileObjects.splice(objectIndex, 1);
+
+ this.validNextTilePositions = Util.deriveValidNextTilePositions(
+ this.mapView.currentDatacenter.rooms, this.selectedTiles);
+
+ this.mapView.updateScene = true;
+ }
+
+ /**
+ * Checks whether a hovered tile is in a valid location.
+ *
+ * @param position The tile location to be checked
+ * @returns {boolean} Whether it is a valid location
+ */
+ public checkHoverTileValidity(position: IGridPosition): boolean {
+ if (this.mapView.mapController.interactionLevel === InteractionLevel.BUILDING) {
+ if (this.selectedTiles.length === 0) {
+ return !Util.checkRoomCollision(this.mapView.currentDatacenter.rooms, position);
+ }
+ return Util.positionListContainsPosition(this.validNextTilePositions, position);
+ } else if (this.mapView.mapController.interactionLevel === InteractionLevel.ROOM) {
+ let valid = false;
+ this.mapView.mapController.roomModeController.currentRoom.tiles.forEach((element: ITile) => {
+ if (position.x === element.position.x && position.y === element.position.y &&
+ element.object === undefined) {
+ valid = true;
+ }
+ });
+ return valid;
+ }
+ }
+
+ /**
+ * Cancels room tile selection by removing all selected tiles from the scene.
+ */
+ public cancelRoomConstruction(): void {
+ if (this.selectedTiles.length === 0) {
+ return;
+ }
+
+ this.selectedTileObjects.forEach((tileObject: TilePositionObject) => {
+ this.container.removeChild(tileObject.tileObject);
+ });
+
+ this.resetTileLists();
+
+ this.mapView.updateScene = true;
+ }
+
+ /**
+ * Finalizes the selected room tiles into a standard room.
+ */
+ public finalizeRoom(room: IRoom): void {
+ if (this.selectedTiles.length === 0) {
+ return;
+ }
+
+ this.mapView.currentDatacenter.rooms.push(room);
+
+ this.resetTileLists();
+
+ // Trigger a redraw
+ this.draw();
+ this.mapView.wallLayer.generateWalls();
+ this.mapView.wallLayer.draw();
+
+ this.mapView.updateScene = true;
+ }
+
+ private resetTileLists(): void {
+ this.selectedTiles = [];
+ this.validNextTilePositions = [];
+ this.selectedTileObjects = [];
+ }
+
+ public setClickable(value: boolean): void {
+ this.allRoomTileObjects.forEach((tileObj: TilePositionObject) => {
+ tileObj.tileObject.cursor = value ? "pointer" : "default";
+ });
+ }
+} \ No newline at end of file
diff --git a/src/scripts/views/layers/roomtext.ts b/src/scripts/views/layers/roomtext.ts
new file mode 100644
index 00000000..65ea0735
--- /dev/null
+++ b/src/scripts/views/layers/roomtext.ts
@@ -0,0 +1,68 @@
+import {MapView} from "../mapview";
+import {Colors} from "../../colors";
+import {Util} from "../../util";
+import {Layer} from "./layer";
+import {CELL_SIZE} from "../../controllers/mapcontroller";
+
+
+export class RoomTextLayer implements Layer {
+ private static TEXT_PADDING = 4;
+
+ public container: createjs.Container;
+
+ private mapView: MapView;
+
+
+ constructor(mapView: MapView) {
+ this.mapView = mapView;
+ this.container = new createjs.Container();
+
+ this.draw();
+ }
+
+ public draw(): void {
+ this.container.removeAllChildren();
+
+ this.mapView.currentDatacenter.rooms.forEach((room: IRoom) => {
+ if (room.name !== "" && room.roomType !== "") {
+ this.renderTextOverlay(room);
+ }
+ });
+ }
+
+ public setVisibility(value: boolean): void {
+ this.mapView.animate(this.container, {alpha: value === true ? 1 : 0});
+ }
+
+ /**
+ * Draws a name and type overlay over the given room.
+ */
+ private renderTextOverlay(room: IRoom): void {
+ if (room.name === null || room.tiles.length === 0) {
+ return;
+ }
+
+ let textPos = Util.calculateRoomNamePosition(room);
+
+ let bottomY = this.renderText(room.name, "12px Arial", textPos,
+ textPos.topLeft.y * CELL_SIZE + RoomTextLayer.TEXT_PADDING);
+ this.renderText("Type: " + Util.toSentenceCase(room.roomType), "10px Arial", textPos, bottomY + 5);
+ }
+
+ private renderText(text: string, font: string, textPos: IRoomNamePos, startY: number): number {
+ let name = new createjs.Text(text, font, Colors.ROOM_NAME_COLOR);
+
+ if (name.getMeasuredWidth() > textPos.length * CELL_SIZE - RoomTextLayer.TEXT_PADDING * 2) {
+ name.scaleX = name.scaleY = (textPos.length * CELL_SIZE - RoomTextLayer.TEXT_PADDING * 2) /
+ name.getMeasuredWidth();
+ }
+
+ // Position the text to the top left of the selected tile
+ name.x = textPos.topLeft.x * CELL_SIZE + RoomTextLayer.TEXT_PADDING;
+ name.y = startY;
+
+ this.container.addChild(name);
+
+ return name.y + name.getMeasuredHeight() * name.scaleY;
+ }
+}
diff --git a/src/scripts/views/layers/wall.ts b/src/scripts/views/layers/wall.ts
new file mode 100644
index 00000000..06ba4675
--- /dev/null
+++ b/src/scripts/views/layers/wall.ts
@@ -0,0 +1,62 @@
+import {Colors} from "../../colors";
+import {MapView} from "../mapview";
+import {Util} from "../../util";
+import {Layer} from "./layer";
+import {CELL_SIZE} from "../../controllers/mapcontroller";
+
+
+/**
+ * Class responsible for rendering the walls.
+ */
+export class WallLayer implements Layer {
+ public container: createjs.Container;
+
+ private mapView: MapView;
+ private walls: IRoomWall[];
+ private wallLineWidth: number;
+
+
+ constructor(mapView: MapView) {
+ this.mapView = mapView;
+ this.container = new createjs.Container();
+ this.wallLineWidth = CELL_SIZE / 20.0;
+
+ this.generateWalls();
+ this.draw();
+ }
+
+ /**
+ * Calls the Util.deriveWallLocations function to generate the wall locations.
+ */
+ public generateWalls(): void {
+ this.walls = Util.deriveWallLocations(this.mapView.currentDatacenter.rooms);
+ }
+
+ /**
+ * Draws all walls to the canvas.
+ */
+ public draw(): void {
+ this.container.removeAllChildren();
+
+ // Draw walls
+ this.walls.forEach((element: IRoomWall) => {
+ if (element.horizontal) {
+ MapView.drawLine(
+ CELL_SIZE * element.startPos[0] - this.wallLineWidth / 2.0,
+ CELL_SIZE * element.startPos[1],
+ CELL_SIZE * (element.startPos[0] + element.length) + this.wallLineWidth / 2.0,
+ CELL_SIZE * element.startPos[1],
+ this.wallLineWidth, Colors.WALL_COLOR, this.container
+ );
+ } else {
+ MapView.drawLine(
+ CELL_SIZE * element.startPos[0],
+ CELL_SIZE * element.startPos[1] - this.wallLineWidth / 2.0,
+ CELL_SIZE * element.startPos[0],
+ CELL_SIZE * (element.startPos[1] + element.length) + this.wallLineWidth / 2.0,
+ this.wallLineWidth, Colors.WALL_COLOR, this.container
+ );
+ }
+ });
+ }
+} \ No newline at end of file
diff --git a/src/scripts/views/mapview.ts b/src/scripts/views/mapview.ts
new file mode 100644
index 00000000..ae7fd5cb
--- /dev/null
+++ b/src/scripts/views/mapview.ts
@@ -0,0 +1,373 @@
+///<reference path="../../../typings/globals/createjs-lib/index.d.ts" />
+///<reference path="../../../typings/globals/easeljs/index.d.ts" />
+///<reference path="../../../typings/globals/tweenjs/index.d.ts" />
+///<reference path="../../../typings/globals/preloadjs/index.d.ts" />
+///<reference path="../definitions.ts" />
+///<reference path="../controllers/mapcontroller.ts" />
+import * as $ from "jquery";
+import {Util} from "../util";
+import {MapController, CELL_SIZE} from "../controllers/mapcontroller";
+import {GridLayer} from "./layers/grid";
+import {RoomLayer} from "./layers/room";
+import {HoverLayer} from "./layers/hover";
+import {WallLayer} from "./layers/wall";
+import {DCObjectLayer} from "./layers/dcobject";
+import {GrayLayer} from "./layers/gray";
+import {RoomTextLayer} from "./layers/roomtext";
+
+
+/**
+ * Class responsible for rendering the map, by delegating the rendering tasks to appropriate instances.
+ */
+export class MapView {
+ public static MAP_SIZE = 100;
+ public static CELL_SIZE_METERS = 0.5;
+ public static MIN_ZOOM = 0.5;
+ public static DEFAULT_ZOOM = 2;
+ public static MAX_ZOOM = 6;
+ public static GAP_CORRECTION_DELTA = 0.2;
+ public static ANIMATION_LENGTH = 250;
+
+ // Models
+ public simulation: ISimulation;
+ public currentDatacenter: IDatacenter;
+
+ // Controllers
+ public mapController: MapController;
+
+ // Canvas objects
+ public stage: createjs.Stage;
+ public mapContainer: createjs.Container;
+
+ // Flag indicating whether the scene should be redrawn
+ public updateScene: boolean;
+ public animating: boolean;
+
+ // Subviews
+ public gridLayer: GridLayer;
+ public roomLayer: RoomLayer;
+ public dcObjectLayer: DCObjectLayer;
+ public roomTextLayer: RoomTextLayer;
+ public hoverLayer: HoverLayer;
+ public wallLayer: WallLayer;
+ public grayLayer: GrayLayer;
+
+ // Dynamic canvas attributes
+ public canvasWidth: number;
+ public canvasHeight: number;
+
+
+ /**
+ * Draws a line from (x1, y1) to (x2, y2).
+ *
+ * @param x1 The x coord. of start point
+ * @param y1 The y coord. of start point
+ * @param x2 The x coord. of end point
+ * @param y2 The y coord. of end point
+ * @param lineWidth The width of the line to be drawn
+ * @param color The color to be used
+ * @param container The container to be drawn to
+ */
+ public static drawLine(x1: number, y1: number, x2: number, y2: number,
+ lineWidth: number, color: string, container: createjs.Container): createjs.Shape {
+ let line = new createjs.Shape();
+ line.graphics.setStrokeStyle(lineWidth).beginStroke(color);
+ line.graphics.moveTo(x1, y1);
+ line.graphics.lineTo(x2, y2);
+ container.addChild(line);
+ return line;
+ }
+
+ /**
+ * Draws a tile at the given location with the given color.
+ *
+ * @param position The grid coordinates of the tile
+ * @param color The color with which the rectangle should be drawn
+ * @param container The container to be drawn to
+ * @param sizeX Optional parameter specifying the width of the tile to be drawn (in grid units)
+ * @param sizeY Optional parameter specifying the height of the tile to be drawn (in grid units)
+ */
+ public static drawRectangle(position: IGridPosition, color: string, container: createjs.Container,
+ sizeX?: number, sizeY?: number): createjs.Shape {
+ let tile = new createjs.Shape();
+ tile.graphics.setStrokeStyle(0);
+ tile.graphics.beginFill(color);
+ tile.graphics.drawRect(
+ position.x * CELL_SIZE - MapView.GAP_CORRECTION_DELTA,
+ position.y * CELL_SIZE - MapView.GAP_CORRECTION_DELTA,
+ CELL_SIZE * (sizeX === undefined ? 1 : sizeX) + MapView.GAP_CORRECTION_DELTA * 2,
+ CELL_SIZE * (sizeY === undefined ? 1 : sizeY) + MapView.GAP_CORRECTION_DELTA * 2
+ );
+ container.addChild(tile);
+ return tile;
+ }
+
+ /**
+ * Draws a tile at the given location with the given color, and add it to the given shape object.
+ *
+ * The fill color must be set beforehand, in order to not set it repeatedly and produce unwanted transparent overlap
+ * artifacts.
+ *
+ * @param position The grid coordinates of the tile
+ * @param shape The shape to be drawn to
+ * @param sizeX Optional parameter specifying the width of the tile to be drawn (in grid units)
+ * @param sizeY Optional parameter specifying the height of the tile to be drawn (in grid units)
+ */
+ public static drawRectangleToShape(position: IGridPosition, shape: createjs.Shape,
+ sizeX?: number, sizeY?: number) {
+ shape.graphics.drawRect(
+ position.x * CELL_SIZE - MapView.GAP_CORRECTION_DELTA,
+ position.y * CELL_SIZE - MapView.GAP_CORRECTION_DELTA,
+ CELL_SIZE * (sizeX === undefined ? 1 : sizeX) + MapView.GAP_CORRECTION_DELTA * 2,
+ CELL_SIZE * (sizeY === undefined ? 1 : sizeY) + MapView.GAP_CORRECTION_DELTA * 2
+ );
+ }
+
+ constructor(simulation: ISimulation, stage: createjs.Stage) {
+ this.simulation = simulation;
+ let path = this.simulation.paths[this.simulation.paths.length - 1];
+ this.currentDatacenter = path.sections[path.sections.length - 1].datacenter;
+
+ this.stage = stage;
+
+ console.log("THE DATA", simulation);
+
+ let canvas = $("#main-canvas");
+ this.canvasWidth = canvas.width();
+ this.canvasHeight = canvas.height();
+
+ this.mapContainer = new createjs.Container();
+
+ this.initializeLayers();
+
+ this.drawMap();
+ this.updateScene = true;
+ this.animating = false;
+
+ this.mapController = new MapController(this);
+
+ // Zoom DC to fit, if rooms are present
+ if (this.currentDatacenter.rooms.length > 0) {
+ this.zoomOutOnDC();
+ }
+
+ // Checks at every rendering tick whether the scene has changed, and updates accordingly
+ createjs.Ticker.addEventListener("tick", (event: createjs.TickerEvent) => {
+ if (this.updateScene || this.animating) {
+ if (this.mapController.isInHoverMode()) {
+ this.hoverLayer.draw();
+ }
+
+ this.updateScene = false;
+ this.stage.update(event);
+ }
+ });
+ }
+
+ private initializeLayers(): void {
+ this.gridLayer = new GridLayer(this);
+ this.roomLayer = new RoomLayer(this);
+ this.dcObjectLayer = new DCObjectLayer(this);
+ this.roomTextLayer = new RoomTextLayer(this);
+ this.hoverLayer = new HoverLayer(this);
+ this.wallLayer = new WallLayer(this);
+ this.grayLayer = new GrayLayer(this);
+ }
+
+ /**
+ * Triggers a redraw and re-population action on all layers.
+ */
+ public redrawMap(): void {
+ this.gridLayer.draw();
+ this.roomLayer.draw();
+ this.dcObjectLayer.populateObjectList();
+ this.dcObjectLayer.draw();
+ this.roomTextLayer.draw();
+ this.hoverLayer.initialDraw();
+ this.wallLayer.generateWalls();
+ this.wallLayer.draw();
+ this.grayLayer.draw(true);
+ this.updateScene = true;
+ }
+
+ /**
+ * Zooms in on a given position with a given amount.
+ *
+ * @param position The position that should appear centered after the zoom action
+ * @param amount The amount of zooming that should be performed
+ */
+ public zoom(position: number[], amount: number): void {
+ const newZoom = this.mapContainer.scaleX + 0.01 * amount;
+
+ // Check whether zooming too far in / out
+ if (newZoom > MapView.MAX_ZOOM ||
+ newZoom < MapView.MIN_ZOOM) {
+ return;
+ }
+
+ // Calculate position difference if zoomed, in order to later compensate for this
+ // unwanted movement
+ let oldPosition = [
+ position[0] - this.mapContainer.x, position[1] - this.mapContainer.y
+ ];
+ let newPosition = [
+ (oldPosition[0] / this.mapContainer.scaleX) * newZoom,
+ (oldPosition[1] / this.mapContainer.scaleX) * newZoom
+ ];
+ let positionDelta = [
+ newPosition[0] - oldPosition[0], newPosition[1] - oldPosition[1]
+ ];
+
+ // Apply the transformation operation to keep the selected position static
+ let newX = this.mapContainer.x - positionDelta[0];
+ let newY = this.mapContainer.y - positionDelta[1];
+
+ let finalPos = this.mapController.checkCanvasMovement(newX, newY, newZoom);
+
+ if (!this.animating) {
+ this.animate(this.mapContainer, {
+ scaleX: newZoom, scaleY: newZoom,
+ x: finalPos.x, y: finalPos.y
+ });
+ }
+ }
+
+ /**
+ * Adjusts the viewing scale to fully display a selected room and center it in view.
+ *
+ * @param room The room to be centered
+ * @param redraw Optional argument specifying whether this is a scene redraw
+ */
+ public zoomInOnRoom(room: IRoom, redraw?: boolean): void {
+ this.zoomInOnRooms([room]);
+
+ if (redraw === undefined || redraw === false) {
+ if (!this.grayLayer.isGrayedOut()) {
+ this.grayLayer.currentRoom = room;
+ this.grayLayer.draw();
+ }
+ }
+
+ this.updateScene = true;
+ }
+
+ /**
+ * Zooms out to global building view.
+ */
+ public zoomOutOnDC(): void {
+ this.grayLayer.clear();
+
+ if (this.currentDatacenter.rooms.length > 0) {
+ this.zoomInOnRooms(this.currentDatacenter.rooms);
+ }
+
+ this.updateScene = true;
+ }
+
+ /**
+ * Fits a given list of rooms to view, by scaling the viewport appropriately and moving the mapContainer.
+ *
+ * @param rooms The array of rooms to be viewed
+ */
+ private zoomInOnRooms(rooms: IRoom[]): void {
+ let bounds = Util.calculateRoomListBounds(rooms);
+ let newScale = this.calculateNewScale(bounds);
+
+ // Coordinates of the center of the room, relative to the global origin of the map
+ let roomCenterCoords = [
+ bounds.center[0] * CELL_SIZE * newScale,
+ bounds.center[1] * CELL_SIZE * newScale
+ ];
+ // Coordinates of the center of the stage (the visible part of the canvas), relative to the global map origin
+ let stageCenterCoords = [
+ -this.mapContainer.x + this.canvasWidth / 2,
+ -this.mapContainer.y + this.canvasHeight / 2
+ ];
+
+ let newX = this.mapContainer.x - roomCenterCoords[0] + stageCenterCoords[0];
+ let newY = this.mapContainer.y - roomCenterCoords[1] + stageCenterCoords[1];
+
+ let newPosition = this.mapController.checkCanvasMovement(newX, newY, newScale);
+
+ this.animate(this.mapContainer, {
+ scaleX: newScale, scaleY: newScale,
+ x: newPosition.x, y: newPosition.y
+ });
+ }
+
+ private calculateNewScale(bounds: IBounds): number {
+ const viewPadding = 30;
+ const sideMenuWidth = 350;
+
+ let width = bounds.max[0] - bounds.min[0];
+ let height = bounds.max[1] - bounds.min[1];
+
+ let scaleX = (this.canvasWidth - 2 * sideMenuWidth) / (width * CELL_SIZE + 2 * viewPadding);
+ let scaleY = this.canvasHeight / (height * CELL_SIZE + 2 * viewPadding);
+
+ let newScale = Math.min(scaleX, scaleY);
+
+ if (this.mapContainer.scaleX > MapView.MAX_ZOOM) {
+ newScale = MapView.MAX_ZOOM;
+ } else if (this.mapContainer.scaleX < MapView.MIN_ZOOM) {
+ newScale = MapView.MIN_ZOOM;
+ }
+
+ return newScale;
+ }
+
+ /**
+ * Draws all tiles contained in the MapModel.
+ */
+ private drawMap(): void {
+ // Create and draw the container for the entire map
+ let gridPixelSize = CELL_SIZE * MapView.MAP_SIZE;
+
+ // Add a white background to the entire container
+ let background = new createjs.Shape();
+ background.graphics.beginFill("#fff");
+ background.graphics.drawRect(0, 0,
+ gridPixelSize, gridPixelSize);
+ this.mapContainer.addChild(background);
+
+ this.stage.addChild(this.mapContainer);
+
+ // Set the map container to a default offset and zoom state (overridden if rooms are present)
+ this.mapContainer.x = -50;
+ this.mapContainer.y = -50;
+ this.mapContainer.scaleX = this.mapContainer.scaleY = MapView.DEFAULT_ZOOM;
+
+ this.addLayerContainers();
+ }
+
+ private addLayerContainers(): void {
+ this.mapContainer.addChild(this.gridLayer.container);
+ this.mapContainer.addChild(this.roomLayer.container);
+ this.mapContainer.addChild(this.dcObjectLayer.container);
+ this.mapContainer.addChild(this.roomTextLayer.container);
+ this.mapContainer.addChild(this.hoverLayer.container);
+ this.mapContainer.addChild(this.wallLayer.container);
+ this.mapContainer.addChild(this.grayLayer.container);
+ }
+
+ /**
+ * Wrapper function for TweenJS animate functionality.
+ *
+ * @param target What to animate
+ * @param properties Properties to be passed on to TweenJS
+ * @param callback To be called when animation ready
+ */
+ public animate(target: any, properties: any, callback?: () => any): void {
+ this.animating = true;
+ createjs.Tween.get(target)
+ .to(properties, MapView.ANIMATION_LENGTH, createjs.Ease.getPowInOut(4))
+ .call(() => {
+ this.animating = false;
+ this.updateScene = true;
+
+ if (callback !== undefined) {
+ callback();
+ }
+ });
+ }
+} \ No newline at end of file