From 90fae26aa4bd0e0eb3272ff6e6524060e9004fbb Mon Sep 17 00:00:00 2001 From: Georgios Andreadis Date: Mon, 29 Jun 2020 15:47:09 +0200 Subject: Prepare frontend repository for monorepo This change prepares the frontend Git repository for the monorepo residing at https://github.com/atlarge-research.com/opendc. To accomodate for this, we move all files into a frontend subdirectory. --- frontend/src/util/authorizations.js | 11 ++ frontend/src/util/colors.js | 29 ++++ frontend/src/util/date-time.js | 104 +++++++++++++ frontend/src/util/date-time.test.js | 35 +++++ frontend/src/util/jquery.js | 8 + frontend/src/util/room-types.js | 7 + frontend/src/util/simulation-load.js | 37 +++++ frontend/src/util/tile-calculations.js | 261 +++++++++++++++++++++++++++++++++ frontend/src/util/timeline.js | 19 +++ 9 files changed, 511 insertions(+) create mode 100644 frontend/src/util/authorizations.js create mode 100644 frontend/src/util/colors.js create mode 100644 frontend/src/util/date-time.js create mode 100644 frontend/src/util/date-time.test.js create mode 100644 frontend/src/util/jquery.js create mode 100644 frontend/src/util/room-types.js create mode 100644 frontend/src/util/simulation-load.js create mode 100644 frontend/src/util/tile-calculations.js create mode 100644 frontend/src/util/timeline.js (limited to 'frontend/src/util') diff --git a/frontend/src/util/authorizations.js b/frontend/src/util/authorizations.js new file mode 100644 index 00000000..ef649c9c --- /dev/null +++ b/frontend/src/util/authorizations.js @@ -0,0 +1,11 @@ +export const AUTH_ICON_MAP = { + OWN: "home", + EDIT: "pencil", + VIEW: "eye" +}; + +export const AUTH_DESCRIPTION_MAP = { + OWN: "Own", + EDIT: "Can Edit", + VIEW: "Can View" +}; diff --git a/frontend/src/util/colors.js b/frontend/src/util/colors.js new file mode 100644 index 00000000..1e84e162 --- /dev/null +++ b/frontend/src/util/colors.js @@ -0,0 +1,29 @@ +export const GRID_COLOR = "rgba(0, 0, 0, 0.5)"; +export const BACKDROP_COLOR = "rgba(255, 255, 255, 1)"; +export const WALL_COLOR = "rgba(0, 0, 0, 1)"; + +export const ROOM_DEFAULT_COLOR = "rgba(150, 150, 150, 1)"; +export const ROOM_IN_CONSTRUCTION_COLOR = "rgba(51, 153, 255, 1)"; +export const ROOM_HOVER_VALID_COLOR = "rgba(51, 153, 255, 1)"; +export const ROOM_HOVER_INVALID_COLOR = "rgba(255, 102, 0, 1)"; +export const ROOM_NAME_COLOR = "rgba(245, 245, 245, 1)"; +export const ROOM_TYPE_COLOR = "rgba(245, 245, 245, 1)"; + +export const TILE_PLUS_COLOR = "rgba(0, 0, 0, 1)"; + +export const OBJECT_BORDER_COLOR = "rgba(0, 0, 0, 1)"; + +export const RACK_BACKGROUND_COLOR = "rgba(170, 170, 170, 1)"; +export const RACK_SPACE_BAR_BACKGROUND_COLOR = "rgba(222, 235, 247, 0.6)"; +export const RACK_SPACE_BAR_FILL_COLOR = "rgba(91, 155, 213, 0.7)"; +export const RACK_ENERGY_BAR_BACKGROUND_COLOR = "rgba(255, 242, 204, 0.6)"; +export const RACK_ENERGY_BAR_FILL_COLOR = "rgba(244, 215, 0, 0.7)"; +export const COOLING_ITEM_BACKGROUND_COLOR = "rgba(40, 50, 230, 1)"; +export const PSU_BACKGROUND_COLOR = "rgba(230, 50, 60, 1)"; + +export const GRAYED_OUT_AREA_COLOR = "rgba(0, 0, 0, 0.6)"; + +export const SIM_LOW_COLOR = "rgba(197, 224, 180, 1)"; +export const SIM_MID_LOW_COLOR = "rgba(255, 230, 153, 1)"; +export const SIM_MID_HIGH_COLOR = "rgba(248, 203, 173, 1)"; +export const SIM_HIGH_COLOR = "rgba(249, 165, 165, 1)"; diff --git a/frontend/src/util/date-time.js b/frontend/src/util/date-time.js new file mode 100644 index 00000000..0b752600 --- /dev/null +++ b/frontend/src/util/date-time.js @@ -0,0 +1,104 @@ +/** + * Parses and formats the given date-time string representation. + * + * The format assumed is "YYYY-MM-DDTHH:MM:SS". + * + * @param dateTimeString A string expressing a date and a time, in the above mentioned format. + * @returns {string} A human-friendly string version of that date and time. + */ +export function parseAndFormatDateTime(dateTimeString) { + return formatDateTime(parseDateTime(dateTimeString)); +} + +/** + * Parses date-time string representations and returns a parsed object. + * + * The format assumed is "YYYY-MM-DDTHH:MM:SS". + * + * @param dateTimeString A string expressing a date and a time, in the above mentioned format. + * @returns {object} A Date object with the parsed date and time information as content. + */ +export function parseDateTime(dateTimeString) { + return new Date(dateTimeString + ".000Z"); +} + +/** + * Serializes the given date and time value to a human-friendly string. + * + * @param dateTime An object representation of a date and time. + * @returns {string} A human-friendly string version of that date and time. + */ +export function formatDateTime(dateTime) { + let date; + const currentDate = new Date(); + + date = + addPaddingToTwo(dateTime.getDay()) + + "/" + + addPaddingToTwo(dateTime.getMonth()) + + "/" + + addPaddingToTwo(dateTime.getFullYear()); + + if ( + dateTime.getFullYear() === currentDate.getFullYear() && + dateTime.getMonth() === currentDate.getMonth() + ) { + if (dateTime.getDate() === currentDate.getDate()) { + date = "Today"; + } else if (dateTime.getDate() === currentDate.getDate() - 1) { + date = "Yesterday"; + } + } + + return ( + date + + ", " + + addPaddingToTwo(dateTime.getHours()) + + ":" + + addPaddingToTwo(dateTime.getMinutes()) + ); +} + +/** + * Formats the given number of seconds/ticks to a formatted time representation. + * + * @param seconds The number of seconds. + * @returns {string} A string representation of that amount of second, in the from of HH:MM:SS. + */ +export function convertSecondsToFormattedTime(seconds) { + if (seconds <= 0) { + return "0s"; + } + + let hour = Math.floor(seconds / 3600); + let minute = Math.floor(seconds / 60) % 60; + let second = seconds % 60; + + hour = isNaN(hour) ? 0 : hour; + minute = isNaN(minute) ? 0 : minute; + second = isNaN(second) ? 0 : second; + + if (hour === 0 && minute === 0) { + return second + "s"; + } else if (hour === 0) { + return minute + "m" + addPaddingToTwo(second) + "s"; + } else { + return ( + hour + "h" + addPaddingToTwo(minute) + "m" + addPaddingToTwo(second) + "s" + ); + } +} + +/** + * Pads the given integer to have at least two digits. + * + * @param integer An integer to be padded. + * @returns {string} A string containing the padded integer. + */ +function addPaddingToTwo(integer) { + if (integer < 10) { + return "0" + integer.toString(); + } else { + return integer.toString(); + } +} diff --git a/frontend/src/util/date-time.test.js b/frontend/src/util/date-time.test.js new file mode 100644 index 00000000..6c7a6b16 --- /dev/null +++ b/frontend/src/util/date-time.test.js @@ -0,0 +1,35 @@ +import { convertSecondsToFormattedTime, parseDateTime } from "./date-time"; + +describe("date-time parsing", () => { + it("reads components properly", () => { + const dateString = "2017-09-27T20:55:01"; + const parsedDate = parseDateTime(dateString); + + expect(parsedDate.getUTCFullYear()).toEqual(2017); + expect(parsedDate.getUTCMonth()).toEqual(8); + expect(parsedDate.getUTCDate()).toEqual(27); + expect(parsedDate.getUTCHours()).toEqual(20); + expect(parsedDate.getUTCMinutes()).toEqual(55); + expect(parsedDate.getUTCSeconds()).toEqual(1); + }); +}); + +describe("tick formatting", () => { + it("returns '0s' for numbers <= 0", () => { + expect(convertSecondsToFormattedTime(-1)).toEqual("0s"); + expect(convertSecondsToFormattedTime(0)).toEqual("0s"); + }); + it("returns only seconds for values under a minute", () => { + expect(convertSecondsToFormattedTime(1)).toEqual("1s"); + expect(convertSecondsToFormattedTime(59)).toEqual("59s"); + }); + it("returns seconds and minutes for values under an hour", () => { + expect(convertSecondsToFormattedTime(60)).toEqual("1m00s"); + expect(convertSecondsToFormattedTime(61)).toEqual("1m01s"); + expect(convertSecondsToFormattedTime(3599)).toEqual("59m59s"); + }); + it("returns full time for values over an hour", () => { + expect(convertSecondsToFormattedTime(3600)).toEqual("1h00m00s"); + expect(convertSecondsToFormattedTime(3601)).toEqual("1h00m01s"); + }); +}); diff --git a/frontend/src/util/jquery.js b/frontend/src/util/jquery.js new file mode 100644 index 00000000..12a64fc6 --- /dev/null +++ b/frontend/src/util/jquery.js @@ -0,0 +1,8 @@ +/** + * Binding of the global jQuery variable for use within React. + * + * This should be used instead of '$', to address ESLint warnings relating to undefined global variables. + */ +const jQuery = window["$"]; + +export default jQuery; diff --git a/frontend/src/util/room-types.js b/frontend/src/util/room-types.js new file mode 100644 index 00000000..5cfe3887 --- /dev/null +++ b/frontend/src/util/room-types.js @@ -0,0 +1,7 @@ +export const ROOM_TYPE_TO_NAME_MAP = { + SERVER: "Server room", + HALLWAY: "Hallway", + OFFICE: "Office", + POWER: "Power room", + COOLING: "Cooling room" +}; diff --git a/frontend/src/util/simulation-load.js b/frontend/src/util/simulation-load.js new file mode 100644 index 00000000..95e17fed --- /dev/null +++ b/frontend/src/util/simulation-load.js @@ -0,0 +1,37 @@ +import { + SIM_HIGH_COLOR, + SIM_LOW_COLOR, + SIM_MID_HIGH_COLOR, + SIM_MID_LOW_COLOR +} from "./colors"; + +export const LOAD_NAME_MAP = { + LOAD: "computational load", + TEMPERATURE: "temperature", + MEMORY: "memory use" +}; + +export function convertLoadToSimulationColor(load) { + if (load <= 0.25) { + return SIM_LOW_COLOR; + } else if (load <= 0.5) { + return SIM_MID_LOW_COLOR; + } else if (load <= 0.75) { + return SIM_MID_HIGH_COLOR; + } else { + return SIM_HIGH_COLOR; + } +} + +export function getStateLoad(loadMetric, state) { + switch (loadMetric) { + case "LOAD": + return state.loadFraction; + case "TEMPERATURE": + return state.temperatureC / 100.0; + case "MEMORY": + return state.inUseMemoryMb / 10000.0; + default: + return -1; + } +} diff --git a/frontend/src/util/tile-calculations.js b/frontend/src/util/tile-calculations.js new file mode 100644 index 00000000..95886eeb --- /dev/null +++ b/frontend/src/util/tile-calculations.js @@ -0,0 +1,261 @@ +export function deriveWallLocations(tiles) { + const { verticalWalls, horizontalWalls } = getWallSegments(tiles); + return mergeWallSegments(verticalWalls, horizontalWalls); +} + +function getWallSegments(tiles) { + const verticalWalls = {}; + const horizontalWalls = {}; + + tiles.forEach(tile => { + const x = tile.positionX, + y = tile.positionY; + + for (let dX = -1; dX <= 1; dX++) { + for (let dY = -1; dY <= 1; dY++) { + if (Math.abs(dX) === Math.abs(dY)) { + continue; + } + + let doInsert = true; + for (let tileIndex in tiles) { + if ( + tiles[tileIndex].positionX === x + dX && + tiles[tileIndex].positionY === y + dY + ) { + doInsert = false; + break; + } + } + if (!doInsert) { + continue; + } + + if (dX === -1) { + if (verticalWalls[x] === undefined) { + verticalWalls[x] = []; + } + if (verticalWalls[x].indexOf(y) === -1) { + verticalWalls[x].push(y); + } + } else if (dX === 1) { + if (verticalWalls[x + 1] === undefined) { + verticalWalls[x + 1] = []; + } + if (verticalWalls[x + 1].indexOf(y) === -1) { + verticalWalls[x + 1].push(y); + } + } else if (dY === -1) { + if (horizontalWalls[y] === undefined) { + horizontalWalls[y] = []; + } + if (horizontalWalls[y].indexOf(x) === -1) { + horizontalWalls[y].push(x); + } + } else if (dY === 1) { + if (horizontalWalls[y + 1] === undefined) { + horizontalWalls[y + 1] = []; + } + if (horizontalWalls[y + 1].indexOf(x) === -1) { + horizontalWalls[y + 1].push(x); + } + } + } + } + }); + + return { verticalWalls, horizontalWalls }; +} + +function mergeWallSegments(vertical, horizontal) { + const result = []; + const walls = [vertical, horizontal]; + + for (let i = 0; i < 2; i++) { + const wallList = walls[i]; + for (let a in wallList) { + a = parseInt(a, 10); + + wallList[a].sort((a, b) => { + return a - b; + }); + + let startPos = wallList[a][0]; + const isHorizontal = i === 1; + + if (wallList[a].length === 1) { + const startPosX = isHorizontal ? startPos : a; + const startPosY = isHorizontal ? a : startPos; + result.push({ + startPosX, + startPosY, + isHorizontal, + length: 1 + }); + } else { + let consecutiveCount = 1; + for (let b = 0; b < wallList[a].length - 1; b++) { + if (b + 1 === wallList[a].length - 1) { + if (wallList[a][b + 1] - wallList[a][b] > 1) { + const startPosX = isHorizontal ? startPos : a; + const startPosY = isHorizontal ? a : startPos; + result.push({ + startPosX, + startPosY, + isHorizontal, + length: consecutiveCount + }); + consecutiveCount = 0; + startPos = wallList[a][b + 1]; + } + const startPosX = isHorizontal ? startPos : a; + const startPosY = isHorizontal ? a : startPos; + result.push({ + startPosX, + startPosY, + isHorizontal, + length: consecutiveCount + 1 + }); + break; + } else if (wallList[a][b + 1] - wallList[a][b] > 1) { + const startPosX = isHorizontal ? startPos : a; + const startPosY = isHorizontal ? a : startPos; + result.push({ + startPosX, + startPosY, + isHorizontal, + length: consecutiveCount + }); + startPos = wallList[a][b + 1]; + consecutiveCount = 0; + } + consecutiveCount++; + } + } + } + } + + return result; +} + +export function deriveValidNextTilePositions(rooms, selectedTiles) { + const result = [], + newPosition = { x: 0, y: 0 }; + let isSurroundingTile; + + selectedTiles.forEach(tile => { + const x = tile.positionX; + const y = tile.positionY; + result.push({ x, y }); + + for (let dX = -1; dX <= 1; dX++) { + for (let dY = -1; dY <= 1; dY++) { + if (Math.abs(dX) === Math.abs(dY)) { + continue; + } + + newPosition.x = x + dX; + newPosition.y = y + dY; + + isSurroundingTile = true; + for (let index in selectedTiles) { + if ( + selectedTiles[index].positionX === newPosition.x && + selectedTiles[index].positionY === newPosition.y + ) { + isSurroundingTile = false; + break; + } + } + + if ( + isSurroundingTile && + findPositionInRooms(rooms, newPosition.x, newPosition.y) === -1 + ) { + result.push({ x: newPosition.x, y: newPosition.y }); + } + } + } + }); + + return result; +} + +export function findPositionInPositions(positions, positionX, positionY) { + for (let i = 0; i < positions.length; i++) { + const position = positions[i]; + if (positionX === position.x && positionY === position.y) { + return i; + } + } + + return -1; +} + +export function findPositionInRooms(rooms, positionX, positionY) { + for (let i = 0; i < rooms.length; i++) { + const room = rooms[i]; + if (findPositionInTiles(room.tiles, positionX, positionY) !== -1) { + return i; + } + } + + return -1; +} + +function findPositionInTiles(tiles, positionX, positionY) { + let index = -1; + + for (let i = 0; i < tiles.length; i++) { + const tile = tiles[i]; + if (positionX === tile.positionX && positionY === tile.positionY) { + index = i; + break; + } + } + + return index; +} + +export function findTileWithPosition(tiles, positionX, positionY) { + for (let i = 0; i < tiles.length; i++) { + if (tiles[i].positionX === positionX && tiles[i].positionY === positionY) { + return tiles[i]; + } + } + + return null; +} + +export function calculateRoomListBounds(rooms) { + const min = { x: Number.MAX_VALUE, y: Number.MAX_VALUE }; + const max = { x: -1, y: -1 }; + + rooms.forEach(room => { + room.tiles.forEach(tile => { + if (tile.positionX < min.x) { + min.x = tile.positionX; + } + if (tile.positionY < min.y) { + min.y = tile.positionY; + } + + if (tile.positionX > max.x) { + max.x = tile.positionX; + } + if (tile.positionY > max.y) { + max.y = tile.positionY; + } + }); + }); + + max.x++; + max.y++; + + const center = { + x: min.x + (max.x - min.x) / 2.0, + y: min.y + (max.y - min.y) / 2.0 + }; + + return { min, center, max }; +} diff --git a/frontend/src/util/timeline.js b/frontend/src/util/timeline.js new file mode 100644 index 00000000..e20d5823 --- /dev/null +++ b/frontend/src/util/timeline.js @@ -0,0 +1,19 @@ +export function convertTickToPercentage(tick, maxTick) { + if (maxTick === 0) { + return "0%"; + } else if (tick > maxTick) { + return maxTick / (maxTick + 1) * 100 + "%"; + } + + return tick / (maxTick + 1) * 100 + "%"; +} + +export function getDatacenterIdOfTick(tick, sections) { + for (let i in sections.reverse()) { + if (tick >= sections[i].startTick) { + return sections[i].datacenterId; + } + } + + return -1; +} -- cgit v1.2.3