<div id="root"></div>
$shaft-color: #1d252b;
$door-color: #bbb;
$status-color: #e4b16c;
body {
background-color: black;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
}
#root {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}
.elevator {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr;
gap: 0px 0px;
margin-bottom: var(--size-31);
width: 800px;
display: flex;
align-items: center;
justify-content: center;
}
.elevator-example {
display: inline-flex;
margin-bottom: var(--size-31);
&__status {
background-color: var(--color-black);
border-radius: 5px;
color: $status-color;
font-weight: 800;
margin: 0.5rem 0;
padding: 1rem;
}
&__button {
&--item {
appearance: none;
background-color: white;
border: 3px solid black;
border-radius: 50%;
cursor: pointer;
font-size: 1.2rem;
font-weight: 800;
height: 3rem;
margin: 1rem;
outline: none;
width: 3rem;
transition: all 200ms ease-out;
&:hover {
opacity: .8;
}
@media screen and (min-width: 800px) {
border: 5px solid black;
font-size: 1.8rem;
height: 4rem;
width: 4rem;
}
}
}
&__building {
background: black;
height: 500px;
margin: 0 auto;
overflow: hidden;
width: 300px;
}
&__shaft {
background: $shaft-color;
height: 500px;
margin: 0 auto;
position: relative;
width: 33.33%;
}
&__elevator {
bottom: 0;
height: 500px;
position: absolute;
width: 100%;
&--cab {
background-size: 100% 100%;
height: 20%;
overflow: hidden;
position: absolute;
width: 100%;
}
&--door {
background: $door-color;
height: 100%;
position: absolute;
top: 0;
transition: all 300ms ease-in;
width: 49%;
&.left {
left: 0;
}
&.right {
background: $door-color;
right: 0;
}
&.active-right {
right: -50%;
}
&.active-left {
left: -50%;
}
}
}
}
xxxxxxxxxx
import React, { Component, ReactElement, useState } from "https://esm.sh/react@18.0.0";
import ReactDOM from "https://esm.sh/react-dom@18.0.0";
type ElevatorProps = Record<string, unknown>;
type ElevatorState = {
currentFloor: number;
status: string;
requestedFloor: string | number;
previousStatus: string | null;
requestedFloors: number[];
};
function timeout(ms: number): Promise<() => unknown> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function sleep() {
await timeout(2000);
}
export const Status = {
IDLE: "IDLE",
IDLE_PENDING: "IDLE_PENDING",
MOVING_UP: "MOVING_UP",
MOVING_DOWN: "MOVING_DOWN"
};
export enum Direction {
UP = "UP",
DOWN = "DOWN"
}
class Elevator extends Component<ElevatorProps, ElevatorState> {
private readonly maxFloors: number;
constructor(props: ElevatorProps) {
super(props);
this.state = {
currentFloor: 0,
status: Status.IDLE,
requestedFloor: 0,
previousStatus: Status.IDLE,
requestedFloors: []
};
this.maxFloors = 4;
this.setCurrentFloor = this.setCurrentFloor.bind(this);
this.moveElevator = this.moveElevator.bind(this);
this.requestFloor = this.requestFloor.bind(this);
}
async setCurrentFloor(upOrDown: Direction): Promise<void> {
const requestedFloors = Array.from(this.state.requestedFloors);
const currentFloor =
upOrDown === Direction.UP
? this.state.currentFloor + 1
: this.state.currentFloor - 1;
const previousStatus = this.state.status;
const status = !requestedFloors.includes(currentFloor)
? this.state.status
: Status.IDLE_PENDING;
if (requestedFloors.includes(currentFloor)) {
requestedFloors.splice(requestedFloors.indexOf(currentFloor), 1);
}
this.setState(
{
currentFloor,
status,
previousStatus,
requestedFloors
},
async () => {
// Simulates the door opening.
await sleep();
this.setState(
{
status: Status.IDLE
},
() => {
// If this isn't the floor that was requested then we'll move to the next one.
if (this.state.requestedFloors.length) {
this.moveElevator();
} else {
this.setState({
previousStatus: null
});
}
}
);
}
);
}
async moveElevator(): Promise<void> {
const ascendingFloors = this.state.requestedFloors.filter(
(item) => item > this.state.currentFloor
);
const descendingFloors = this.state.requestedFloors.filter(
(item) => item < this.state.currentFloor
);
const nextFloor =
this.state.previousStatus === Status.MOVING_UP && ascendingFloors.length
? ascendingFloors[0]
: this.state.previousStatus === Status.MOVING_DOWN &&
descendingFloors.length
? descendingFloors[0]
: this.state.requestedFloors[0];
if (typeof nextFloor !== "undefined" && this.state.status === Status.IDLE) {
this.setState(
{
status:
nextFloor > this.state.currentFloor
? Status.MOVING_UP
: Status.MOVING_DOWN
},
async () => {
if (this.state.currentFloor !== nextFloor) {
await sleep();
this.setCurrentFloor(
nextFloor > this.state.currentFloor
? Direction.UP
: Direction.DOWN
);
}
}
);
}
}
requestFloor(requestedFloor: number): void {
if (requestedFloor !== this.state.currentFloor) {
const requestedFloors = Array.from(this.state.requestedFloors);
if (!requestedFloors.includes(requestedFloor)) {
requestedFloors.push(requestedFloor);
}
this.setState(
{
requestedFloors
},
() => this.moveElevator()
);
}
}
render(): ReactElement {
return (
<div className="elevator row items-center">
<div
className="flex justify-center"
style={{ flexDirection: "column" }}
>
<div className="elevator-example__status">
Current Floor:{" "}
{this.state.currentFloor === 0 ? "G" : this.state.currentFloor}
</div>
<div className="elevator-example__status">
Current Status: {this.state.status}
</div>
<div className="elevator-example__status">
Requested Floor(s):{" "}
{this.state.requestedFloors.join(", ").replace("0", "G")}
</div>
<div
className="elevator-example__button flex"
style={{ flexWrap: "wrap" }}
>
<button
className="elevator-example__button--item flex items-center justify-center text-black disabled:text-gray-400"
onClick={() => this.requestFloor(0)}
disabled={this.state.requestedFloors.includes(0)}
>
G
</button>
<button
className="elevator-example__button--item flex items-center justify-center text-black disabled:text-gray-400"
onClick={() => this.requestFloor(1)}
disabled={this.state.requestedFloors.includes(1)}
>
1
</button>
<button
className="elevator-example__button--item flex items-center justify-center text-black disabled:text-gray-400"
onClick={() => this.requestFloor(2)}
disabled={this.state.requestedFloors.includes(2)}
>
2
</button>
<button
className="elevator-example__button--item flex items-center justify-center text-black disabled:text-gray-400"
onClick={() => this.requestFloor(3)}
disabled={this.state.requestedFloors.includes(3)}
>
3
</button>
<button
className="elevator-example__button--item flex items-center justify-center text-black disabled:text-gray-400"
onClick={() => this.requestFloor(4)}
disabled={this.state.requestedFloors.includes(4)}
>
4
</button>
</div>
</div>
<div className="elevator-example__building" id="building">
<div className="elevator-example__shaft">
<div className="elevator-example__elevator">
<div
className="elevator-example__elevator--cab"
style={{
backgroundImage: `url("https://i.imgur.com/qU2zQAS.png")`,
bottom: `${
(this.state.currentFloor * (this.maxFloors * 10)) / 2
}%`
}}
>
<div
className={`elevator-example__elevator--door left ${
this.state.status === "IDLE" ||
this.state.status === "IDLE_PENDING"
? "active-left"
: ""
}`}
/>
<div
className={`elevator-example__elevator--door right ${
this.state.status === "IDLE" ||
this.state.status === "IDLE_PENDING"
? "active-right"
: ""
}`}
/>
</div>
</div>
</div>
</div>
</div>
);
}
}
ReactDOM.render(<Elevator />, document.getElementById("root"));
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.